yems-ui 1.1.1 → 1.1.5
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 +711 -100
- package/dist/index.d.mts +25 -44
- package/dist/index.d.ts +25 -44
- package/dist/index.js +175 -306
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +173 -304
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -9
- package/dist/index.css +0 -320
- package/dist/index.css.map +0 -1
package/README.md
CHANGED
|
@@ -1,66 +1,58 @@
|
|
|
1
|
-
#
|
|
1
|
+
# yems-ui
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A React component library with **glassmorphism effects**, **premium micro-animations**, and a fully themeable design system built on Tailwind CSS v4.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/yems-ui)
|
|
6
|
+
[](https://www.npmjs.com/package/yems-ui)
|
|
7
|
+
[](./LICENSE)
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
- ⚡ **Premium Animations** - Smooth micro-interactions using Motion.dev (Framer Motion)
|
|
9
|
-
- 🎯 **TypeScript First** - Full type safety with exported types
|
|
10
|
-
- 🎭 **Radix UI Primitives** - Fully accessible components
|
|
11
|
-
- 🎨 **Tailwind CSS v4** - Modern utility-first styling
|
|
12
|
-
- 🌓 **Dark/Light Mode** - Theme-aware with CSS variables
|
|
13
|
-
- 🚀 **Tree-shakeable** - Import only what you need
|
|
9
|
+
**[Live Demo →](https://yem-ui.vercel.app)** — See all components in action with light/dark mode toggle.
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
---
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
## Table of Contents
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Setup](#setup)
|
|
17
|
+
- [Theming](#theming)
|
|
18
|
+
- [Components](#components)
|
|
19
|
+
- [Dark Mode](#dark-mode)
|
|
20
|
+
- [TypeScript](#typescript)
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
---
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
## Installation
|
|
26
25
|
|
|
27
26
|
```bash
|
|
28
|
-
npm install
|
|
27
|
+
npm install yems-ui
|
|
28
|
+
# or
|
|
29
|
+
pnpm add yems-ui
|
|
30
|
+
# or
|
|
31
|
+
yarn add yems-ui
|
|
29
32
|
```
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
This library requires Tailwind CSS v4:
|
|
34
|
+
**Peer dependencies** (install if not already present):
|
|
34
35
|
|
|
35
36
|
```bash
|
|
36
|
-
npm install
|
|
37
|
+
npm install react react-dom
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
---
|
|
40
41
|
|
|
41
|
-
##
|
|
42
|
+
## Setup
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
yems-ui requires **Tailwind CSS v4** and the `@tailwindcss/vite` plugin.
|
|
44
45
|
|
|
45
|
-
###
|
|
46
|
+
### 1. Install Tailwind CSS v4
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
```css
|
|
50
|
-
@import "tailwindcss";
|
|
51
|
-
@source "../node_modules/@yems-ui/core/dist"; /* <-- REQUIRED! */
|
|
52
|
-
@import "@yems-ui/core/styles.css";
|
|
53
|
-
|
|
54
|
-
/* Your custom styles below... */
|
|
48
|
+
```bash
|
|
49
|
+
npm install tailwindcss @tailwindcss/vite
|
|
55
50
|
```
|
|
56
51
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
### Step 2: Ensure Vite + Tailwind v4 plugin
|
|
60
|
-
|
|
61
|
-
Make sure your `vite.config.ts` includes the Tailwind plugin:
|
|
52
|
+
### 2. Configure Vite
|
|
62
53
|
|
|
63
54
|
```ts
|
|
55
|
+
// vite.config.ts
|
|
64
56
|
import { defineConfig } from "vite";
|
|
65
57
|
import react from "@vitejs/plugin-react";
|
|
66
58
|
import tailwindcss from "@tailwindcss/vite";
|
|
@@ -70,43 +62,51 @@ export default defineConfig({
|
|
|
70
62
|
});
|
|
71
63
|
```
|
|
72
64
|
|
|
73
|
-
|
|
65
|
+
### 3. Import the styles
|
|
74
66
|
|
|
75
|
-
|
|
67
|
+
In your app's entry CSS file (e.g. `src/index.css`):
|
|
76
68
|
|
|
77
|
-
|
|
69
|
+
```css
|
|
70
|
+
@import "tailwindcss";
|
|
78
71
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- **Create React App** - Compatible
|
|
82
|
-
- **Remix** - Works as expected
|
|
83
|
-
- **Astro + React** - Fully compatible
|
|
84
|
-
- **Gatsby** - Supported
|
|
85
|
-
- **Any custom webpack/rollup setup** - Should work fine
|
|
72
|
+
/* Tell Tailwind to scan yems-ui's dist folder for class names */
|
|
73
|
+
@source "../node_modules/yems-ui/dist";
|
|
86
74
|
|
|
87
|
-
|
|
75
|
+
/* Import the design system tokens and base styles */
|
|
76
|
+
@import "yems-ui/styles.css";
|
|
77
|
+
```
|
|
88
78
|
|
|
89
|
-
|
|
79
|
+
> ⚠️ The `@source` directive is required. Without it, Tailwind won't generate the utility classes used by yems-ui components.
|
|
90
80
|
|
|
91
|
-
|
|
81
|
+
### 4. Import styles in your entry file
|
|
92
82
|
|
|
93
83
|
```tsx
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
84
|
+
// src/main.tsx
|
|
85
|
+
import "./index.css";
|
|
86
|
+
import React from "react";
|
|
87
|
+
import ReactDOM from "react-dom/client";
|
|
88
|
+
import App from "./App";
|
|
89
|
+
|
|
90
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
91
|
+
<React.StrictMode>
|
|
92
|
+
<App />
|
|
93
|
+
</React.StrictMode>,
|
|
94
|
+
);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 5. Use components
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { Button, Card, CardHeader, CardTitle, CardContent } from "yems-ui";
|
|
101
101
|
|
|
102
102
|
function App() {
|
|
103
103
|
return (
|
|
104
|
-
<Card
|
|
104
|
+
<Card>
|
|
105
105
|
<CardHeader>
|
|
106
|
-
<CardTitle>
|
|
106
|
+
<CardTitle>Hello World</CardTitle>
|
|
107
107
|
</CardHeader>
|
|
108
108
|
<CardContent>
|
|
109
|
-
<Button variant="primary">
|
|
109
|
+
<Button variant="primary">Get Started</Button>
|
|
110
110
|
</CardContent>
|
|
111
111
|
</Card>
|
|
112
112
|
);
|
|
@@ -115,52 +115,663 @@ function App() {
|
|
|
115
115
|
|
|
116
116
|
---
|
|
117
117
|
|
|
118
|
-
##
|
|
118
|
+
## Theming
|
|
119
|
+
|
|
120
|
+
yems-ui uses a **two-layer CSS variable system** built into Tailwind v4's `@theme`. You can override any part of it in your own CSS.
|
|
121
|
+
|
|
122
|
+
### How it works
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
Layer 1 — Raw palette → Layer 2 — Semantic tokens → Components
|
|
126
|
+
--brand-500: #5000ab → --color-primary: var(--brand-500) → bg-primary
|
|
127
|
+
--accent-500: #e3b23c → --color-accent: var(--accent-500) → bg-accent
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
To retheme the library, you only need to override **Layer 1** values. Everything cascades automatically.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### Changing the primary color
|
|
135
|
+
|
|
136
|
+
Add this to your CSS file after importing yems-ui styles:
|
|
137
|
+
|
|
138
|
+
```css
|
|
139
|
+
@import "tailwindcss";
|
|
140
|
+
@source "../node_modules/yems-ui/dist";
|
|
141
|
+
@import "yems-ui/styles.css";
|
|
142
|
+
|
|
143
|
+
/* Override just what you want to change */
|
|
144
|
+
:root {
|
|
145
|
+
--brand-500: #0066ff; /* new primary — all buttons, links, rings update */
|
|
146
|
+
--brand-900: #001a66; /* new secondary / dark shade */
|
|
147
|
+
--accent-500: #f59e0b; /* new accent color */
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### All themeable tokens
|
|
152
|
+
|
|
153
|
+
#### Brand palette (Layer 1)
|
|
154
|
+
|
|
155
|
+
| Token | Default | Purpose |
|
|
156
|
+
| -------------- | --------- | ---------------------- |
|
|
157
|
+
| `--brand-50` | `#ede8f7` | Lightest brand tint |
|
|
158
|
+
| `--brand-100` | `#d4c5f0` | Light brand tint |
|
|
159
|
+
| `--brand-200` | `#a98cdf` | |
|
|
160
|
+
| `--brand-300` | `#7e53ce` | |
|
|
161
|
+
| `--brand-400` | `#6529be` | |
|
|
162
|
+
| `--brand-500` | `#5000ab` | **Main primary color** |
|
|
163
|
+
| `--brand-600` | `#40008a` | |
|
|
164
|
+
| `--brand-700` | `#300069` | |
|
|
165
|
+
| `--brand-800` | `#200048` | |
|
|
166
|
+
| `--brand-900` | `#1c0636` | Secondary / dark shade |
|
|
167
|
+
| `--accent-500` | `#e3b23c` | **Main accent color** |
|
|
168
|
+
| `--accent-700` | `#bb4d00` | Ember / dark accent |
|
|
169
|
+
|
|
170
|
+
#### Semantic tokens (Layer 2)
|
|
171
|
+
|
|
172
|
+
These are what components use internally. You can override these individually if you want to rewire which color plays which role:
|
|
173
|
+
|
|
174
|
+
| Token | Default maps to | Used for |
|
|
175
|
+
| ---------------------------- | --------------- | ----------------------------------- |
|
|
176
|
+
| `--color-primary` | `--brand-500` | Primary buttons, links, focus rings |
|
|
177
|
+
| `--color-primary-foreground` | `--neutral-50` | Text on primary backgrounds |
|
|
178
|
+
| `--color-secondary` | `--brand-900` | Secondary buttons |
|
|
179
|
+
| `--color-accent` | `--accent-500` | Accent buttons, highlights |
|
|
180
|
+
| `--color-ember` | `--accent-700` | Ember variant |
|
|
181
|
+
| `--color-destructive` | `--color-error` | Destructive actions |
|
|
182
|
+
| `--color-background` | `--neutral-100` | Page background |
|
|
183
|
+
| `--color-foreground` | `--neutral-900` | Body text |
|
|
184
|
+
| `--color-muted` | `--neutral-100` | Subtle backgrounds |
|
|
185
|
+
| `--color-muted-foreground` | `--neutral-500` | Placeholder / hint text |
|
|
186
|
+
| `--color-border` | `--neutral-200` | Borders and dividers |
|
|
187
|
+
| `--color-ring` | `--brand-500` | Focus ring color |
|
|
188
|
+
|
|
189
|
+
#### Shape & spacing
|
|
190
|
+
|
|
191
|
+
| Token | Default | Purpose |
|
|
192
|
+
| ---------- | ------- | ------------------------------------- |
|
|
193
|
+
| `--radius` | `12px` | Base border radius for all components |
|
|
194
|
+
|
|
195
|
+
```css
|
|
196
|
+
:root {
|
|
197
|
+
--radius: 8px; /* more angular */
|
|
198
|
+
/* or */
|
|
199
|
+
--radius: 20px; /* more pill-shaped */
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### Shadows
|
|
204
|
+
|
|
205
|
+
| Token | Default | Purpose |
|
|
206
|
+
| ------------------ | ------------- | -------------------- |
|
|
207
|
+
| `--shadow-sm` | subtle | Small elevation |
|
|
208
|
+
| `--shadow-md` | medium | Default cards |
|
|
209
|
+
| `--shadow-lg` | prominent | Dropdowns, modals |
|
|
210
|
+
| `--shadow-xl` | strong | Tooltips, popovers |
|
|
211
|
+
| `--shadow-primary` | brand-tinted | Primary button hover |
|
|
212
|
+
| `--shadow-accent` | accent-tinted | Accent button hover |
|
|
213
|
+
|
|
214
|
+
```css
|
|
215
|
+
:root {
|
|
216
|
+
/* Softer shadows for a flatter look */
|
|
217
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
218
|
+
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
219
|
+
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.12);
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### Glassmorphism
|
|
224
|
+
|
|
225
|
+
| Token | Light default | Purpose |
|
|
226
|
+
| ------------------- | ------------------------ | --------------------- |
|
|
227
|
+
| `--glass-bg` | `rgba(255,255,255,0.65)` | Glass background |
|
|
228
|
+
| `--glass-bg-strong` | `rgba(255,255,255,0.85)` | Strong glass |
|
|
229
|
+
| `--glass-border` | `rgba(0,0,0,0.08)` | Glass border |
|
|
230
|
+
| `--glass-blur` | `16px` | Backdrop blur amount |
|
|
231
|
+
| `--glass-card-bg` | `rgba(255,255,255,0.75)` | Card glass background |
|
|
232
|
+
| `--glass-shadow` | subtle blue-tinted | Glass drop shadow |
|
|
233
|
+
|
|
234
|
+
```css
|
|
235
|
+
:root {
|
|
236
|
+
/* Heavier blur for more prominent glass effect */
|
|
237
|
+
--glass-blur: 24px;
|
|
238
|
+
--glass-bg: rgba(255, 255, 255, 0.5);
|
|
239
|
+
|
|
240
|
+
/* Or minimal glass — nearly transparent */
|
|
241
|
+
--glass-blur: 8px;
|
|
242
|
+
--glass-bg: rgba(255, 255, 255, 0.9);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### Typography
|
|
247
|
+
|
|
248
|
+
| Token | Default | Purpose |
|
|
249
|
+
| ---------------- | ---------------------- | -------------------- |
|
|
250
|
+
| `--font-sans` | `"Poppins", system-ui` | Body font |
|
|
251
|
+
| `--font-display` | `"Otama EP", Georgia` | Display/heading font |
|
|
252
|
+
|
|
253
|
+
```css
|
|
254
|
+
:root {
|
|
255
|
+
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Transitions
|
|
260
|
+
|
|
261
|
+
| Token | Default | Purpose |
|
|
262
|
+
| --------------------- | ------- | ---------------- |
|
|
263
|
+
| `--transition-fast` | `150ms` | Hover states |
|
|
264
|
+
| `--transition-normal` | `250ms` | Most animations |
|
|
265
|
+
| `--transition-slow` | `350ms` | Page transitions |
|
|
266
|
+
|
|
267
|
+
```css
|
|
268
|
+
:root {
|
|
269
|
+
/* Snappier animations */
|
|
270
|
+
--transition-fast: 100ms;
|
|
271
|
+
--transition-normal: 180ms;
|
|
272
|
+
|
|
273
|
+
/* Or disable motion for accessibility */
|
|
274
|
+
--transition-fast: 0ms;
|
|
275
|
+
--transition-normal: 0ms;
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Complete theme example
|
|
280
|
+
|
|
281
|
+
```css
|
|
282
|
+
@import "tailwindcss";
|
|
283
|
+
@source "../node_modules/yems-ui/dist";
|
|
284
|
+
@import "yems-ui/styles.css";
|
|
285
|
+
|
|
286
|
+
/* A blue/teal theme with softer radius and minimal glass */
|
|
287
|
+
:root {
|
|
288
|
+
/* Brand */
|
|
289
|
+
--brand-500: #0ea5e9;
|
|
290
|
+
--brand-900: #0c4a6e;
|
|
291
|
+
--accent-500: #f59e0b;
|
|
292
|
+
--accent-700: #d97706;
|
|
293
|
+
|
|
294
|
+
/* Shape */
|
|
295
|
+
--radius: 8px;
|
|
296
|
+
|
|
297
|
+
/* Glass — subtle */
|
|
298
|
+
--glass-blur: 8px;
|
|
299
|
+
--glass-bg: rgba(255, 255, 255, 0.8);
|
|
300
|
+
--glass-card-bg: rgba(255, 255, 255, 0.9);
|
|
301
|
+
|
|
302
|
+
/* Shadows — flatter */
|
|
303
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
304
|
+
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
305
|
+
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Per-component override via className
|
|
310
|
+
|
|
311
|
+
Every component accepts a `className` prop for one-off overrides without touching theme variables:
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
<Button variant="primary" className="rounded-full px-8">
|
|
315
|
+
Pill Button
|
|
316
|
+
</Button>
|
|
317
|
+
|
|
318
|
+
<Card className="border-2 border-primary/30 shadow-xl">
|
|
319
|
+
Custom Card
|
|
320
|
+
</Card>
|
|
321
|
+
|
|
322
|
+
<Input className="h-14 text-lg" placeholder="Large input" />
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Components
|
|
328
|
+
|
|
329
|
+
### Button
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
import { Button } from "yems-ui";
|
|
333
|
+
|
|
334
|
+
// Variants
|
|
335
|
+
<Button variant="primary">Primary</Button>
|
|
336
|
+
<Button variant="secondary">Secondary</Button>
|
|
337
|
+
<Button variant="accent">Accent</Button>
|
|
338
|
+
<Button variant="ember">Ember</Button>
|
|
339
|
+
<Button variant="destructive">Delete</Button>
|
|
340
|
+
<Button variant="ghost">Ghost</Button>
|
|
341
|
+
<Button variant="outline">Outline</Button>
|
|
342
|
+
<Button variant="link">Link</Button>
|
|
343
|
+
|
|
344
|
+
// Outline variants
|
|
345
|
+
<Button variant="outline-primary">Outline Primary</Button>
|
|
346
|
+
<Button variant="outline-secondary">Outline Secondary</Button>
|
|
347
|
+
<Button variant="outline-accent">Outline Accent</Button>
|
|
348
|
+
<Button variant="outline-ember">Outline Ember</Button>
|
|
349
|
+
<Button variant="outline-destructive">Outline Destructive</Button>
|
|
350
|
+
|
|
351
|
+
// Ghost variants
|
|
352
|
+
<Button variant="ghost-primary">Ghost Primary</Button>
|
|
353
|
+
<Button variant="ghost-secondary">Ghost Secondary</Button>
|
|
354
|
+
|
|
355
|
+
// Sizes
|
|
356
|
+
<Button size="sm">Small</Button>
|
|
357
|
+
<Button size="default">Default</Button>
|
|
358
|
+
<Button size="lg">Large</Button>
|
|
359
|
+
<Button size="xl">Extra Large</Button>
|
|
360
|
+
<Button size="icon"><SearchIcon /></Button>
|
|
361
|
+
|
|
362
|
+
// With icons
|
|
363
|
+
<Button leftIcon={<PlusIcon />}>Add Item</Button>
|
|
364
|
+
<Button rightIcon={<ArrowRight />}>Continue</Button>
|
|
365
|
+
|
|
366
|
+
// Loading state
|
|
367
|
+
<Button isLoading>Saving...</Button>
|
|
368
|
+
|
|
369
|
+
// As a link (renders as child element)
|
|
370
|
+
<Button asChild>
|
|
371
|
+
<a href="/dashboard">Dashboard</a>
|
|
372
|
+
</Button>
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Props:**
|
|
376
|
+
|
|
377
|
+
| Prop | Type | Default | Description |
|
|
378
|
+
| ----------- | --------------------------------------------------------- | --------- | ------------------------------------- |
|
|
379
|
+
| `variant` | see above | `primary` | Visual style |
|
|
380
|
+
| `size` | `sm \| default \| lg \| xl \| icon \| icon-sm \| icon-lg` | `default` | Size |
|
|
381
|
+
| `isLoading` | `boolean` | `false` | Shows spinner, disables interaction |
|
|
382
|
+
| `leftIcon` | `ReactNode` | — | Icon before label |
|
|
383
|
+
| `rightIcon` | `ReactNode` | — | Icon after label |
|
|
384
|
+
| `asChild` | `boolean` | `false` | Renders as child element (e.g. `<a>`) |
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
### Card
|
|
389
|
+
|
|
390
|
+
```tsx
|
|
391
|
+
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, StatCard } from "yems-ui";
|
|
392
|
+
|
|
393
|
+
<Card>
|
|
394
|
+
<CardHeader>
|
|
395
|
+
<CardTitle>Card Title</CardTitle>
|
|
396
|
+
<CardDescription>A short description.</CardDescription>
|
|
397
|
+
</CardHeader>
|
|
398
|
+
<CardContent>
|
|
399
|
+
Content goes here.
|
|
400
|
+
</CardContent>
|
|
401
|
+
<CardFooter>
|
|
402
|
+
<Button variant="outline-primary">Action</Button>
|
|
403
|
+
</CardFooter>
|
|
404
|
+
</Card>
|
|
405
|
+
|
|
406
|
+
// With hover animation
|
|
407
|
+
<Card hover>
|
|
408
|
+
<CardContent>Lifts on hover</CardContent>
|
|
409
|
+
</Card>
|
|
410
|
+
|
|
411
|
+
// Stats card
|
|
412
|
+
<StatCard
|
|
413
|
+
title="Total Revenue"
|
|
414
|
+
value="$12,340"
|
|
415
|
+
icon={<DollarSign />}
|
|
416
|
+
trend={{ value: 12.5, isPositive: true }}
|
|
417
|
+
description="vs last month"
|
|
418
|
+
/>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**StatCard props:**
|
|
119
422
|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
| **Switch** | Glassmorphism toggle |
|
|
128
|
-
| **Checkbox** | Animated checkbox |
|
|
129
|
-
| **Alert** | 5 variants (default, info, success, warning, error) |
|
|
130
|
-
| **Accordion** | Collapsible content sections |
|
|
131
|
-
| **Popover** | Contextual overlays |
|
|
132
|
-
| **Tabs** | Tabbed content with glass effect |
|
|
133
|
-
| **Select** | Dropdown select with glass styling |
|
|
134
|
-
| **Tooltip** | Hover tooltips |
|
|
135
|
-
| **Dropdown Menu** | Context menus |
|
|
136
|
-
| **Table** | Data tables with hover effects |
|
|
137
|
-
| **Badge** | Status badges and indicators |
|
|
138
|
-
| **Avatar** | User avatars with fallback |
|
|
139
|
-
| **Progress** | Progress bars |
|
|
140
|
-
| **Separator** | Dividers |
|
|
141
|
-
| **Pagination** | Smart page navigation |
|
|
142
|
-
| **Breadcrumbs** | Navigation hierarchy |
|
|
143
|
-
| **Skeleton** | Loading states (text, card, avatar, table) |
|
|
144
|
-
| **Empty State** | No data displays |
|
|
145
|
-
| **Toast** | Notification toasts |
|
|
423
|
+
| Prop | Type | Description |
|
|
424
|
+
| ------------- | ---------------------------------------- | --------------------------- |
|
|
425
|
+
| `title` | `string` | Label above the value |
|
|
426
|
+
| `value` | `string \| number` | The main figure |
|
|
427
|
+
| `icon` | `ReactNode` | Icon shown top-right |
|
|
428
|
+
| `trend` | `{ value: number, isPositive: boolean }` | Percentage change indicator |
|
|
429
|
+
| `description` | `string` | Sub-label below the value |
|
|
146
430
|
|
|
147
431
|
---
|
|
148
432
|
|
|
149
|
-
|
|
433
|
+
### Input
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
import { Input, Label, FormField, Textarea } from "yems-ui";
|
|
437
|
+
import { Search } from "lucide-react";
|
|
438
|
+
|
|
439
|
+
// Variants
|
|
440
|
+
<Input placeholder="Default" />
|
|
441
|
+
<Input placeholder="Filled" variant="filled" />
|
|
442
|
+
<Input placeholder="Ghost" variant="ghost" />
|
|
443
|
+
|
|
444
|
+
// Sizes
|
|
445
|
+
<Input placeholder="Small" inputSize="sm" />
|
|
446
|
+
<Input placeholder="Large" inputSize="lg" />
|
|
447
|
+
|
|
448
|
+
// With icons and addons
|
|
449
|
+
<Input leftIcon={<Search className="h-4 w-4" />} placeholder="Search..." />
|
|
450
|
+
<Input leftAddon="https://" placeholder="yoursite.com" />
|
|
451
|
+
<Input rightAddon=".com" placeholder="domain" />
|
|
452
|
+
|
|
453
|
+
// Validation states
|
|
454
|
+
<Input state="error" error="This field is required" />
|
|
455
|
+
<Input state="success" hint="Looks good!" />
|
|
456
|
+
|
|
457
|
+
// Label + FormField (handles layout, label, error, hint together)
|
|
458
|
+
<FormField label="Email" htmlFor="email" required error="Invalid email">
|
|
459
|
+
<Input id="email" type="email" />
|
|
460
|
+
</FormField>
|
|
461
|
+
|
|
462
|
+
// Textarea
|
|
463
|
+
<Textarea placeholder="Write something..." rows={4} />
|
|
464
|
+
<Textarea variant="filled" state="error" error="Required" />
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
### Alert
|
|
470
|
+
|
|
471
|
+
```tsx
|
|
472
|
+
import { Alert, AlertTitle, AlertDescription } from "yems-ui";
|
|
473
|
+
import { InfoIcon } from "lucide-react";
|
|
474
|
+
|
|
475
|
+
<Alert variant="info">
|
|
476
|
+
<InfoIcon className="h-4 w-4" />
|
|
477
|
+
<AlertTitle>Heads up</AlertTitle>
|
|
478
|
+
<AlertDescription>Something you should know.</AlertDescription>
|
|
479
|
+
</Alert>
|
|
480
|
+
|
|
481
|
+
// Variants: default | info | success | warning | error
|
|
482
|
+
<Alert variant="success">...</Alert>
|
|
483
|
+
<Alert variant="warning">...</Alert>
|
|
484
|
+
<Alert variant="error">...</Alert>
|
|
485
|
+
|
|
486
|
+
// Dismissible
|
|
487
|
+
<Alert variant="info" dismissible onDismiss={() => console.log("dismissed")}>
|
|
488
|
+
<AlertTitle>Dismissible</AlertTitle>
|
|
489
|
+
</Alert>
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
### Badge
|
|
495
|
+
|
|
496
|
+
```tsx
|
|
497
|
+
import { Badge, StatusBadge } from "yems-ui";
|
|
498
|
+
|
|
499
|
+
// Filled
|
|
500
|
+
<Badge variant="primary">Primary</Badge>
|
|
501
|
+
<Badge variant="success">Active</Badge>
|
|
502
|
+
<Badge variant="error">Failed</Badge>
|
|
503
|
+
|
|
504
|
+
// Soft (light background, colored text)
|
|
505
|
+
<Badge variant="soft-success">Paid</Badge>
|
|
506
|
+
<Badge variant="soft-warning">Pending</Badge>
|
|
507
|
+
<Badge variant="soft-error">Unpaid</Badge>
|
|
508
|
+
|
|
509
|
+
// Outline
|
|
510
|
+
<Badge variant="outline-primary">Draft</Badge>
|
|
511
|
+
|
|
512
|
+
// With dot indicator
|
|
513
|
+
<Badge variant="soft-success" dot>Online</Badge>
|
|
514
|
+
|
|
515
|
+
// Sizes
|
|
516
|
+
<Badge size="sm">Small</Badge>
|
|
517
|
+
<Badge size="lg">Large</Badge>
|
|
518
|
+
|
|
519
|
+
// Preset status badge
|
|
520
|
+
<StatusBadge status="active" />
|
|
521
|
+
<StatusBadge status="pending" />
|
|
522
|
+
<StatusBadge status="inactive" />
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
### Dialog
|
|
528
|
+
|
|
529
|
+
```tsx
|
|
530
|
+
import {
|
|
531
|
+
Dialog,
|
|
532
|
+
DialogTrigger,
|
|
533
|
+
DialogContent,
|
|
534
|
+
DialogHeader,
|
|
535
|
+
DialogTitle,
|
|
536
|
+
DialogDescription,
|
|
537
|
+
DialogFooter,
|
|
538
|
+
} from "yems-ui";
|
|
539
|
+
|
|
540
|
+
<Dialog>
|
|
541
|
+
<DialogTrigger asChild>
|
|
542
|
+
<Button>Open Dialog</Button>
|
|
543
|
+
</DialogTrigger>
|
|
544
|
+
<DialogContent>
|
|
545
|
+
<DialogHeader>
|
|
546
|
+
<DialogTitle>Edit Profile</DialogTitle>
|
|
547
|
+
<DialogDescription>Make changes to your profile here.</DialogDescription>
|
|
548
|
+
</DialogHeader>
|
|
549
|
+
<div className="py-4">
|
|
550
|
+
<Input placeholder="Name" />
|
|
551
|
+
</div>
|
|
552
|
+
<DialogFooter>
|
|
553
|
+
<Button variant="primary">Save</Button>
|
|
554
|
+
</DialogFooter>
|
|
555
|
+
</DialogContent>
|
|
556
|
+
</Dialog>;
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
### Select
|
|
562
|
+
|
|
563
|
+
```tsx
|
|
564
|
+
import {
|
|
565
|
+
Select,
|
|
566
|
+
SelectTrigger,
|
|
567
|
+
SelectValue,
|
|
568
|
+
SelectContent,
|
|
569
|
+
SelectItem,
|
|
570
|
+
} from "yems-ui";
|
|
571
|
+
|
|
572
|
+
const [value, setValue] = useState("");
|
|
573
|
+
|
|
574
|
+
<Select value={value} onValueChange={setValue}>
|
|
575
|
+
<SelectTrigger className="w-48">
|
|
576
|
+
<SelectValue placeholder="Choose one" />
|
|
577
|
+
</SelectTrigger>
|
|
578
|
+
<SelectContent>
|
|
579
|
+
<SelectItem value="option1">Option 1</SelectItem>
|
|
580
|
+
<SelectItem value="option2">Option 2</SelectItem>
|
|
581
|
+
<SelectItem value="option3">Option 3</SelectItem>
|
|
582
|
+
</SelectContent>
|
|
583
|
+
</Select>;
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
### Tabs
|
|
589
|
+
|
|
590
|
+
```tsx
|
|
591
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "yems-ui";
|
|
592
|
+
|
|
593
|
+
<Tabs defaultValue="overview">
|
|
594
|
+
<TabsList>
|
|
595
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
596
|
+
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
|
597
|
+
<TabsTrigger value="settings">Settings</TabsTrigger>
|
|
598
|
+
</TabsList>
|
|
599
|
+
<TabsContent value="overview">Overview content</TabsContent>
|
|
600
|
+
<TabsContent value="analytics">Analytics content</TabsContent>
|
|
601
|
+
<TabsContent value="settings">Settings content</TabsContent>
|
|
602
|
+
</Tabs>;
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
### Toast
|
|
608
|
+
|
|
609
|
+
```tsx
|
|
610
|
+
import { Toaster, useToast } from "yems-ui";
|
|
611
|
+
|
|
612
|
+
// Add <Toaster /> once near the root of your app
|
|
613
|
+
function App() {
|
|
614
|
+
return (
|
|
615
|
+
<>
|
|
616
|
+
<YourApp />
|
|
617
|
+
<Toaster />
|
|
618
|
+
</>
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Use the hook anywhere
|
|
623
|
+
function MyComponent() {
|
|
624
|
+
const { toast } = useToast();
|
|
625
|
+
|
|
626
|
+
return (
|
|
627
|
+
<Button
|
|
628
|
+
onClick={() =>
|
|
629
|
+
toast({ title: "Saved!", description: "Your changes have been saved." })
|
|
630
|
+
}
|
|
631
|
+
>
|
|
632
|
+
Save
|
|
633
|
+
</Button>
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Destructive variant
|
|
638
|
+
toast({
|
|
639
|
+
variant: "destructive",
|
|
640
|
+
title: "Error",
|
|
641
|
+
description: "Something went wrong.",
|
|
642
|
+
});
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
### Skeleton
|
|
648
|
+
|
|
649
|
+
```tsx
|
|
650
|
+
import { Skeleton, SkeletonCard, SkeletonText, SkeletonAvatar, SkeletonTable } from "yems-ui";
|
|
651
|
+
|
|
652
|
+
// Basic
|
|
653
|
+
<Skeleton className="h-4 w-48" />
|
|
654
|
+
<Skeleton variant="circular" className="h-10 w-10" />
|
|
655
|
+
|
|
656
|
+
// Animation variants
|
|
657
|
+
<Skeleton animation="pulse" className="h-4 w-full" /> // default
|
|
658
|
+
<Skeleton animation="wave" className="h-4 w-full" />
|
|
659
|
+
<Skeleton animation="none" className="h-4 w-full" />
|
|
660
|
+
|
|
661
|
+
// Preset layouts
|
|
662
|
+
<SkeletonCard />
|
|
663
|
+
<SkeletonText lines={4} />
|
|
664
|
+
<SkeletonAvatar size="lg" />
|
|
665
|
+
<SkeletonTable rows={5} columns={4} />
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
### Other components
|
|
671
|
+
|
|
672
|
+
| Component | Import | Notes |
|
|
673
|
+
| -------------- | ----------------------------------------------------------------------------- | -------------------------------------------------- |
|
|
674
|
+
| `Accordion` | `AccordionItem, AccordionTrigger, AccordionContent` | Radix-based, animated |
|
|
675
|
+
| `Avatar` | `Avatar, AvatarImage, AvatarFallback` | With image fallback |
|
|
676
|
+
| `Breadcrumbs` | `Breadcrumbs` | Accepts `items: { label, href? }[]` |
|
|
677
|
+
| `Checkbox` | `Checkbox` | Controlled via `checked` + `onCheckedChange` |
|
|
678
|
+
| `DropdownMenu` | `DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem` | Radix-based |
|
|
679
|
+
| `EmptyState` | `EmptyState` | Props: `title`, `description`, `icon`, `action` |
|
|
680
|
+
| `Pagination` | `Pagination` | Props: `currentPage`, `totalPages`, `onPageChange` |
|
|
681
|
+
| `Popover` | `Popover, PopoverTrigger, PopoverContent` | Radix-based |
|
|
682
|
+
| `Progress` | `Progress` | Accepts `value` (0–100) |
|
|
683
|
+
| `RadioGroup` | `RadioGroup, RadioGroupItem` | Controlled via `value` + `onValueChange` |
|
|
684
|
+
| `Separator` | `Separator` | Horizontal or vertical divider |
|
|
685
|
+
| `Switch` | `Switch` | Controlled via `checked` + `onCheckedChange` |
|
|
686
|
+
| `Table` | `Table, TableHeader, TableBody, TableRow, TableHead, TableCell, TableCaption` | Standard table layout |
|
|
687
|
+
| `Tooltip` | `TooltipProvider, Tooltip, TooltipTrigger, TooltipContent` | Wrap app in `TooltipProvider` |
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
## Dark Mode
|
|
692
|
+
|
|
693
|
+
yems-ui supports dark mode via a `.dark` class on the `<html>` element. Add or remove it to switch themes:
|
|
694
|
+
|
|
695
|
+
```tsx
|
|
696
|
+
// Toggle dark mode
|
|
697
|
+
document.documentElement.classList.toggle("dark");
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
A simple React hook to manage this:
|
|
701
|
+
|
|
702
|
+
```tsx
|
|
703
|
+
function useDarkMode() {
|
|
704
|
+
const [isDark, setIsDark] = useState(() =>
|
|
705
|
+
document.documentElement.classList.contains("dark"),
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
const toggle = () => {
|
|
709
|
+
document.documentElement.classList.toggle("dark");
|
|
710
|
+
setIsDark((prev) => !prev);
|
|
711
|
+
localStorage.setItem("theme", isDark ? "light" : "dark");
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
// Restore on mount
|
|
715
|
+
useEffect(() => {
|
|
716
|
+
const saved = localStorage.getItem("theme");
|
|
717
|
+
const prefersDark = window.matchMedia(
|
|
718
|
+
"(prefers-color-scheme: dark)",
|
|
719
|
+
).matches;
|
|
720
|
+
if (saved === "dark" || (!saved && prefersDark)) {
|
|
721
|
+
document.documentElement.classList.add("dark");
|
|
722
|
+
setIsDark(true);
|
|
723
|
+
}
|
|
724
|
+
}, []);
|
|
725
|
+
|
|
726
|
+
return { isDark, toggle };
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Usage
|
|
730
|
+
function Header() {
|
|
731
|
+
const { isDark, toggle } = useDarkMode();
|
|
732
|
+
return (
|
|
733
|
+
<Button variant="ghost" size="icon" onClick={toggle}>
|
|
734
|
+
{isDark ? <Sun /> : <Moon />}
|
|
735
|
+
</Button>
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
Dark mode overrides the glass variables and all semantic tokens automatically — no extra setup needed.
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
## TypeScript
|
|
745
|
+
|
|
746
|
+
All components are fully typed. Props interfaces are exported:
|
|
747
|
+
|
|
748
|
+
```tsx
|
|
749
|
+
import type {
|
|
750
|
+
ButtonProps,
|
|
751
|
+
CardProps,
|
|
752
|
+
InputProps,
|
|
753
|
+
BadgeProps,
|
|
754
|
+
StatCardProps,
|
|
755
|
+
EmptyStateProps,
|
|
756
|
+
PaginationProps,
|
|
757
|
+
SkeletonProps,
|
|
758
|
+
} from "yems-ui";
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
---
|
|
150
762
|
|
|
151
|
-
|
|
763
|
+
## Framework notes
|
|
152
764
|
|
|
153
|
-
|
|
|
154
|
-
| ------------------------ |
|
|
155
|
-
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
| `--color-seasalt` | Light | `#fafafa` |
|
|
765
|
+
| Framework | Notes |
|
|
766
|
+
| ------------------------ | --------------------------------------------------------------------------------- |
|
|
767
|
+
| **Vite** | Full support. See setup above. |
|
|
768
|
+
| **Next.js App Router** | Add `"use client"` to files using hooks or event handlers. CSS setup is the same. |
|
|
769
|
+
| **Next.js Pages Router** | Import styles in `_app.tsx`. Works without `"use client"`. |
|
|
770
|
+
| **Remix** | Import styles in `root.tsx` links export. |
|
|
771
|
+
| **Astro** | Use inside `.tsx` components with `client:load` or `client:visible`. |
|
|
161
772
|
|
|
162
773
|
---
|
|
163
774
|
|
|
164
|
-
##
|
|
775
|
+
## License
|
|
165
776
|
|
|
166
|
-
MIT © Sodiq Ogundairo
|
|
777
|
+
MIT © [Sodiq Ogundairo](https://github.com/SodiqOgundairo)
|