@mrintel/villain-ui 0.3.0 → 0.7.1
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/LICENSE +21 -21
- package/README.md +3490 -1296
- package/dist/components/buttons/Button.svelte +27 -33
- package/dist/components/buttons/Button.svelte.d.ts +4 -1
- package/dist/components/buttons/ButtonGroup.svelte +17 -30
- package/dist/components/buttons/FloatingActionButton.svelte +20 -44
- package/dist/components/buttons/FloatingActionButton.svelte.d.ts +2 -1
- package/dist/components/buttons/IconButton.svelte +23 -53
- package/dist/components/buttons/IconButton.svelte.d.ts +2 -1
- package/dist/components/buttons/LinkButton.svelte +24 -37
- package/dist/components/buttons/LinkButton.svelte.d.ts +4 -1
- package/dist/components/buttons/buttonClasses.d.ts +5 -0
- package/dist/components/buttons/buttonClasses.js +8 -3
- package/dist/components/cards/Card.svelte +54 -46
- package/dist/components/cards/Card.svelte.d.ts +9 -2
- package/dist/components/cards/Container.svelte +17 -33
- package/dist/components/cards/Divider.svelte +36 -52
- package/dist/components/cards/Divider.svelte.d.ts +2 -0
- package/dist/components/cards/Grid.svelte +55 -44
- package/dist/components/cards/Panel.svelte +18 -32
- package/dist/components/cards/Panel.svelte.d.ts +2 -1
- package/dist/components/cards/SectionHeader.svelte +24 -38
- package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
- package/dist/components/data/Avatar.svelte +48 -67
- package/dist/components/data/Badge.svelte +45 -32
- package/dist/components/data/Badge.svelte.d.ts +7 -1
- package/dist/components/data/CalendarGrid.svelte +433 -0
- package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
- package/dist/components/data/CalendarGrid.types.d.ts +7 -0
- package/dist/components/data/CalendarGrid.types.js +1 -0
- package/dist/components/data/CodeBlock.svelte +119 -121
- package/dist/components/data/CodeBlock.svelte.d.ts +8 -0
- package/dist/components/data/List.svelte +87 -64
- package/dist/components/data/List.svelte.d.ts +7 -0
- package/dist/components/data/Pagination.svelte +121 -123
- package/dist/components/data/Pagination.svelte.d.ts +5 -0
- package/dist/components/data/Sparkline.svelte +117 -0
- package/dist/components/data/Sparkline.svelte.d.ts +43 -0
- package/dist/components/data/Stat.svelte +92 -103
- package/dist/components/data/Table.svelte +443 -76
- package/dist/components/data/Table.svelte.d.ts +23 -2
- package/dist/components/data/Table.types.d.ts +14 -0
- package/dist/components/data/Table.types.js +1 -0
- package/dist/components/data/Tag.svelte +51 -53
- package/dist/components/data/Tag.svelte.d.ts +5 -1
- package/dist/components/data/index.d.ts +4 -0
- package/dist/components/data/index.js +2 -0
- package/dist/components/forms/Checkbox.svelte +39 -51
- package/dist/components/forms/Checkbox.svelte.d.ts +3 -1
- package/dist/components/forms/DatePicker.svelte +61 -0
- package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
- package/dist/components/forms/DateTimePicker.svelte +63 -0
- package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
- package/dist/components/forms/FileUpload.svelte +136 -164
- package/dist/components/forms/FileUpload.svelte.d.ts +1 -0
- package/dist/components/forms/Input.svelte +284 -57
- package/dist/components/forms/Input.svelte.d.ts +10 -3
- package/dist/components/forms/InputGroup.svelte +7 -7
- package/dist/components/forms/RadioGroup.svelte +77 -87
- package/dist/components/forms/RadioGroup.svelte.d.ts +3 -1
- package/dist/components/forms/RangeSlider.svelte +90 -116
- package/dist/components/forms/Select.svelte +106 -71
- package/dist/components/forms/Select.svelte.d.ts +3 -1
- package/dist/components/forms/Step.svelte +25 -0
- package/dist/components/forms/Step.svelte.d.ts +12 -0
- package/dist/components/forms/StepContext.d.ts +3 -0
- package/dist/components/forms/StepContext.js +5 -0
- package/dist/components/forms/Stepper.types.d.ts +37 -0
- package/dist/components/forms/Stepper.types.js +1 -0
- package/dist/components/forms/StepperForm.svelte +183 -0
- package/dist/components/forms/StepperForm.svelte.d.ts +17 -0
- package/dist/components/forms/Switch.svelte +44 -56
- package/dist/components/forms/Switch.svelte.d.ts +3 -1
- package/dist/components/forms/Textarea.svelte +52 -57
- package/dist/components/forms/Textarea.svelte.d.ts +3 -1
- package/dist/components/forms/TimePicker.svelte +63 -0
- package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
- package/dist/components/forms/formClasses.d.ts +3 -0
- package/dist/components/forms/formClasses.js +3 -0
- package/dist/components/forms/index.d.ts +6 -0
- package/dist/components/forms/index.js +5 -0
- package/dist/components/navigation/Breadcrumbs.svelte +56 -59
- package/dist/components/navigation/Breadcrumbs.svelte.d.ts +1 -0
- package/dist/components/navigation/ContextMenu.svelte +133 -83
- package/dist/components/navigation/ContextMenu.svelte.d.ts +8 -1
- package/dist/components/navigation/DropdownMenu.svelte +139 -80
- package/dist/components/navigation/DropdownMenu.svelte.d.ts +8 -1
- package/dist/components/navigation/Menu.svelte +72 -48
- package/dist/components/navigation/Navbar.svelte +111 -32
- package/dist/components/navigation/Navbar.svelte.d.ts +6 -0
- package/dist/components/navigation/Sidebar.svelte +236 -35
- package/dist/components/navigation/Sidebar.svelte.d.ts +2 -0
- package/dist/components/navigation/Stepper.svelte +204 -0
- package/dist/components/navigation/Stepper.svelte.d.ts +34 -0
- package/dist/components/navigation/Tabs.svelte +86 -54
- package/dist/components/navigation/Tabs.svelte.d.ts +5 -1
- package/dist/components/navigation/index.d.ts +1 -0
- package/dist/components/navigation/index.js +1 -0
- package/dist/components/overlays/Alert.svelte +81 -99
- package/dist/components/overlays/Alert.svelte.d.ts +5 -1
- package/dist/components/overlays/CommandPalette.svelte +182 -217
- package/dist/components/overlays/Drawer.svelte +158 -167
- package/dist/components/overlays/Drawer.svelte.d.ts +3 -1
- package/dist/components/overlays/Dropdown.svelte +62 -30
- package/dist/components/overlays/Dropdown.svelte.d.ts +2 -0
- package/dist/components/overlays/Modal.svelte +125 -130
- package/dist/components/overlays/Modal.svelte.d.ts +3 -1
- package/dist/components/overlays/Popover.svelte +106 -131
- package/dist/components/overlays/ProgressBar.svelte +29 -45
- package/dist/components/overlays/SkeletonLoader.svelte +66 -82
- package/dist/components/overlays/Spinner.svelte +33 -43
- package/dist/components/overlays/Toast.svelte +111 -140
- package/dist/components/overlays/Toast.svelte.d.ts +3 -0
- package/dist/components/overlays/Tooltip.svelte +94 -115
- package/dist/components/overlays/Tooltip.svelte.d.ts +3 -1
- package/dist/components/typography/Code.svelte +10 -14
- package/dist/components/typography/Heading.svelte +15 -22
- package/dist/components/typography/Heading.svelte.d.ts +1 -0
- package/dist/components/typography/Text.svelte +21 -24
- package/dist/components/typography/Text.svelte.d.ts +2 -1
- package/dist/components/utilities/Accordion.svelte +54 -67
- package/dist/components/utilities/Accordion.svelte.d.ts +4 -1
- package/dist/components/utilities/Carousel.svelte +124 -152
- package/dist/components/utilities/Collapse.svelte +46 -60
- package/dist/components/utilities/Hero.svelte +42 -0
- package/dist/components/utilities/Hero.svelte.d.ts +10 -0
- package/dist/components/utilities/Portal.svelte +47 -72
- package/dist/components/utilities/ScrollArea.svelte +33 -41
- package/dist/components/utilities/SystemConsole.svelte +310 -0
- package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
- package/dist/components/utilities/SystemInterface.svelte +726 -0
- package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
- package/dist/components/utilities/index.d.ts +4 -0
- package/dist/components/utilities/index.js +3 -0
- package/dist/components/utilities/utilities.types.d.ts +46 -0
- package/dist/components/utilities/utilities.types.js +4 -0
- package/dist/index.d.ts +57 -5
- package/dist/index.js +5 -5
- package/dist/theme.css +2889 -218
- package/package.json +83 -76
package/README.md
CHANGED
|
@@ -1,1296 +1,3490 @@
|
|
|
1
|
-
# @mrintel/villain-ui
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@mrintel/villain-ui)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
|
|
6
|
-
A luxury Svelte 5 component library
|
|
7
|
-
|
|
8
|
-
## ✨ Features
|
|
9
|
-
|
|
10
|
-
- **🚀 Svelte 5 with Runes** - Built on the latest Svelte 5 reactivity system with full TypeScript support
|
|
11
|
-
- **🎨
|
|
12
|
-
- **🎭 Tailwind CSS v4** - Powered by the latest Tailwind with CSS variable theming
|
|
13
|
-
- **🌳 Tree-Shakeable** - Import only what you need for optimal bundle size
|
|
14
|
-
- **🎯 Fully Typed** - Strict TypeScript mode with complete type definitions
|
|
15
|
-
- **🎬 Premium Motion** - Smooth animations with custom luxury easing curves
|
|
16
|
-
- **🔧 Highly Customizable** - Theme via CSS variables without touching component code
|
|
17
|
-
- **♿ Accessible** - ARIA-compliant components following WAI-ARIA best practices
|
|
18
|
-
|
|
19
|
-
## 📦 Installation
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
# npm
|
|
23
|
-
npm install @mrintel/villain-ui
|
|
24
|
-
|
|
25
|
-
# pnpm
|
|
26
|
-
pnpm add @mrintel/villain-ui
|
|
27
|
-
|
|
28
|
-
# yarn
|
|
29
|
-
yarn add @mrintel/villain-ui
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
> **Note on Imports:** All components are exported from the root package (`@mrintel/villain-ui`). Category-specific subpath imports (e.g., `@mrintel/villain-ui/buttons`) are not provided, as the library is designed for tree-shaking at the component level. Your bundler will automatically include only the components you import.
|
|
33
|
-
|
|
34
|
-
### Peer Dependencies
|
|
35
|
-
|
|
36
|
-
Install the required peer dependency:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npm install svelte@^5.0.0
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**Note on Tailwind CSS:** The library uses Tailwind CSS v4 internally for styling, but it is **not required** as a peer dependency. The compiled theme CSS is included in the package. You only need to install Tailwind CSS in your project if you want to extend the library's theme with custom Tailwind utilities or use Tailwind for your own application styling.
|
|
43
|
-
|
|
44
|
-
### Import Theme
|
|
45
|
-
|
|
46
|
-
**Important:** The theme CSS is **not** automatically imported when you use components. You must explicitly import it in your app's entry point to apply the default styles.
|
|
47
|
-
|
|
48
|
-
Import the theme CSS in your app's entry point (e.g., `+layout.svelte` in SvelteKit or `main.ts` in Vite):
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
import '@mrintel/villain-ui/theme.css';
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
This explicit import strategy gives you full control over styling and allows you to:
|
|
55
|
-
- Use a custom theme instead of the default
|
|
56
|
-
- Conditionally load themes
|
|
57
|
-
- Override theme variables before or after the default theme loads
|
|
58
|
-
|
|
59
|
-
## 🚀 Quick Start
|
|
60
|
-
|
|
61
|
-
### SvelteKit Setup
|
|
62
|
-
|
|
63
|
-
```svelte
|
|
64
|
-
<!-- src/routes/+layout.svelte -->
|
|
65
|
-
<script>
|
|
66
|
-
import '@mrintel/villain-ui/theme.css';
|
|
67
|
-
</script>
|
|
68
|
-
|
|
69
|
-
<slot />
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### Basic Usage
|
|
73
|
-
|
|
74
|
-
```svelte
|
|
75
|
-
<script>
|
|
76
|
-
import { Button, Card } from '@mrintel/villain-ui';
|
|
77
|
-
</script>
|
|
78
|
-
|
|
79
|
-
<Card padding="lg">
|
|
80
|
-
<h1>Welcome to @mrintel/villain-ui</h1>
|
|
81
|
-
<p>Build luxury interfaces with ease.</p>
|
|
82
|
-
<Button variant="primary">Get Started</Button>
|
|
83
|
-
</Card>
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
##
|
|
87
|
-
|
|
88
|
-
###
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
**
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
**
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
**
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
</script>
|
|
255
|
-
|
|
256
|
-
<
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
<
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
<
|
|
324
|
-
|
|
325
|
-
</
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
<
|
|
354
|
-
|
|
355
|
-
</
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
{/
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
{
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
</script>
|
|
461
|
-
|
|
462
|
-
<
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
<
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
```svelte
|
|
580
|
-
<script>
|
|
581
|
-
import {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
<
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
</
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
{
|
|
643
|
-
|
|
644
|
-
{
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
**
|
|
651
|
-
|
|
652
|
-
```svelte
|
|
653
|
-
<script>
|
|
654
|
-
import {
|
|
655
|
-
</script>
|
|
656
|
-
|
|
657
|
-
<
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
<
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
</
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
</
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
<
|
|
838
|
-
|
|
839
|
-
</
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
</
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
<
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
```
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
<
|
|
948
|
-
|
|
949
|
-
</
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
<
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
**
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
</
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
const
|
|
1020
|
-
{
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
```
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
```
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
:
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
```
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
**
|
|
1127
|
-
-
|
|
1128
|
-
-
|
|
1129
|
-
-
|
|
1130
|
-
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
-
|
|
1135
|
-
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
-
|
|
1186
|
-
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
-
|
|
1193
|
-
-
|
|
1194
|
-
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
-
|
|
1256
|
-
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1
|
+
# @mrintel/villain-ui
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@mrintel/villain-ui)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A luxury Svelte 5 component library featuring a **Modern Villain Luxury** aesthetic. Built on Onyx Black backgrounds with Royal Purple accents, glass morphism, and neon edges for modern web applications that demand commanding elegance and exceptional user experience.
|
|
7
|
+
|
|
8
|
+
## ✨ Features
|
|
9
|
+
|
|
10
|
+
- **🚀 Svelte 5 with Runes** - Built on the latest Svelte 5 reactivity system with full TypeScript support
|
|
11
|
+
- **🎨 Modern Villain Luxury** - Onyx Black base with Royal Purple accents, glass morphism, and commanding neon edges
|
|
12
|
+
- **🎭 Tailwind CSS v4** - Powered by the latest Tailwind with CSS variable theming
|
|
13
|
+
- **🌳 Tree-Shakeable** - Import only what you need for optimal bundle size
|
|
14
|
+
- **🎯 Fully Typed** - Strict TypeScript mode with complete type definitions
|
|
15
|
+
- **🎬 Premium Motion** - Smooth animations with custom luxury easing curves
|
|
16
|
+
- **🔧 Highly Customizable** - Theme via CSS variables without touching component code
|
|
17
|
+
- **♿ Accessible** - ARIA-compliant components following WAI-ARIA best practices
|
|
18
|
+
|
|
19
|
+
## 📦 Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# npm
|
|
23
|
+
npm install @mrintel/villain-ui
|
|
24
|
+
|
|
25
|
+
# pnpm
|
|
26
|
+
pnpm add @mrintel/villain-ui
|
|
27
|
+
|
|
28
|
+
# yarn
|
|
29
|
+
yarn add @mrintel/villain-ui
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> **Note on Imports:** All components are exported from the root package (`@mrintel/villain-ui`). Category-specific subpath imports (e.g., `@mrintel/villain-ui/buttons`) are not provided, as the library is designed for tree-shaking at the component level. Your bundler will automatically include only the components you import.
|
|
33
|
+
|
|
34
|
+
### Peer Dependencies
|
|
35
|
+
|
|
36
|
+
Install the required peer dependency:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install svelte@^5.0.0
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Note on Tailwind CSS:** The library uses Tailwind CSS v4 internally for styling, but it is **not required** as a peer dependency. The compiled theme CSS is included in the package. You only need to install Tailwind CSS in your project if you want to extend the library's theme with custom Tailwind utilities or use Tailwind for your own application styling.
|
|
43
|
+
|
|
44
|
+
### Import Theme
|
|
45
|
+
|
|
46
|
+
**Important:** The theme CSS is **not** automatically imported when you use components. You must explicitly import it in your app's entry point to apply the default styles.
|
|
47
|
+
|
|
48
|
+
Import the theme CSS in your app's entry point (e.g., `+layout.svelte` in SvelteKit or `main.ts` in Vite):
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import '@mrintel/villain-ui/theme.css';
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This explicit import strategy gives you full control over styling and allows you to:
|
|
55
|
+
- Use a custom theme instead of the default
|
|
56
|
+
- Conditionally load themes
|
|
57
|
+
- Override theme variables before or after the default theme loads
|
|
58
|
+
|
|
59
|
+
## 🚀 Quick Start
|
|
60
|
+
|
|
61
|
+
### SvelteKit Setup
|
|
62
|
+
|
|
63
|
+
```svelte
|
|
64
|
+
<!-- src/routes/+layout.svelte -->
|
|
65
|
+
<script>
|
|
66
|
+
import '@mrintel/villain-ui/theme.css';
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<slot />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Basic Usage
|
|
73
|
+
|
|
74
|
+
```svelte
|
|
75
|
+
<script>
|
|
76
|
+
import { Button, Card } from '@mrintel/villain-ui';
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<Card padding="lg">
|
|
80
|
+
<h1>Welcome to @mrintel/villain-ui</h1>
|
|
81
|
+
<p>Build luxury interfaces with ease.</p>
|
|
82
|
+
<Button variant="primary">Get Started</Button>
|
|
83
|
+
</Card>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## ✨ Key Features
|
|
87
|
+
|
|
88
|
+
### Icon Support with Snippets
|
|
89
|
+
|
|
90
|
+
Comprehensive icon snippet support across the entire component library for maximum flexibility:
|
|
91
|
+
|
|
92
|
+
**Forms:**
|
|
93
|
+
- **Input, Textarea, Select**: `iconBefore` and `iconAfter` snippets for flexible positioning
|
|
94
|
+
- **Checkbox, Switch**: `iconBefore` snippet for visual enhancement
|
|
95
|
+
- **RadioGroup**: Per-option `iconBefore` in options array for rich radio lists
|
|
96
|
+
- **FileUpload**: Custom `icon` snippet to override default upload icon
|
|
97
|
+
|
|
98
|
+
**Buttons & Navigation:**
|
|
99
|
+
- **Button, LinkButton**: `iconBefore` and `iconAfter` snippets for flexible positioning
|
|
100
|
+
- **Breadcrumbs**: Per-item `icon` in items array for visual breadcrumb trails
|
|
101
|
+
- **Pagination**: `prevIcon` and `nextIcon` snippets for custom navigation arrows
|
|
102
|
+
- **Tabs**: `iconBefore` in tab objects for tabbed navigation
|
|
103
|
+
|
|
104
|
+
**Data Display:**
|
|
105
|
+
- **Badge**: Simple `icon` snippet for status indicators (not iconBefore, as it's a simple badge)
|
|
106
|
+
- **Tag**: Simple `icon` snippet for tags
|
|
107
|
+
- **List**: Per-item `icon` in items array for icon lists
|
|
108
|
+
- **Avatar**: Image/initials support (not snippet-based)
|
|
109
|
+
|
|
110
|
+
**Overlays:**
|
|
111
|
+
- **Alert**: `iconBefore` snippet for custom alert icons
|
|
112
|
+
- **Modal, Toast, Drawer**: `iconBefore` snippet for title icons
|
|
113
|
+
|
|
114
|
+
**Navigation:**
|
|
115
|
+
- **Menu**: Per-item `icon` in items array
|
|
116
|
+
- **DropdownMenu, ContextMenu**: Per-item `icon` in items array with structured `MenuItem[]` interface
|
|
117
|
+
|
|
118
|
+
#### Icon API Patterns
|
|
119
|
+
|
|
120
|
+
The library uses three consistent icon patterns:
|
|
121
|
+
|
|
122
|
+
**1. Simple icon snippet** - Single `icon?: Snippet` for components with one icon position:
|
|
123
|
+
```svelte
|
|
124
|
+
<Tag>
|
|
125
|
+
{#snippet icon()}
|
|
126
|
+
<StarIcon class="w-4 h-4" />
|
|
127
|
+
{/snippet}
|
|
128
|
+
Featured
|
|
129
|
+
</Tag>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**2. Positional icon snippets** - Separate `iconBefore`/`iconAfter` snippets for flexible positioning:
|
|
133
|
+
```svelte
|
|
134
|
+
<Button>
|
|
135
|
+
{#snippet iconAfter()}
|
|
136
|
+
<ArrowRightIcon class="w-5 h-5" />
|
|
137
|
+
{/snippet}
|
|
138
|
+
Next
|
|
139
|
+
</Button>
|
|
140
|
+
|
|
141
|
+
<!-- Or with both icons -->
|
|
142
|
+
<LinkButton>
|
|
143
|
+
{#snippet iconBefore()}
|
|
144
|
+
<HomeIcon class="w-5 h-5" />
|
|
145
|
+
{/snippet}
|
|
146
|
+
Home
|
|
147
|
+
{#snippet iconAfter()}
|
|
148
|
+
<ExternalLinkIcon class="w-4 h-4" />
|
|
149
|
+
{/snippet}
|
|
150
|
+
</LinkButton>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**3. Per-item icons** - Icons specified in item/option arrays:
|
|
154
|
+
```svelte
|
|
155
|
+
<script>
|
|
156
|
+
const options = [
|
|
157
|
+
{
|
|
158
|
+
value: 'option1',
|
|
159
|
+
label: 'Option 1',
|
|
160
|
+
iconBefore: IconSnippet // Snippet reference
|
|
161
|
+
}
|
|
162
|
+
];
|
|
163
|
+
</script>
|
|
164
|
+
|
|
165
|
+
<RadioGroup {options} bind:value={selected} />
|
|
166
|
+
<Breadcrumbs items={breadcrumbItems} />
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### Best Practices
|
|
170
|
+
|
|
171
|
+
- **Consistent sizing**: Use `w-5 h-5` or `w-4 h-4` across your application for visual harmony
|
|
172
|
+
- **Color inheritance**: Icons automatically inherit text color via `currentColor`
|
|
173
|
+
- **Library agnostic**: Works with any icon library (Heroicons, Lucide, Phosphor, etc.) or inline SVG
|
|
174
|
+
- **Optional everywhere**: All icon snippets are optional - components work perfectly without them
|
|
175
|
+
|
|
176
|
+
### Active State Support
|
|
177
|
+
|
|
178
|
+
**Navbar** and **Sidebar** components now automatically style active navigation items:
|
|
179
|
+
|
|
180
|
+
- Add the `active` class to links/buttons for current page indication
|
|
181
|
+
- Or use the new `currentPath` prop for automatic active state management (see Layout Best Practices)
|
|
182
|
+
- **How `currentPath` works**: Components scan for `<a>` and `<button>` elements, match their `href` or `data-href` attribute against `currentPath`, and automatically add/remove the `active` class
|
|
183
|
+
- **Manual classes preserved**: Manually applied `active` classes take precedence and are never removed by automatic management
|
|
184
|
+
- **Falsy currentPath**: When `currentPath` becomes falsy, only auto-managed `active` classes are cleared
|
|
185
|
+
- **Button support**: Buttons need a `data-href` attribute to participate in automatic active state (e.g., `<button data-href="/action">Action</button>`)
|
|
186
|
+
- Navbar: Shows accent color with underline indicator
|
|
187
|
+
- Sidebar: Shows accent background, left border, and glow effect
|
|
188
|
+
- Works seamlessly with SvelteKit's page stores or manual state management
|
|
189
|
+
|
|
190
|
+
### Improved Layout Management
|
|
191
|
+
|
|
192
|
+
**Automatic Sidebar Positioning** - Sidebar now automatically detects Navbar presence and adjusts its top position to start just below the Navbar. Zero configuration needed!
|
|
193
|
+
|
|
194
|
+
**How it works:**
|
|
195
|
+
- Sidebar uses a `data-navbar` attribute selector to find the Navbar element
|
|
196
|
+
- Reads the Navbar's `offsetHeight` and dynamically sets its own `top` style property
|
|
197
|
+
- A ResizeObserver watches for Navbar height changes (responsive behavior, window resize)
|
|
198
|
+
- When Navbar is absent, Sidebar starts from the top (top: 0)
|
|
199
|
+
|
|
200
|
+
**Z-index layering** ensures proper visual hierarchy:
|
|
201
|
+
- **Navbar**: `z-50` (highest, sits on top)
|
|
202
|
+
- **Sidebar**: `z-40` (below navbar, above content)
|
|
203
|
+
- **Modals, tooltips, overlays**: `z-50+` (above everything)
|
|
204
|
+
|
|
205
|
+
**Example - Zero Configuration:**
|
|
206
|
+
```svelte
|
|
207
|
+
<script>
|
|
208
|
+
let sidebarOpen = $state(true);
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
<!-- Navbar automatically gets data-navbar attribute -->
|
|
212
|
+
<Navbar position="sticky" height="md">
|
|
213
|
+
{#snippet toggleButton()}
|
|
214
|
+
<IconButton variant="ghost" onclick={() => sidebarOpen = !sidebarOpen}>
|
|
215
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
216
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
|
217
|
+
</svg>
|
|
218
|
+
</IconButton>
|
|
219
|
+
{/snippet}
|
|
220
|
+
|
|
221
|
+
{#snippet logo()}
|
|
222
|
+
<span>MyApp</span>
|
|
223
|
+
{/snippet}
|
|
224
|
+
|
|
225
|
+
{#snippet navigation()}
|
|
226
|
+
<a href="/">Home</a>
|
|
227
|
+
<a href="/about">About</a>
|
|
228
|
+
{/snippet}
|
|
229
|
+
|
|
230
|
+
{#snippet actions()}
|
|
231
|
+
<Button variant="primary">Sign In</Button>
|
|
232
|
+
{/snippet}
|
|
233
|
+
</Navbar>
|
|
234
|
+
|
|
235
|
+
<!-- Sidebar automatically detects Navbar and positions below it -->
|
|
236
|
+
<Sidebar bind:open={sidebarOpen} side="left" width="md">
|
|
237
|
+
<!-- Sidebar content -->
|
|
238
|
+
</Sidebar>
|
|
239
|
+
|
|
240
|
+
<!-- No manual margin-top needed on Sidebar! -->
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Responsive behavior**: The Sidebar's positioning updates automatically when the Navbar height changes (e.g., responsive breakpoints, content changes). This ensures consistent layout across all screen sizes.
|
|
244
|
+
|
|
245
|
+
## 📚 Components
|
|
246
|
+
|
|
247
|
+
### Buttons
|
|
248
|
+
|
|
249
|
+
**Button** - Primary interactive element with variants
|
|
250
|
+
|
|
251
|
+
```svelte
|
|
252
|
+
<script>
|
|
253
|
+
import { Button } from '@mrintel/villain-ui';
|
|
254
|
+
</script>
|
|
255
|
+
|
|
256
|
+
<Button variant="primary" size="md">Primary Button</Button>
|
|
257
|
+
<Button variant="secondary" size="md">Secondary Button</Button>
|
|
258
|
+
<Button variant="ghost" size="sm">Ghost Button</Button>
|
|
259
|
+
<Button variant="primary" size="lg" disabled>Disabled</Button>
|
|
260
|
+
|
|
261
|
+
<!-- Simple icon usage -->
|
|
262
|
+
<Button variant="primary">
|
|
263
|
+
{#snippet iconBefore()}
|
|
264
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
265
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
266
|
+
</svg>
|
|
267
|
+
{/snippet}
|
|
268
|
+
Add Item
|
|
269
|
+
</Button>
|
|
270
|
+
|
|
271
|
+
<!-- Icon after text -->
|
|
272
|
+
<Button variant="secondary">
|
|
273
|
+
Download
|
|
274
|
+
{#snippet iconAfter()}
|
|
275
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
276
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
|
277
|
+
</svg>
|
|
278
|
+
{/snippet}
|
|
279
|
+
</Button>
|
|
280
|
+
|
|
281
|
+
<!-- Advanced: Different icons before and after -->
|
|
282
|
+
<Button variant="primary">
|
|
283
|
+
{#snippet iconBefore()}
|
|
284
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
285
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
286
|
+
</svg>
|
|
287
|
+
{/snippet}
|
|
288
|
+
Upload Photo
|
|
289
|
+
{#snippet iconAfter()}
|
|
290
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
291
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
292
|
+
</svg>
|
|
293
|
+
{/snippet}
|
|
294
|
+
</Button>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**IconButton** - Compact button for icon-only interactions
|
|
298
|
+
|
|
299
|
+
```svelte
|
|
300
|
+
<script>
|
|
301
|
+
import { IconButton } from '@mrintel/villain-ui';
|
|
302
|
+
</script>
|
|
303
|
+
|
|
304
|
+
<IconButton variant="primary" size="md" ariaLabel="Settings">
|
|
305
|
+
<SettingsIcon />
|
|
306
|
+
</IconButton>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Props:**
|
|
310
|
+
- `variant?: 'primary' | 'secondary' | 'ghost'` - Button style variant
|
|
311
|
+
- `size?: 'sm' | 'md' | 'lg'` - Button size
|
|
312
|
+
- `ariaLabel: string` - Accessibility label (required for screen readers)
|
|
313
|
+
- `disabled?: boolean` - Disable button interaction
|
|
314
|
+
|
|
315
|
+
**ButtonGroup** - Group related buttons together
|
|
316
|
+
|
|
317
|
+
```svelte
|
|
318
|
+
<script>
|
|
319
|
+
import { ButtonGroup, Button } from '@mrintel/villain-ui';
|
|
320
|
+
</script>
|
|
321
|
+
|
|
322
|
+
<ButtonGroup>
|
|
323
|
+
<Button variant="secondary">Left</Button>
|
|
324
|
+
<Button variant="secondary">Center</Button>
|
|
325
|
+
<Button variant="secondary">Right</Button>
|
|
326
|
+
</ButtonGroup>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**LinkButton** - Button styled as link
|
|
330
|
+
|
|
331
|
+
```svelte
|
|
332
|
+
<script>
|
|
333
|
+
import { LinkButton } from '@mrintel/villain-ui';
|
|
334
|
+
</script>
|
|
335
|
+
|
|
336
|
+
<LinkButton href="/docs" variant="primary">View Documentation</LinkButton>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Icon Examples:**
|
|
340
|
+
```svelte
|
|
341
|
+
<!-- LinkButton with icon before text -->
|
|
342
|
+
<LinkButton href="/docs" variant="primary">
|
|
343
|
+
{#snippet iconBefore()}
|
|
344
|
+
<BookOpenIcon class="w-5 h-5" />
|
|
345
|
+
{/snippet}
|
|
346
|
+
View Documentation
|
|
347
|
+
</LinkButton>
|
|
348
|
+
|
|
349
|
+
<!-- LinkButton with icon after text -->
|
|
350
|
+
<LinkButton href="/download" variant="secondary">
|
|
351
|
+
Download
|
|
352
|
+
{#snippet iconAfter()}
|
|
353
|
+
<DownloadIcon class="w-5 h-5" />
|
|
354
|
+
{/snippet}
|
|
355
|
+
</LinkButton>
|
|
356
|
+
|
|
357
|
+
<!-- LinkButton with different icons before and after -->
|
|
358
|
+
<LinkButton href="/external" variant="ghost" target="_blank">
|
|
359
|
+
{#snippet iconBefore()}
|
|
360
|
+
<ExternalLinkIcon class="w-4 h-4" />
|
|
361
|
+
{/snippet}
|
|
362
|
+
External Link
|
|
363
|
+
{#snippet iconAfter()}
|
|
364
|
+
<ArrowRightIcon class="w-4 h-4" />
|
|
365
|
+
{/snippet}
|
|
366
|
+
</LinkButton>
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**FloatingActionButton** - Prominent floating action button
|
|
370
|
+
|
|
371
|
+
```svelte
|
|
372
|
+
<script>
|
|
373
|
+
import { FloatingActionButton } from '@mrintel/villain-ui';
|
|
374
|
+
</script>
|
|
375
|
+
|
|
376
|
+
<FloatingActionButton
|
|
377
|
+
position="bottom-right"
|
|
378
|
+
ariaLabel="Create new item"
|
|
379
|
+
onclick={() => console.log('FAB clicked')}
|
|
380
|
+
>
|
|
381
|
+
<PlusIcon />
|
|
382
|
+
</FloatingActionButton>
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Props:**
|
|
386
|
+
- `position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'` - FAB position on screen
|
|
387
|
+
- `ariaLabel: string` - Accessibility label (required for screen readers)
|
|
388
|
+
- `onclick?: () => void` - Click handler
|
|
389
|
+
- `disabled?: boolean` - Disable button interaction
|
|
390
|
+
|
|
391
|
+
### Forms
|
|
392
|
+
|
|
393
|
+
**Input** - Text input with label and error states
|
|
394
|
+
|
|
395
|
+
```svelte
|
|
396
|
+
<script>
|
|
397
|
+
import { Input } from '@mrintel/villain-ui';
|
|
398
|
+
|
|
399
|
+
let email = $state('');
|
|
400
|
+
let hasError = $state(false);
|
|
401
|
+
</script>
|
|
402
|
+
|
|
403
|
+
<Input
|
|
404
|
+
type="email"
|
|
405
|
+
label="Email Address"
|
|
406
|
+
placeholder="you@example.com"
|
|
407
|
+
bind:value={email}
|
|
408
|
+
error={hasError}
|
|
409
|
+
/>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Icon Examples:**
|
|
413
|
+
```svelte
|
|
414
|
+
<!-- Input with icon before (search) -->
|
|
415
|
+
<Input
|
|
416
|
+
type="text"
|
|
417
|
+
label="Search"
|
|
418
|
+
placeholder="Search..."
|
|
419
|
+
bind:value={searchQuery}
|
|
420
|
+
>
|
|
421
|
+
{#snippet iconBefore()}
|
|
422
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
423
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
424
|
+
</svg>
|
|
425
|
+
{/snippet}
|
|
426
|
+
</Input>
|
|
427
|
+
|
|
428
|
+
<!-- Input with icon after (password visibility toggle) -->
|
|
429
|
+
<Input
|
|
430
|
+
type="password"
|
|
431
|
+
label="Password"
|
|
432
|
+
bind:value={password}
|
|
433
|
+
>
|
|
434
|
+
{#snippet iconAfter()}
|
|
435
|
+
<button onclick={togglePasswordVisibility}>
|
|
436
|
+
<EyeIcon class="w-5 h-5" />
|
|
437
|
+
</button>
|
|
438
|
+
{/snippet}
|
|
439
|
+
</Input>
|
|
440
|
+
|
|
441
|
+
<!-- Input with email icon -->
|
|
442
|
+
<Input
|
|
443
|
+
type="email"
|
|
444
|
+
label="Email"
|
|
445
|
+
bind:value={email}
|
|
446
|
+
>
|
|
447
|
+
{#snippet iconBefore()}
|
|
448
|
+
<MailIcon class="w-5 h-5" />
|
|
449
|
+
{/snippet}
|
|
450
|
+
</Input>
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Textarea** - Multi-line text input
|
|
454
|
+
|
|
455
|
+
```svelte
|
|
456
|
+
<script>
|
|
457
|
+
import { Textarea } from '@mrintel/villain-ui';
|
|
458
|
+
|
|
459
|
+
let comment = $state('');
|
|
460
|
+
</script>
|
|
461
|
+
|
|
462
|
+
<Textarea
|
|
463
|
+
label="Comment"
|
|
464
|
+
placeholder="Enter your comment..."
|
|
465
|
+
rows={5}
|
|
466
|
+
bind:value={comment}
|
|
467
|
+
/>
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Icon Example:**
|
|
471
|
+
```svelte
|
|
472
|
+
<Textarea
|
|
473
|
+
label="Message"
|
|
474
|
+
rows={5}
|
|
475
|
+
bind:value={message}
|
|
476
|
+
>
|
|
477
|
+
{#snippet iconBefore()}
|
|
478
|
+
<MessageIcon class="w-5 h-5" />
|
|
479
|
+
{/snippet}
|
|
480
|
+
</Textarea>
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
**Note:** Textarea only supports `iconBefore` snippet, positioned at top-left of the text area.
|
|
484
|
+
|
|
485
|
+
**Select** - Dropdown selection
|
|
486
|
+
|
|
487
|
+
```svelte
|
|
488
|
+
<script>
|
|
489
|
+
import { Select } from '@mrintel/villain-ui';
|
|
490
|
+
|
|
491
|
+
let selected = $state('');
|
|
492
|
+
const options = [
|
|
493
|
+
{ value: 'option1', label: 'Option 1' },
|
|
494
|
+
{ value: 'option2', label: 'Option 2' },
|
|
495
|
+
{ value: 'option3', label: 'Option 3' }
|
|
496
|
+
];
|
|
497
|
+
</script>
|
|
498
|
+
|
|
499
|
+
<Select label="Choose an option" {options} bind:value={selected} />
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**Icon Example:**
|
|
503
|
+
```svelte
|
|
504
|
+
<Select
|
|
505
|
+
label="Country"
|
|
506
|
+
{options}
|
|
507
|
+
bind:value={selectedCountry}
|
|
508
|
+
>
|
|
509
|
+
{#snippet iconBefore()}
|
|
510
|
+
<GlobeIcon class="w-5 h-5" />
|
|
511
|
+
{/snippet}
|
|
512
|
+
</Select>
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**Note:** Select only supports `iconBefore` snippet, positioned at the left side with automatic padding.
|
|
516
|
+
|
|
517
|
+
**Checkbox** - Boolean selection
|
|
518
|
+
|
|
519
|
+
```svelte
|
|
520
|
+
<script>
|
|
521
|
+
import { Checkbox } from '@mrintel/villain-ui';
|
|
522
|
+
|
|
523
|
+
let accepted = $state(false);
|
|
524
|
+
</script>
|
|
525
|
+
|
|
526
|
+
<Checkbox label="I accept the terms and conditions" bind:checked={accepted} />
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
**Icon Example:**
|
|
530
|
+
```svelte
|
|
531
|
+
<Checkbox bind:checked={accepted}>
|
|
532
|
+
{#snippet iconBefore()}
|
|
533
|
+
<ShieldCheckIcon class="w-4 h-4" />
|
|
534
|
+
{/snippet}
|
|
535
|
+
I accept the terms and conditions
|
|
536
|
+
</Checkbox>
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Switch** - Toggle switch
|
|
540
|
+
|
|
541
|
+
```svelte
|
|
542
|
+
<script>
|
|
543
|
+
import { Switch } from '@mrintel/villain-ui';
|
|
544
|
+
|
|
545
|
+
let enabled = $state(false);
|
|
546
|
+
</script>
|
|
547
|
+
|
|
548
|
+
<Switch label="Enable notifications" bind:checked={enabled} />
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**Icon Example:**
|
|
552
|
+
```svelte
|
|
553
|
+
<Switch bind:checked={darkMode}>
|
|
554
|
+
{#snippet iconBefore()}
|
|
555
|
+
<MoonIcon class="w-4 h-4" />
|
|
556
|
+
{/snippet}
|
|
557
|
+
Dark Mode
|
|
558
|
+
</Switch>
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**RadioGroup** - Single selection from multiple options
|
|
562
|
+
|
|
563
|
+
```svelte
|
|
564
|
+
<script>
|
|
565
|
+
import { RadioGroup } from '@mrintel/villain-ui';
|
|
566
|
+
|
|
567
|
+
let selected = $state('');
|
|
568
|
+
const options = [
|
|
569
|
+
{ value: 'small', label: 'Small' },
|
|
570
|
+
{ value: 'medium', label: 'Medium' },
|
|
571
|
+
{ value: 'large', label: 'Large' }
|
|
572
|
+
];
|
|
573
|
+
</script>
|
|
574
|
+
|
|
575
|
+
<RadioGroup label="Select size" {options} bind:value={selected} />
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**Icon Example:**
|
|
579
|
+
```svelte
|
|
580
|
+
<script>
|
|
581
|
+
import { RadioGroup } from '@mrintel/villain-ui';
|
|
582
|
+
import { CreditCardIcon, PayPalIcon } from 'your-icon-library';
|
|
583
|
+
|
|
584
|
+
const options = [
|
|
585
|
+
{
|
|
586
|
+
value: 'card',
|
|
587
|
+
label: 'Credit Card',
|
|
588
|
+
iconBefore: CreditCardIcon // Snippet reference
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
value: 'paypal',
|
|
592
|
+
label: 'PayPal',
|
|
593
|
+
iconBefore: PayPalIcon // Snippet reference
|
|
594
|
+
}
|
|
595
|
+
];
|
|
596
|
+
</script>
|
|
597
|
+
|
|
598
|
+
<RadioGroup
|
|
599
|
+
label="Payment Method"
|
|
600
|
+
{options}
|
|
601
|
+
bind:value={paymentMethod}
|
|
602
|
+
/>
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**RangeSlider** - Numeric range selection
|
|
606
|
+
|
|
607
|
+
```svelte
|
|
608
|
+
<script>
|
|
609
|
+
import { RangeSlider } from '@mrintel/villain-ui';
|
|
610
|
+
|
|
611
|
+
let volume = $state(50);
|
|
612
|
+
</script>
|
|
613
|
+
|
|
614
|
+
<RangeSlider label="Volume" min={0} max={100} bind:value={volume} />
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
**FileUpload** - File selection with drag & drop
|
|
618
|
+
|
|
619
|
+
```svelte
|
|
620
|
+
<script>
|
|
621
|
+
import { FileUpload } from '@mrintel/villain-ui';
|
|
622
|
+
|
|
623
|
+
function handleUpload(files) {
|
|
624
|
+
console.log('Files:', files);
|
|
625
|
+
}
|
|
626
|
+
</script>
|
|
627
|
+
|
|
628
|
+
<FileUpload
|
|
629
|
+
accept="image/*"
|
|
630
|
+
multiple
|
|
631
|
+
onchange={handleUpload}
|
|
632
|
+
label="Upload Images"
|
|
633
|
+
/>
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
**Icon Example:**
|
|
637
|
+
```svelte
|
|
638
|
+
<FileUpload
|
|
639
|
+
bind:files={uploadedFiles}
|
|
640
|
+
accept="image/*"
|
|
641
|
+
>
|
|
642
|
+
{#snippet icon()}
|
|
643
|
+
<CloudUploadIcon class="w-8 h-8" />
|
|
644
|
+
{/snippet}
|
|
645
|
+
</FileUpload>
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**Note:** FileUpload uses a simple `icon` snippet (not `iconBefore`) to replace the default upload icon.
|
|
649
|
+
|
|
650
|
+
**InputGroup** - Grouped input with addons
|
|
651
|
+
|
|
652
|
+
```svelte
|
|
653
|
+
<script>
|
|
654
|
+
import { InputGroup } from '@mrintel/villain-ui';
|
|
655
|
+
</script>
|
|
656
|
+
|
|
657
|
+
<InputGroup>
|
|
658
|
+
{#snippet prepend()}
|
|
659
|
+
https://
|
|
660
|
+
{/snippet}
|
|
661
|
+
|
|
662
|
+
<input type="text" placeholder="example.com" />
|
|
663
|
+
|
|
664
|
+
{#snippet append()}
|
|
665
|
+
.com
|
|
666
|
+
{/snippet}
|
|
667
|
+
</InputGroup>
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
**DatePicker** - Date input with native date picker
|
|
671
|
+
|
|
672
|
+
```svelte
|
|
673
|
+
<script>
|
|
674
|
+
import { DatePicker } from '@mrintel/villain-ui';
|
|
675
|
+
|
|
676
|
+
let selectedDate = $state('');
|
|
677
|
+
</script>
|
|
678
|
+
|
|
679
|
+
<DatePicker
|
|
680
|
+
label="Select Date"
|
|
681
|
+
bind:value={selectedDate}
|
|
682
|
+
min="2024-01-01"
|
|
683
|
+
max="2024-12-31"
|
|
684
|
+
/>
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
**Props:**
|
|
688
|
+
- `value?: string` - Selected date in YYYY-MM-DD format (bindable)
|
|
689
|
+
- `label?: string` - Input label
|
|
690
|
+
- `min?: string` - Minimum selectable date
|
|
691
|
+
- `max?: string` - Maximum selectable date
|
|
692
|
+
- `placeholder?: string` - Placeholder text
|
|
693
|
+
- `disabled?: boolean` - Disable date selection
|
|
694
|
+
- `error?: boolean` - Show error state
|
|
695
|
+
- `class?: string` - Additional CSS classes
|
|
696
|
+
|
|
697
|
+
**TimePicker** - Time input with native time picker
|
|
698
|
+
|
|
699
|
+
```svelte
|
|
700
|
+
<script>
|
|
701
|
+
import { TimePicker } from '@mrintel/villain-ui';
|
|
702
|
+
|
|
703
|
+
let selectedTime = $state('');
|
|
704
|
+
</script>
|
|
705
|
+
|
|
706
|
+
<TimePicker
|
|
707
|
+
label="Select Time"
|
|
708
|
+
bind:value={selectedTime}
|
|
709
|
+
step={900}
|
|
710
|
+
/>
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
**Props:**
|
|
714
|
+
- `value?: string` - Selected time in HH:MM format (bindable)
|
|
715
|
+
- `label?: string` - Input label
|
|
716
|
+
- `min?: string` - Minimum selectable time
|
|
717
|
+
- `max?: string` - Maximum selectable time
|
|
718
|
+
- `step?: number` - Time interval in seconds (e.g., 900 = 15 minutes)
|
|
719
|
+
- `placeholder?: string` - Placeholder text
|
|
720
|
+
- `disabled?: boolean` - Disable time selection
|
|
721
|
+
- `error?: boolean` - Show error state
|
|
722
|
+
- `class?: string` - Additional CSS classes
|
|
723
|
+
|
|
724
|
+
**DateTimePicker** - Combined date and time input
|
|
725
|
+
|
|
726
|
+
```svelte
|
|
727
|
+
<script>
|
|
728
|
+
import { DateTimePicker } from '@mrintel/villain-ui';
|
|
729
|
+
|
|
730
|
+
let selectedDateTime = $state('');
|
|
731
|
+
</script>
|
|
732
|
+
|
|
733
|
+
<DateTimePicker
|
|
734
|
+
label="Select Date & Time"
|
|
735
|
+
bind:value={selectedDateTime}
|
|
736
|
+
min="2024-01-01T00:00"
|
|
737
|
+
max="2024-12-31T23:59"
|
|
738
|
+
/>
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
**Props:**
|
|
742
|
+
- `value?: string` - Selected date-time in ISO format (bindable)
|
|
743
|
+
- `label?: string` - Input label
|
|
744
|
+
- `min?: string` - Minimum selectable date-time
|
|
745
|
+
- `max?: string` - Maximum selectable date-time
|
|
746
|
+
- `step?: number` - Time interval in seconds
|
|
747
|
+
- `placeholder?: string` - Placeholder text
|
|
748
|
+
- `disabled?: boolean` - Disable date-time selection
|
|
749
|
+
- `error?: boolean` - Show error state
|
|
750
|
+
- `class?: string` - Additional CSS classes
|
|
751
|
+
|
|
752
|
+
### Layout
|
|
753
|
+
|
|
754
|
+
**Card** - Content container with optional header and footer
|
|
755
|
+
|
|
756
|
+
```svelte
|
|
757
|
+
<script>
|
|
758
|
+
import { Card } from '@mrintel/villain-ui';
|
|
759
|
+
</script>
|
|
760
|
+
|
|
761
|
+
<Card padding="lg" hoverable>
|
|
762
|
+
{#snippet header()}
|
|
763
|
+
<h2>Card Title</h2>
|
|
764
|
+
{/snippet}
|
|
765
|
+
|
|
766
|
+
<p>Card content goes here with beautiful glass morphism effect.</p>
|
|
767
|
+
|
|
768
|
+
{#snippet footer()}
|
|
769
|
+
<Button variant="primary">Action</Button>
|
|
770
|
+
{/snippet}
|
|
771
|
+
</Card>
|
|
772
|
+
|
|
773
|
+
<!-- Card as a link with lift effect and icon -->
|
|
774
|
+
<Card href="/features" class="hover-lift" padding="lg">
|
|
775
|
+
{#snippet iconBefore()}
|
|
776
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
777
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
778
|
+
</svg>
|
|
779
|
+
{/snippet}
|
|
780
|
+
|
|
781
|
+
<h3>Feature Name</h3>
|
|
782
|
+
<p>Clickable card with hover lift effect.</p>
|
|
783
|
+
</Card>
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
**Props:**
|
|
787
|
+
- `href?: string` - Makes the card a clickable link (renders as `<a>` tag)
|
|
788
|
+
- `target?: string` - Link target attribute (e.g., '_blank')
|
|
789
|
+
- `rel?: string` - Link rel attribute (defaults to 'noopener noreferrer' for target='_blank')
|
|
790
|
+
- `padding?: 'none' | 'sm' | 'md' | 'lg'` - Internal padding (default: 'md')
|
|
791
|
+
- `iconBefore?: Snippet` - Optional icon displayed with `.card-icon` utility
|
|
792
|
+
- `header?: Snippet` - Optional header content
|
|
793
|
+
- `footer?: Snippet` - Optional footer content
|
|
794
|
+
- `children?: Snippet` - Main card content
|
|
795
|
+
- `class?: string` - Additional CSS classes
|
|
796
|
+
|
|
797
|
+
**Note:** For clickable cards with hover effects, use `href` to make the card a link and add `class="hover-lift"` to enable the lift animation. The `iconBefore` snippet uses the `.card-icon` utility class for centered, accent-colored icon display.
|
|
798
|
+
|
|
799
|
+
**Panel** - Simple content panel
|
|
800
|
+
|
|
801
|
+
```svelte
|
|
802
|
+
<script>
|
|
803
|
+
import { Panel } from '@mrintel/villain-ui';
|
|
804
|
+
</script>
|
|
805
|
+
|
|
806
|
+
<!-- Recommended: Use variant prop for styling -->
|
|
807
|
+
<Panel variant="glass" padding="lg">
|
|
808
|
+
<p>Enhanced glass morphism panel</p>
|
|
809
|
+
</Panel>
|
|
810
|
+
|
|
811
|
+
<!-- Default panel with basic glass styling -->
|
|
812
|
+
<Panel>
|
|
813
|
+
<p>Panel content with default styling</p>
|
|
814
|
+
</Panel>
|
|
815
|
+
|
|
816
|
+
<!-- Legacy: glass prop (deprecated, use variant='glass' instead) -->
|
|
817
|
+
<Panel glass={false}>
|
|
818
|
+
<p>Solid background panel (backwards compatible)</p>
|
|
819
|
+
</Panel>
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
**Panel Props:**
|
|
823
|
+
- `variant?: 'default' | 'glass'` - Primary styling selector. Use `'glass'` for enhanced glass morphism with accent glow.
|
|
824
|
+
- `padding?: 'none' | 'sm' | 'md' | 'lg'` - Internal padding (default: `'md'`)
|
|
825
|
+
- `rounded?: boolean` - Apply rounded corners (default: `true`)
|
|
826
|
+
- `glass?: boolean` - **Deprecated**. Use `variant='glass'` instead. Only affects `variant='default'` for backwards compatibility.
|
|
827
|
+
- `class?: string` - Additional CSS classes
|
|
828
|
+
|
|
829
|
+
**Grid** - Responsive grid layout
|
|
830
|
+
|
|
831
|
+
```svelte
|
|
832
|
+
<script>
|
|
833
|
+
import { Grid, Card } from '@mrintel/villain-ui';
|
|
834
|
+
</script>
|
|
835
|
+
|
|
836
|
+
<Grid columns={3} gap="lg">
|
|
837
|
+
<Card>Item 1</Card>
|
|
838
|
+
<Card>Item 2</Card>
|
|
839
|
+
<Card>Item 3</Card>
|
|
840
|
+
</Grid>
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
**Container** - Centered content container
|
|
844
|
+
|
|
845
|
+
```svelte
|
|
846
|
+
<script>
|
|
847
|
+
import { Container } from '@mrintel/villain-ui';
|
|
848
|
+
</script>
|
|
849
|
+
|
|
850
|
+
<Container maxWidth="lg">
|
|
851
|
+
<h1>Centered Content</h1>
|
|
852
|
+
</Container>
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
**SectionHeader** - Section heading with divider
|
|
856
|
+
|
|
857
|
+
```svelte
|
|
858
|
+
<script>
|
|
859
|
+
import { SectionHeader } from '@mrintel/villain-ui';
|
|
860
|
+
</script>
|
|
861
|
+
|
|
862
|
+
<SectionHeader title="Features" subtitle="What makes us different" />
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
**Divider** - Visual separator
|
|
866
|
+
|
|
867
|
+
```svelte
|
|
868
|
+
<script>
|
|
869
|
+
import { Divider } from '@mrintel/villain-ui';
|
|
870
|
+
</script>
|
|
871
|
+
|
|
872
|
+
<Divider />
|
|
873
|
+
<Divider orientation="vertical" />
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
### Navigation
|
|
877
|
+
|
|
878
|
+
**Navbar** - Top navigation bar
|
|
879
|
+
|
|
880
|
+
```svelte
|
|
881
|
+
<script>
|
|
882
|
+
import { Navbar, Button, IconButton } from '@mrintel/villain-ui';
|
|
883
|
+
import { page } from '$app/stores';
|
|
884
|
+
|
|
885
|
+
let sidebarOpen = $state(false);
|
|
886
|
+
let currentPath = $derived($page.url.pathname);
|
|
887
|
+
</script>
|
|
888
|
+
|
|
889
|
+
<Navbar {currentPath}>
|
|
890
|
+
{#snippet toggleButton()}
|
|
891
|
+
<IconButton variant="ghost" size="sm" onclick={() => sidebarOpen = !sidebarOpen}>
|
|
892
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
893
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
|
894
|
+
</svg>
|
|
895
|
+
</IconButton>
|
|
896
|
+
{/snippet}
|
|
897
|
+
|
|
898
|
+
{#snippet logo()}
|
|
899
|
+
<span style="color: var(--color-accent)">MyApp</span>
|
|
900
|
+
{/snippet}
|
|
901
|
+
|
|
902
|
+
{#snippet navigation()}
|
|
903
|
+
<a href="/">Home</a>
|
|
904
|
+
<a href="/about">About</a>
|
|
905
|
+
<a href="/contact">Contact</a>
|
|
906
|
+
{/snippet}
|
|
907
|
+
|
|
908
|
+
{#snippet actions()}
|
|
909
|
+
<Button variant="primary">Sign In</Button>
|
|
910
|
+
{/snippet}
|
|
911
|
+
</Navbar>
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
**Props:**
|
|
915
|
+
- `position?: 'sticky' | 'fixed'` - Navbar positioning (default: 'sticky')
|
|
916
|
+
- `height?: 'sm' | 'md' | 'lg'` - Navbar height (default: 'md')
|
|
917
|
+
- `navigationAlign?: 'left' | 'center'` - Navigation alignment (default: 'center')
|
|
918
|
+
- `toggleButton?: Snippet` - Toggle button slot (typically for sidebar/mobile menu)
|
|
919
|
+
- `logo?: Snippet` - Logo content snippet
|
|
920
|
+
- `navigation?: Snippet` - Primary navigation links slot
|
|
921
|
+
- `actions?: Snippet` - Action buttons or profile controls slot
|
|
922
|
+
- `children?: Snippet` - Fallback navigation content (used if `navigation` not provided)
|
|
923
|
+
- `currentPath?: string` - Current route path for automatic active state highlighting
|
|
924
|
+
|
|
925
|
+
**Note:** The `currentPath` prop enables automatic active state management. Links matching the current path will receive the `active` class automatically.
|
|
926
|
+
|
|
927
|
+
**Sidebar** - Side navigation with collapsible state
|
|
928
|
+
|
|
929
|
+
```svelte
|
|
930
|
+
<script>
|
|
931
|
+
import { Sidebar } from '@mrintel/villain-ui';
|
|
932
|
+
import { page } from '$app/stores';
|
|
933
|
+
|
|
934
|
+
let open = $state(true);
|
|
935
|
+
let currentPath = $derived($page.url.pathname);
|
|
936
|
+
</script>
|
|
937
|
+
|
|
938
|
+
<Sidebar bind:open {currentPath}>
|
|
939
|
+
{#snippet header()}
|
|
940
|
+
<div>App Name</div>
|
|
941
|
+
{/snippet}
|
|
942
|
+
|
|
943
|
+
<!-- Recommended markup pattern for predictable collapsed behavior -->
|
|
944
|
+
<a href="/dashboard">
|
|
945
|
+
<span class="sidebar-item-icon">
|
|
946
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
947
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
|
948
|
+
</svg>
|
|
949
|
+
</span>
|
|
950
|
+
<span class="sidebar-item-label">Dashboard</span>
|
|
951
|
+
</a>
|
|
952
|
+
|
|
953
|
+
<a href="/settings">
|
|
954
|
+
<span class="sidebar-item-icon">
|
|
955
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
956
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
957
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
958
|
+
</svg>
|
|
959
|
+
</span>
|
|
960
|
+
<span class="sidebar-item-label">Settings</span>
|
|
961
|
+
</a>
|
|
962
|
+
|
|
963
|
+
<!-- Items without icons will show first letter in collapsed mode -->
|
|
964
|
+
<a href="/profile">
|
|
965
|
+
<span class="sidebar-item-label">Profile</span>
|
|
966
|
+
</a>
|
|
967
|
+
</Sidebar>
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
**Props:**
|
|
971
|
+
- `open?: boolean` (bindable) - Sidebar open/collapsed state (default: true)
|
|
972
|
+
- `side?: 'left' | 'right'` - Sidebar position (default: 'left')
|
|
973
|
+
- `width?: 'sm' | 'md' | 'lg'` - Sidebar width when open (default: 'md')
|
|
974
|
+
- `header?: Snippet` - Header content snippet
|
|
975
|
+
- `children?: Snippet` - Sidebar navigation content
|
|
976
|
+
- `currentPath?: string` - Current route path for automatic active state highlighting
|
|
977
|
+
|
|
978
|
+
**Collapsed Mode Behavior:**
|
|
979
|
+
- Items with `.sidebar-item-icon` will show the icon centered when collapsed
|
|
980
|
+
- Items without icons will show the first letter of `.sidebar-item-label` (or text content) in a circular badge
|
|
981
|
+
- Use `.sidebar-item-icon` and `.sidebar-item-label` classes for predictable behavior
|
|
982
|
+
- `currentPath?: string` - Current route path for automatic active state highlighting
|
|
983
|
+
|
|
984
|
+
**Collapsed State Behavior:**
|
|
985
|
+
|
|
986
|
+
When `open={false}`, the sidebar automatically adapts its display:
|
|
987
|
+
- **Items with icons**: Shows centered icons (enlarged to `1.5rem`) with text hidden
|
|
988
|
+
- **Items without icons**: Shows first letter in an accent-colored circle as a fallback
|
|
989
|
+
- **Header**: Remains visible with reduced padding and centered text
|
|
990
|
+
|
|
991
|
+
The component automatically detects icons by looking for `<svg>`, `[class*="icon"]`, or `[data-icon]` elements. All transitions use smooth animations with `var(--ease-luxe)`.
|
|
992
|
+
|
|
993
|
+
**Active State Management**
|
|
994
|
+
|
|
995
|
+
Navigation components (`Navbar`, `Sidebar`) support automatic active state highlighting via the `currentPath` prop. Pass the current route path, and the component will automatically add the `active` class to matching links and buttons based on `href` or `data-href` attributes. This eliminates manual active class management.
|
|
996
|
+
|
|
997
|
+
Example:
|
|
998
|
+
```svelte
|
|
999
|
+
<script>
|
|
1000
|
+
import { page } from '$app/stores'; // SvelteKit
|
|
1001
|
+
let currentPath = $derived($page.url.pathname);
|
|
1002
|
+
</script>
|
|
1003
|
+
|
|
1004
|
+
<Navbar {currentPath}>
|
|
1005
|
+
<a href="/">Home</a>
|
|
1006
|
+
<a href="/about">About</a>
|
|
1007
|
+
</Navbar>
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
The component matches exact paths and nested routes (e.g., `/buttons` matches `/buttons/icon-button`).
|
|
1011
|
+
|
|
1012
|
+
**Tabs** - Tabbed interface
|
|
1013
|
+
|
|
1014
|
+
```svelte
|
|
1015
|
+
<script>
|
|
1016
|
+
import { Tabs } from '@mrintel/villain-ui';
|
|
1017
|
+
|
|
1018
|
+
let activeTab = $state('tab1');
|
|
1019
|
+
const tabs = [
|
|
1020
|
+
{
|
|
1021
|
+
id: 'tab1',
|
|
1022
|
+
label: 'Overview',
|
|
1023
|
+
iconBefore: OverviewIcon // Snippet reference
|
|
1024
|
+
},
|
|
1025
|
+
{ id: 'tab2', label: 'Analytics' },
|
|
1026
|
+
{ id: 'tab3', label: 'Reports' }
|
|
1027
|
+
];
|
|
1028
|
+
</script>
|
|
1029
|
+
|
|
1030
|
+
<Tabs {tabs} bind:activeTab ontabchange={(newTab) => console.log('Tab changed:', newTab)}>
|
|
1031
|
+
{#if activeTab === 'tab1'}
|
|
1032
|
+
<div>Overview content</div>
|
|
1033
|
+
{:else if activeTab === 'tab2'}
|
|
1034
|
+
<div>Analytics content</div>
|
|
1035
|
+
{:else}
|
|
1036
|
+
<div>Reports content</div>
|
|
1037
|
+
{/if}
|
|
1038
|
+
</Tabs>
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
**Keyboard Navigation:**
|
|
1042
|
+
- Arrow Left/Right (horizontal) or Up/Down (vertical): Navigate between tabs
|
|
1043
|
+
- Home: Jump to first tab
|
|
1044
|
+
- End: Jump to last tab
|
|
1045
|
+
- Tab: Move focus out of tab list
|
|
1046
|
+
|
|
1047
|
+
**Accessibility Features:**
|
|
1048
|
+
- ARIA: `role="tablist"`, `role="tab"`, `aria-selected`, `aria-disabled`
|
|
1049
|
+
- Roving tabindex: Only active tab is focusable (`tabindex="0"`)
|
|
1050
|
+
- Screen readers: Tab labels announced with selection state
|
|
1051
|
+
- Focus management: Arrow keys change both focus and selection
|
|
1052
|
+
|
|
1053
|
+
**Breadcrumbs** - Navigation breadcrumb trail
|
|
1054
|
+
|
|
1055
|
+
```svelte
|
|
1056
|
+
<script>
|
|
1057
|
+
import { Breadcrumbs } from '@mrintel/villain-ui';
|
|
1058
|
+
|
|
1059
|
+
const items = [
|
|
1060
|
+
{ label: 'Home', href: '/' },
|
|
1061
|
+
{ label: 'Products', href: '/products' },
|
|
1062
|
+
{ label: 'Category', href: '/products/category' },
|
|
1063
|
+
{ label: 'Item' }
|
|
1064
|
+
];
|
|
1065
|
+
</script>
|
|
1066
|
+
|
|
1067
|
+
<Breadcrumbs {items} />
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
**Icon Example:**
|
|
1071
|
+
```svelte
|
|
1072
|
+
<script>
|
|
1073
|
+
import { Breadcrumbs } from '@mrintel/villain-ui';
|
|
1074
|
+
import { HomeIcon, FolderIcon, DocumentIcon } from 'your-icon-library';
|
|
1075
|
+
|
|
1076
|
+
const items = [
|
|
1077
|
+
{
|
|
1078
|
+
label: 'Home',
|
|
1079
|
+
href: '/',
|
|
1080
|
+
icon: HomeIcon // Snippet reference
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
label: 'Projects',
|
|
1084
|
+
href: '/projects',
|
|
1085
|
+
icon: FolderIcon // Snippet reference
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
label: 'Document',
|
|
1089
|
+
icon: DocumentIcon // Snippet reference
|
|
1090
|
+
}
|
|
1091
|
+
];
|
|
1092
|
+
</script>
|
|
1093
|
+
|
|
1094
|
+
<Breadcrumbs {items} />
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
**Menu** - Vertical navigation menu
|
|
1098
|
+
|
|
1099
|
+
```svelte
|
|
1100
|
+
<script>
|
|
1101
|
+
import { Menu } from '@mrintel/villain-ui';
|
|
1102
|
+
|
|
1103
|
+
const items = [
|
|
1104
|
+
{
|
|
1105
|
+
id: 'dashboard',
|
|
1106
|
+
label: 'Dashboard',
|
|
1107
|
+
icon: DashboardIcon, // Snippet reference
|
|
1108
|
+
onclick: () => goto('/dashboard')
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
id: 'settings',
|
|
1112
|
+
label: 'Settings',
|
|
1113
|
+
icon: SettingsIcon, // Snippet reference
|
|
1114
|
+
onclick: () => goto('/settings')
|
|
1115
|
+
}
|
|
1116
|
+
];
|
|
1117
|
+
</script>
|
|
1118
|
+
|
|
1119
|
+
<Menu {items} />
|
|
1120
|
+
```
|
|
1121
|
+
|
|
1122
|
+
**Props:**
|
|
1123
|
+
- `items?: MenuItem[]` - Array of menu items
|
|
1124
|
+
- `children?: Snippet` - Alternative to items array for custom content
|
|
1125
|
+
|
|
1126
|
+
**MenuItem Interface:**
|
|
1127
|
+
- `id: string` - Unique identifier
|
|
1128
|
+
- `label: string` - Display text
|
|
1129
|
+
- `icon?: Snippet` - Optional icon snippet (not iconBefore)
|
|
1130
|
+
- `disabled?: boolean` - Disable item
|
|
1131
|
+
- `onclick?: () => void` - Click handler
|
|
1132
|
+
|
|
1133
|
+
**Keyboard Navigation:**
|
|
1134
|
+
- Arrow Down/Up: Navigate menu items (wraps around)
|
|
1135
|
+
- Home: Jump to first item
|
|
1136
|
+
- End: Jump to last item
|
|
1137
|
+
- Enter/Space: Activate selected item
|
|
1138
|
+
- Escape: Close menu (if in a dropdown/context menu)
|
|
1139
|
+
|
|
1140
|
+
**Accessibility Features:**
|
|
1141
|
+
- ARIA: `role="menu"`, `role="menuitem"`
|
|
1142
|
+
- Roving tabindex: Only selected item is focusable
|
|
1143
|
+
- Screen readers: Menu items announced with disabled state
|
|
1144
|
+
|
|
1145
|
+
**DropdownMenu** - Dropdown menu with items
|
|
1146
|
+
|
|
1147
|
+
```svelte
|
|
1148
|
+
<script>
|
|
1149
|
+
import { DropdownMenu, Button } from '@mrintel/villain-ui';
|
|
1150
|
+
import { EditIcon, TrashIcon } from 'your-icon-library';
|
|
1151
|
+
|
|
1152
|
+
const items = [
|
|
1153
|
+
{
|
|
1154
|
+
id: 'edit',
|
|
1155
|
+
label: 'Edit',
|
|
1156
|
+
icon: EditIcon, // Snippet reference
|
|
1157
|
+
onclick: () => console.log('Edit')
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
id: 'delete',
|
|
1161
|
+
label: 'Delete',
|
|
1162
|
+
icon: TrashIcon, // Snippet reference
|
|
1163
|
+
onclick: () => console.log('Delete'),
|
|
1164
|
+
disabled: false
|
|
1165
|
+
}
|
|
1166
|
+
];
|
|
1167
|
+
</script>
|
|
1168
|
+
|
|
1169
|
+
<DropdownMenu {items}>
|
|
1170
|
+
{#snippet trigger()}
|
|
1171
|
+
<Button>Options</Button>
|
|
1172
|
+
{/snippet}
|
|
1173
|
+
</DropdownMenu>
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
**Props:**
|
|
1177
|
+
- `items: MenuItem[]` - Array of menu items
|
|
1178
|
+
- `open?: boolean` (bindable) - Dropdown open state
|
|
1179
|
+
- `placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'` - Menu position
|
|
1180
|
+
- `trigger?: Snippet` - Trigger button content
|
|
1181
|
+
|
|
1182
|
+
**MenuItem Interface:**
|
|
1183
|
+
- `id: string` - Unique identifier
|
|
1184
|
+
- `label: string` - Display text
|
|
1185
|
+
- `icon?: Snippet` - Optional icon snippet
|
|
1186
|
+
- `disabled?: boolean` - Disable item
|
|
1187
|
+
- `onclick?: () => void` - Click handler
|
|
1188
|
+
|
|
1189
|
+
**Keyboard Navigation:**
|
|
1190
|
+
- Arrow Down/Up: Navigate menu items (wraps around)
|
|
1191
|
+
- Home: Jump to first item
|
|
1192
|
+
- End: Jump to last item
|
|
1193
|
+
- Enter/Space: Activate selected item
|
|
1194
|
+
- Escape: Close menu
|
|
1195
|
+
|
|
1196
|
+
**Accessibility Features:**
|
|
1197
|
+
- ARIA: `role="menu"`, `role="menuitem"`, `aria-haspopup`, `aria-expanded`, `aria-controls`
|
|
1198
|
+
- Auto-focus: First item focused on open
|
|
1199
|
+
- Roving tabindex: Only selected item is focusable
|
|
1200
|
+
- Click outside: Closes menu when clicking outside
|
|
1201
|
+
- Screen readers: Menu state and items announced clearly
|
|
1202
|
+
|
|
1203
|
+
**ContextMenu** - Right-click context menu
|
|
1204
|
+
|
|
1205
|
+
```svelte
|
|
1206
|
+
<script>
|
|
1207
|
+
import { ContextMenu } from '@mrintel/villain-ui';
|
|
1208
|
+
import { CopyIcon, PasteIcon } from 'your-icon-library';
|
|
1209
|
+
|
|
1210
|
+
const items = [
|
|
1211
|
+
{
|
|
1212
|
+
id: 'copy',
|
|
1213
|
+
label: 'Copy',
|
|
1214
|
+
icon: CopyIcon, // Snippet reference
|
|
1215
|
+
onclick: () => console.log('Copy')
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
id: 'paste',
|
|
1219
|
+
label: 'Paste',
|
|
1220
|
+
icon: PasteIcon, // Snippet reference
|
|
1221
|
+
onclick: () => console.log('Paste')
|
|
1222
|
+
}
|
|
1223
|
+
];
|
|
1224
|
+
</script>
|
|
1225
|
+
|
|
1226
|
+
<ContextMenu {items}>
|
|
1227
|
+
{#snippet trigger()}
|
|
1228
|
+
<div>Right click me</div>
|
|
1229
|
+
{/snippet}
|
|
1230
|
+
</ContextMenu>
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
**Props:**
|
|
1234
|
+
- `items: MenuItem[]` - Array of menu items
|
|
1235
|
+
- `open?: boolean` (bindable) - Context menu open state
|
|
1236
|
+
- `x?: number` (bindable) - Menu X position
|
|
1237
|
+
- `y?: number` (bindable) - Menu Y position
|
|
1238
|
+
- `trigger?: Snippet` - Content that triggers context menu on right-click
|
|
1239
|
+
|
|
1240
|
+
**MenuItem Interface:**
|
|
1241
|
+
- `id: string` - Unique identifier
|
|
1242
|
+
- `label: string` - Display text
|
|
1243
|
+
- `icon?: Snippet` - Optional icon snippet
|
|
1244
|
+
- `disabled?: boolean` - Disable item
|
|
1245
|
+
- `onclick?: () => void` - Click handler
|
|
1246
|
+
|
|
1247
|
+
**Keyboard Navigation:**
|
|
1248
|
+
- Arrow Down/Up: Navigate menu items (wraps around)
|
|
1249
|
+
- Home: Jump to first item
|
|
1250
|
+
- End: Jump to last item
|
|
1251
|
+
- Enter/Space: Activate selected item
|
|
1252
|
+
- Escape: Close menu
|
|
1253
|
+
|
|
1254
|
+
**Accessibility Features:**
|
|
1255
|
+
- ARIA: `role="menu"`, `role="menuitem"`, `aria-haspopup`
|
|
1256
|
+
- Auto-focus: First item focused on open
|
|
1257
|
+
- Roving tabindex: Only selected item is focusable
|
|
1258
|
+
- Click outside: Closes menu when clicking outside
|
|
1259
|
+
- Screen readers: Menu state and items announced clearly
|
|
1260
|
+
|
|
1261
|
+
### Overlays & Feedback
|
|
1262
|
+
|
|
1263
|
+
**Modal** - Modal dialog with backdrop
|
|
1264
|
+
|
|
1265
|
+
```svelte
|
|
1266
|
+
<script>
|
|
1267
|
+
import { Modal, Button } from '@mrintel/villain-ui';
|
|
1268
|
+
|
|
1269
|
+
let open = $state(false);
|
|
1270
|
+
</script>
|
|
1271
|
+
|
|
1272
|
+
<Button onclick={() => open = true}>Open Modal</Button>
|
|
1273
|
+
|
|
1274
|
+
<Modal bind:open title="Confirm Action">
|
|
1275
|
+
<p>Are you sure you want to proceed?</p>
|
|
1276
|
+
|
|
1277
|
+
{#snippet footer()}
|
|
1278
|
+
<Button variant="ghost" onclick={() => open = false}>Cancel</Button>
|
|
1279
|
+
<Button variant="primary" onclick={() => open = false}>Confirm</Button>
|
|
1280
|
+
{/snippet}
|
|
1281
|
+
</Modal>
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
**Icon Example:**
|
|
1285
|
+
```svelte
|
|
1286
|
+
<script>
|
|
1287
|
+
import { Modal, Button } from '@mrintel/villain-ui';
|
|
1288
|
+
import { ExclamationTriangleIcon } from 'your-icon-library';
|
|
1289
|
+
|
|
1290
|
+
let open = $state(false);
|
|
1291
|
+
</script>
|
|
1292
|
+
|
|
1293
|
+
<Button onclick={() => open = true}>Delete Item</Button>
|
|
1294
|
+
|
|
1295
|
+
<Modal bind:open title="Confirm Deletion">
|
|
1296
|
+
{#snippet iconBefore()}
|
|
1297
|
+
<ExclamationTriangleIcon class="w-6 h-6 text-error" />
|
|
1298
|
+
{/snippet}
|
|
1299
|
+
|
|
1300
|
+
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
|
|
1301
|
+
|
|
1302
|
+
{#snippet footer()}
|
|
1303
|
+
<Button variant="ghost" onclick={() => open = false}>Cancel</Button>
|
|
1304
|
+
<Button variant="primary" onclick={() => { deleteItem(); open = false; }}>Delete</Button>
|
|
1305
|
+
{/snippet}
|
|
1306
|
+
</Modal>
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
**Props:**
|
|
1310
|
+
- `open?: boolean` (bindable) - Modal open state
|
|
1311
|
+
- `title?: string` - Modal title text
|
|
1312
|
+
- `iconBefore?: Snippet` - Optional icon displayed before title
|
|
1313
|
+
- `closeOnEscape?: boolean` - Close modal on Escape key (default: true)
|
|
1314
|
+
- `closeOnBackdrop?: boolean` - Close modal on backdrop click (default: true)
|
|
1315
|
+
- `footer?: Snippet` - Optional footer content
|
|
1316
|
+
- `children?: Snippet` - Main modal content
|
|
1317
|
+
|
|
1318
|
+
**Accessibility Features:**
|
|
1319
|
+
- Focus trap: Tab/Shift+Tab cycles within modal
|
|
1320
|
+
- Escape key: Closes modal (if `closeOnEscape=true`)
|
|
1321
|
+
- Auto-focus: First interactive element focused on open
|
|
1322
|
+
- Focus restoration: Returns focus to trigger element on close
|
|
1323
|
+
- ARIA: `role="dialog"`, `aria-modal="true"`, `aria-labelledby`
|
|
1324
|
+
- Screen readers: Modal title and content announced
|
|
1325
|
+
|
|
1326
|
+
**Alert** - Alert message with variants
|
|
1327
|
+
|
|
1328
|
+
```svelte
|
|
1329
|
+
<script>
|
|
1330
|
+
import { Alert } from '@mrintel/villain-ui';
|
|
1331
|
+
</script>
|
|
1332
|
+
|
|
1333
|
+
<Alert variant="success" title="Success">
|
|
1334
|
+
Operation completed successfully!
|
|
1335
|
+
</Alert>
|
|
1336
|
+
|
|
1337
|
+
<Alert variant="warning" title="Warning">
|
|
1338
|
+
Please review your changes.
|
|
1339
|
+
</Alert>
|
|
1340
|
+
|
|
1341
|
+
<Alert variant="error" title="Error">
|
|
1342
|
+
An error occurred.
|
|
1343
|
+
</Alert>
|
|
1344
|
+
|
|
1345
|
+
<!-- With custom icon -->
|
|
1346
|
+
<Alert variant="info" title="Custom Icon">
|
|
1347
|
+
{#snippet iconBefore()}
|
|
1348
|
+
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
|
1349
|
+
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/>
|
|
1350
|
+
<path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/>
|
|
1351
|
+
</svg>
|
|
1352
|
+
{/snippet}
|
|
1353
|
+
This alert uses a custom icon snippet.
|
|
1354
|
+
</Alert>
|
|
1355
|
+
```
|
|
1356
|
+
|
|
1357
|
+
**Props:**
|
|
1358
|
+
- `variant?: 'success' | 'warning' | 'error' | 'info'` - Alert style variant
|
|
1359
|
+
- `title: string` - Alert title text
|
|
1360
|
+
- `iconBefore?: Snippet` - Optional custom icon (overrides default variant icon)
|
|
1361
|
+
- `dismissible?: boolean` - Show close button (default: false)
|
|
1362
|
+
- `onclose?: () => void` - Callback when alert is dismissed
|
|
1363
|
+
- `children?: Snippet` - Alert content
|
|
1364
|
+
|
|
1365
|
+
**Accessibility Features:**
|
|
1366
|
+
- ARIA: `role="status"` (info/success) or `role="alert"` (warning/error)
|
|
1367
|
+
- `aria-live`: "polite" (info/success/warning) or "assertive" (error)
|
|
1368
|
+
- Dismissible: Close button has `aria-label="Dismiss alert"`
|
|
1369
|
+
- Screen readers: Alert content announced based on severity
|
|
1370
|
+
|
|
1371
|
+
**Spinner** - Loading spinner
|
|
1372
|
+
|
|
1373
|
+
```svelte
|
|
1374
|
+
<script>
|
|
1375
|
+
import { Spinner } from '@mrintel/villain-ui';
|
|
1376
|
+
</script>
|
|
1377
|
+
|
|
1378
|
+
<Spinner size="lg" />
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
**Tooltip** - Hover tooltip
|
|
1382
|
+
|
|
1383
|
+
```svelte
|
|
1384
|
+
<script>
|
|
1385
|
+
import { Tooltip } from '@mrintel/villain-ui';
|
|
1386
|
+
</script>
|
|
1387
|
+
|
|
1388
|
+
<Tooltip content="This is a helpful tip" position="top">
|
|
1389
|
+
<Button>Hover me</Button>
|
|
1390
|
+
</Tooltip>
|
|
1391
|
+
```
|
|
1392
|
+
|
|
1393
|
+
**ProgressBar** - Progress indicator
|
|
1394
|
+
|
|
1395
|
+
```svelte
|
|
1396
|
+
<script>
|
|
1397
|
+
import { ProgressBar } from '@mrintel/villain-ui';
|
|
1398
|
+
|
|
1399
|
+
let progress = $state(45);
|
|
1400
|
+
</script>
|
|
1401
|
+
|
|
1402
|
+
<ProgressBar value={progress} max={100} showLabel />
|
|
1403
|
+
```
|
|
1404
|
+
|
|
1405
|
+
**SkeletonLoader** - Content loading placeholder
|
|
1406
|
+
|
|
1407
|
+
```svelte
|
|
1408
|
+
<script>
|
|
1409
|
+
import { SkeletonLoader } from '@mrintel/villain-ui';
|
|
1410
|
+
</script>
|
|
1411
|
+
|
|
1412
|
+
<SkeletonLoader variant="text" count={3} />
|
|
1413
|
+
<SkeletonLoader variant="circle" width="60px" height="60px" />
|
|
1414
|
+
<SkeletonLoader variant="rectangle" width="100%" height="200px" />
|
|
1415
|
+
```
|
|
1416
|
+
|
|
1417
|
+
**Toast** - Notification toast
|
|
1418
|
+
|
|
1419
|
+
```svelte
|
|
1420
|
+
<script>
|
|
1421
|
+
import { Toast } from '@mrintel/villain-ui';
|
|
1422
|
+
|
|
1423
|
+
let show = $state(false);
|
|
1424
|
+
</script>
|
|
1425
|
+
|
|
1426
|
+
<Toast bind:show variant="success" duration={3000}>
|
|
1427
|
+
Changes saved successfully!
|
|
1428
|
+
</Toast>
|
|
1429
|
+
```
|
|
1430
|
+
|
|
1431
|
+
**Icon Examples:**
|
|
1432
|
+
```svelte
|
|
1433
|
+
<script>
|
|
1434
|
+
import { Toast } from '@mrintel/villain-ui';
|
|
1435
|
+
import { CheckCircleIcon, XCircleIcon, InformationCircleIcon } from 'your-icon-library';
|
|
1436
|
+
|
|
1437
|
+
let showSuccess = $state(false);
|
|
1438
|
+
let showError = $state(false);
|
|
1439
|
+
let showInfo = $state(false);
|
|
1440
|
+
</script>
|
|
1441
|
+
|
|
1442
|
+
<!-- Success toast with custom icon -->
|
|
1443
|
+
<Toast bind:show={showSuccess} variant="success" duration={3000}>
|
|
1444
|
+
{#snippet iconBefore()}
|
|
1445
|
+
<CheckCircleIcon class="w-5 h-5" />
|
|
1446
|
+
{/snippet}
|
|
1447
|
+
Changes saved successfully!
|
|
1448
|
+
</Toast>
|
|
1449
|
+
|
|
1450
|
+
<!-- Error toast with custom icon -->
|
|
1451
|
+
<Toast bind:show={showError} variant="error" duration={5000}>
|
|
1452
|
+
{#snippet iconBefore()}
|
|
1453
|
+
<XCircleIcon class="w-5 h-5" />
|
|
1454
|
+
{/snippet}
|
|
1455
|
+
Failed to save changes. Please try again.
|
|
1456
|
+
</Toast>
|
|
1457
|
+
|
|
1458
|
+
<!-- Info toast with custom icon -->
|
|
1459
|
+
<Toast bind:show={showInfo} variant="info">
|
|
1460
|
+
{#snippet iconBefore()}
|
|
1461
|
+
<InformationCircleIcon class="w-5 h-5" />
|
|
1462
|
+
{/snippet}
|
|
1463
|
+
New features available! Check them out.
|
|
1464
|
+
</Toast>
|
|
1465
|
+
```
|
|
1466
|
+
|
|
1467
|
+
**Drawer** - Slide-out drawer panel
|
|
1468
|
+
|
|
1469
|
+
```svelte
|
|
1470
|
+
<script>
|
|
1471
|
+
import { Drawer, Button } from '@mrintel/villain-ui';
|
|
1472
|
+
|
|
1473
|
+
let open = $state(false);
|
|
1474
|
+
</script>
|
|
1475
|
+
|
|
1476
|
+
<Button onclick={() => open = true}>Open Drawer</Button>
|
|
1477
|
+
|
|
1478
|
+
<Drawer bind:open position="right">
|
|
1479
|
+
<h2>Drawer Content</h2>
|
|
1480
|
+
<p>This slides in from the side.</p>
|
|
1481
|
+
</Drawer>
|
|
1482
|
+
```
|
|
1483
|
+
|
|
1484
|
+
**Icon Example:**
|
|
1485
|
+
```svelte
|
|
1486
|
+
<script>
|
|
1487
|
+
import { Drawer, Button } from '@mrintel/villain-ui';
|
|
1488
|
+
import { Cog6ToothIcon } from 'your-icon-library';
|
|
1489
|
+
|
|
1490
|
+
let open = $state(false);
|
|
1491
|
+
</script>
|
|
1492
|
+
|
|
1493
|
+
<Button onclick={() => open = true}>Open Settings</Button>
|
|
1494
|
+
|
|
1495
|
+
<Drawer bind:open position="right" title="Settings">
|
|
1496
|
+
{#snippet iconBefore()}
|
|
1497
|
+
<Cog6ToothIcon class="w-6 h-6" />
|
|
1498
|
+
{/snippet}
|
|
1499
|
+
|
|
1500
|
+
<div class="space-y-4">
|
|
1501
|
+
<h3>Application Settings</h3>
|
|
1502
|
+
<p>Configure your preferences here.</p>
|
|
1503
|
+
<!-- Settings content -->
|
|
1504
|
+
</div>
|
|
1505
|
+
</Drawer>
|
|
1506
|
+
```
|
|
1507
|
+
|
|
1508
|
+
**Props:**
|
|
1509
|
+
- `open?: boolean` (bindable) - Drawer open state
|
|
1510
|
+
- `side?: 'left' | 'right' | 'top' | 'bottom'` - Drawer slide-in position (default: 'right')
|
|
1511
|
+
- `title?: string` - Drawer title text
|
|
1512
|
+
- `iconBefore?: Snippet` - Optional icon displayed before title
|
|
1513
|
+
- `closeOnEscape?: boolean` - Close drawer on Escape key (default: true)
|
|
1514
|
+
- `closeOnBackdrop?: boolean` - Close drawer on backdrop click (default: true)
|
|
1515
|
+
- `children?: Snippet` - Main drawer content
|
|
1516
|
+
|
|
1517
|
+
**Accessibility Features:**
|
|
1518
|
+
- Focus trap: Tab/Shift+Tab cycles within drawer
|
|
1519
|
+
- Escape key: Closes drawer (if `closeOnEscape=true`)
|
|
1520
|
+
- Auto-focus: First interactive element focused on open
|
|
1521
|
+
- Focus restoration: Returns focus to trigger element on close
|
|
1522
|
+
- ARIA: `role="dialog"`, `aria-modal="true"`, `aria-labelledby`
|
|
1523
|
+
- Screen readers: Drawer title and content announced
|
|
1524
|
+
|
|
1525
|
+
**Popover** - Popover content
|
|
1526
|
+
|
|
1527
|
+
```svelte
|
|
1528
|
+
<script>
|
|
1529
|
+
import { Popover } from '@mrintel/villain-ui';
|
|
1530
|
+
</script>
|
|
1531
|
+
|
|
1532
|
+
<Popover>
|
|
1533
|
+
{#snippet trigger()}
|
|
1534
|
+
<Button>Click me</Button>
|
|
1535
|
+
{/snippet}
|
|
1536
|
+
|
|
1537
|
+
{#snippet content()}
|
|
1538
|
+
<div>Popover content here</div>
|
|
1539
|
+
{/snippet}
|
|
1540
|
+
</Popover>
|
|
1541
|
+
```
|
|
1542
|
+
|
|
1543
|
+
**Dropdown** - Generic dropdown container
|
|
1544
|
+
|
|
1545
|
+
```svelte
|
|
1546
|
+
<script>
|
|
1547
|
+
import { Dropdown } from '@mrintel/villain-ui';
|
|
1548
|
+
</script>
|
|
1549
|
+
|
|
1550
|
+
<Dropdown trigger="Select Option">
|
|
1551
|
+
<a href="#">Option 1</a>
|
|
1552
|
+
<a href="#">Option 2</a>
|
|
1553
|
+
<a href="#">Option 3</a>
|
|
1554
|
+
</Dropdown>
|
|
1555
|
+
```
|
|
1556
|
+
|
|
1557
|
+
**CommandPalette** - Command palette with fuzzy search
|
|
1558
|
+
|
|
1559
|
+
```svelte
|
|
1560
|
+
<script>
|
|
1561
|
+
import { CommandPalette } from '@mrintel/villain-ui';
|
|
1562
|
+
|
|
1563
|
+
let open = $state(false);
|
|
1564
|
+
const commands = [
|
|
1565
|
+
{ id: '1', label: 'New File', onSelect: () => console.log('New File') },
|
|
1566
|
+
{ id: '2', label: 'Open Settings', onSelect: () => console.log('Settings') },
|
|
1567
|
+
{ id: '3', label: 'Search Files', onSelect: () => console.log('Search') }
|
|
1568
|
+
];
|
|
1569
|
+
</script>
|
|
1570
|
+
|
|
1571
|
+
<CommandPalette bind:open {commands} placeholder="Search commands..." />
|
|
1572
|
+
```
|
|
1573
|
+
|
|
1574
|
+
**Keyboard Navigation:**
|
|
1575
|
+
- Arrow Down/Up: Navigate command list
|
|
1576
|
+
- Enter: Execute selected command
|
|
1577
|
+
- Escape: Close palette
|
|
1578
|
+
- Type to search: Fuzzy search filters commands in real-time
|
|
1579
|
+
|
|
1580
|
+
**Accessibility Features:**
|
|
1581
|
+
- ARIA: `role="combobox"`, `aria-expanded`, `aria-haspopup`, `aria-controls`, `aria-activedescendant`
|
|
1582
|
+
- Auto-focus: Search input focused on open
|
|
1583
|
+
- Keyboard navigation: Arrow keys navigate filtered results, Enter executes command
|
|
1584
|
+
- Screen readers: Command count and selected command announced via aria-activedescendant
|
|
1585
|
+
```
|
|
1586
|
+
|
|
1587
|
+
### Typography
|
|
1588
|
+
|
|
1589
|
+
**Heading** - Semantic heading levels
|
|
1590
|
+
|
|
1591
|
+
```svelte
|
|
1592
|
+
<script>
|
|
1593
|
+
import { Heading } from '@mrintel/villain-ui';
|
|
1594
|
+
</script>
|
|
1595
|
+
|
|
1596
|
+
<Heading level={1}>Main Title</Heading>
|
|
1597
|
+
<Heading level={2}>Section Title</Heading>
|
|
1598
|
+
<Heading level={3}>Subsection</Heading>
|
|
1599
|
+
|
|
1600
|
+
<!-- Gradient variant for hero and section titles -->
|
|
1601
|
+
<Heading level={1} variant="gradient">Hero Title with Gradient</Heading>
|
|
1602
|
+
<Heading level={2} variant="gradient" glow>Section with Gradient and Glow</Heading>
|
|
1603
|
+
```
|
|
1604
|
+
|
|
1605
|
+
**Props:**
|
|
1606
|
+
- `level: 1 | 2 | 3 | 4 | 5 | 6` - Semantic heading level (required)
|
|
1607
|
+
- `variant?: 'gradient'` - Apply gradient styling using the `.text-gradient` utility
|
|
1608
|
+
- `glow?: boolean` - Add text glow effect (works well with gradient variant)
|
|
1609
|
+
- `class?: string` - Additional CSS classes
|
|
1610
|
+
|
|
1611
|
+
**Note:** The `gradient` variant applies the `.text-gradient` utility class, creating a purple-to-soft-purple gradient effect. Combine with `glow` for enhanced visual impact on hero sections.
|
|
1612
|
+
|
|
1613
|
+
**Text** - Text with variants
|
|
1614
|
+
|
|
1615
|
+
```svelte
|
|
1616
|
+
<script>
|
|
1617
|
+
import { Text } from '@mrintel/villain-ui';
|
|
1618
|
+
</script>
|
|
1619
|
+
|
|
1620
|
+
<Text variant="body">Regular body text</Text>
|
|
1621
|
+
<Text variant="caption">Caption text</Text>
|
|
1622
|
+
<Text variant="muted">Muted text</Text>
|
|
1623
|
+
```
|
|
1624
|
+
|
|
1625
|
+
**Code** - Inline code
|
|
1626
|
+
|
|
1627
|
+
```svelte
|
|
1628
|
+
<script>
|
|
1629
|
+
import { Code } from '@mrintel/villain-ui';
|
|
1630
|
+
</script>
|
|
1631
|
+
|
|
1632
|
+
<p>Install with <Code>npm install @mrintel/villain-ui</Code></p>
|
|
1633
|
+
```
|
|
1634
|
+
|
|
1635
|
+
### Data Display
|
|
1636
|
+
|
|
1637
|
+
**Table** - Data table with sorting, filtering, and custom rendering
|
|
1638
|
+
|
|
1639
|
+
```svelte
|
|
1640
|
+
<script>
|
|
1641
|
+
import { Table, type TableColumn, type SortDirection } from '@mrintel/villain-ui';
|
|
1642
|
+
|
|
1643
|
+
const columns: TableColumn[] = [
|
|
1644
|
+
{ key: 'name', label: 'Name', sortable: true },
|
|
1645
|
+
{ key: 'email', label: 'Email', sortable: true },
|
|
1646
|
+
{ key: 'role', label: 'Role', align: 'center' },
|
|
1647
|
+
{
|
|
1648
|
+
key: 'status',
|
|
1649
|
+
label: 'Status',
|
|
1650
|
+
render: (value) => `<span class="badge-${value}">${value}</span>`
|
|
1651
|
+
}
|
|
1652
|
+
];
|
|
1653
|
+
|
|
1654
|
+
let allData = [
|
|
1655
|
+
{ name: 'John Doe', email: 'john@example.com', role: 'Admin', status: 'active' },
|
|
1656
|
+
{ name: 'Jane Smith', email: 'jane@example.com', role: 'User', status: 'active' },
|
|
1657
|
+
{ name: 'Bob Johnson', email: 'bob@example.com', role: 'User', status: 'inactive' }
|
|
1658
|
+
];
|
|
1659
|
+
|
|
1660
|
+
let data = $state([...allData]);
|
|
1661
|
+
let searchQuery = $state('');
|
|
1662
|
+
|
|
1663
|
+
// User-defined filter function
|
|
1664
|
+
const filterFn = (row: Record<string, any>) => {
|
|
1665
|
+
if (!searchQuery) return true;
|
|
1666
|
+
const query = searchQuery.toLowerCase();
|
|
1667
|
+
return (
|
|
1668
|
+
row.name.toLowerCase().includes(query) ||
|
|
1669
|
+
row.email.toLowerCase().includes(query) ||
|
|
1670
|
+
row.role.toLowerCase().includes(query)
|
|
1671
|
+
);
|
|
1672
|
+
};
|
|
1673
|
+
|
|
1674
|
+
// User-defined sort handler
|
|
1675
|
+
function handleSort(columnKey: string, direction: SortDirection) {
|
|
1676
|
+
if (!direction) {
|
|
1677
|
+
data = [...allData];
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
data = [...data].sort((a, b) => {
|
|
1682
|
+
const aVal = a[columnKey];
|
|
1683
|
+
const bVal = b[columnKey];
|
|
1684
|
+
const modifier = direction === 'asc' ? 1 : -1;
|
|
1685
|
+
return aVal > bVal ? modifier : aVal < bVal ? -modifier : 0;
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
// User-defined row click handler
|
|
1690
|
+
function handleRowClick(row: Record<string, any>) {
|
|
1691
|
+
console.log('Clicked row:', row);
|
|
1692
|
+
}
|
|
1693
|
+
</script>
|
|
1694
|
+
|
|
1695
|
+
<input type="text" bind:value={searchQuery} placeholder="Search..." />
|
|
1696
|
+
|
|
1697
|
+
<Table
|
|
1698
|
+
{columns}
|
|
1699
|
+
{data}
|
|
1700
|
+
{filterFn}
|
|
1701
|
+
onSort={handleSort}
|
|
1702
|
+
onRowClick={handleRowClick}
|
|
1703
|
+
loading={isLoading}
|
|
1704
|
+
loadingMessage="Loading data..."
|
|
1705
|
+
emptyMessage="No results found"
|
|
1706
|
+
stickyHeader
|
|
1707
|
+
hoverable
|
|
1708
|
+
striped
|
|
1709
|
+
/>
|
|
1710
|
+
|
|
1711
|
+
<!-- Custom empty state -->
|
|
1712
|
+
<Table {columns} {data} striped>
|
|
1713
|
+
{#snippet emptyState()}
|
|
1714
|
+
<div>
|
|
1715
|
+
<h3>No data yet</h3>
|
|
1716
|
+
<p>Add your first item to get started</p>
|
|
1717
|
+
<button>Add Item</button>
|
|
1718
|
+
</div>
|
|
1719
|
+
{/snippet}
|
|
1720
|
+
</Table>
|
|
1721
|
+
|
|
1722
|
+
<!-- Manual markup mode still supported -->
|
|
1723
|
+
<Table striped hoverable>
|
|
1724
|
+
<thead>
|
|
1725
|
+
<tr>
|
|
1726
|
+
<th>Name</th>
|
|
1727
|
+
<th>Email</th>
|
|
1728
|
+
</tr>
|
|
1729
|
+
</thead>
|
|
1730
|
+
<tbody>
|
|
1731
|
+
<tr>
|
|
1732
|
+
<td>John</td>
|
|
1733
|
+
<td>john@example.com</td>
|
|
1734
|
+
</tr>
|
|
1735
|
+
</tbody>
|
|
1736
|
+
</Table>
|
|
1737
|
+
|
|
1738
|
+
<!-- Table Features -->
|
|
1739
|
+
Props:
|
|
1740
|
+
- filterFn: (row) => boolean - User-defined filter/search function
|
|
1741
|
+
- onSort: (columnKey, direction) => void - Sort callback
|
|
1742
|
+
- onRowClick: (row) => void - Row click callback
|
|
1743
|
+
- loading: boolean - Show loading spinner
|
|
1744
|
+
- loadingMessage: string - Custom loading text
|
|
1745
|
+
- emptyMessage: string - Text when no data
|
|
1746
|
+
- emptyState: Snippet - Custom empty state component
|
|
1747
|
+
- stickyHeader: boolean - Sticky table header on scroll
|
|
1748
|
+
- striped/hoverable/compact: Visual variants
|
|
1749
|
+
```
|
|
1750
|
+
|
|
1751
|
+
**Pagination** - Page navigation
|
|
1752
|
+
|
|
1753
|
+
```svelte
|
|
1754
|
+
<script>
|
|
1755
|
+
import { Pagination } from '@mrintel/villain-ui';
|
|
1756
|
+
|
|
1757
|
+
let currentPage = $state(1);
|
|
1758
|
+
const totalPages = 10;
|
|
1759
|
+
</script>
|
|
1760
|
+
|
|
1761
|
+
<Pagination bind:currentPage {totalPages} />
|
|
1762
|
+
```
|
|
1763
|
+
|
|
1764
|
+
**Icon Examples:**
|
|
1765
|
+
```svelte
|
|
1766
|
+
<script>
|
|
1767
|
+
import { Pagination } from '@mrintel/villain-ui';
|
|
1768
|
+
import { ChevronLeftIcon, ChevronRightIcon } from 'your-icon-library';
|
|
1769
|
+
|
|
1770
|
+
let currentPage = $state(1);
|
|
1771
|
+
</script>
|
|
1772
|
+
|
|
1773
|
+
<!-- Pagination with custom prev/next icons -->
|
|
1774
|
+
<Pagination
|
|
1775
|
+
{currentPage}
|
|
1776
|
+
totalPages={10}
|
|
1777
|
+
onPageChange={(page) => currentPage = page}
|
|
1778
|
+
>
|
|
1779
|
+
{#snippet prevIcon()}
|
|
1780
|
+
<ChevronLeftIcon class="w-5 h-5" />
|
|
1781
|
+
{/snippet}
|
|
1782
|
+
{#snippet nextIcon()}
|
|
1783
|
+
<ChevronRightIcon class="w-5 h-5" />
|
|
1784
|
+
{/snippet}
|
|
1785
|
+
</Pagination>
|
|
1786
|
+
|
|
1787
|
+
<!-- Icon-only pagination (no "Previous"/"Next" text) -->
|
|
1788
|
+
<Pagination
|
|
1789
|
+
{currentPage}
|
|
1790
|
+
totalPages={10}
|
|
1791
|
+
showLabels={false}
|
|
1792
|
+
onPageChange={(page) => currentPage = page}
|
|
1793
|
+
>
|
|
1794
|
+
{#snippet prevIcon()}
|
|
1795
|
+
<ChevronLeftIcon class="w-5 h-5" />
|
|
1796
|
+
{/snippet}
|
|
1797
|
+
{#snippet nextIcon()}
|
|
1798
|
+
<ChevronRightIcon class="w-5 h-5" />
|
|
1799
|
+
{/snippet}
|
|
1800
|
+
</Pagination>
|
|
1801
|
+
```
|
|
1802
|
+
|
|
1803
|
+
**Badge** - Status badge
|
|
1804
|
+
|
|
1805
|
+
```svelte
|
|
1806
|
+
<script>
|
|
1807
|
+
import { Badge } from '@mrintel/villain-ui';
|
|
1808
|
+
</script>
|
|
1809
|
+
|
|
1810
|
+
<!-- Basic variants -->
|
|
1811
|
+
<Badge variant="success">Active</Badge>
|
|
1812
|
+
<Badge variant="warning">Pending</Badge>
|
|
1813
|
+
<Badge variant="error">Error</Badge>
|
|
1814
|
+
<Badge variant="accent">Accent</Badge>
|
|
1815
|
+
|
|
1816
|
+
<!-- Feature variant - purple accent styling -->
|
|
1817
|
+
<Badge variant="feature">New Feature</Badge>
|
|
1818
|
+
<Badge variant="feature">Beta</Badge>
|
|
1819
|
+
|
|
1820
|
+
<!-- With hover effects -->
|
|
1821
|
+
<Badge variant="feature" hover>Hoverable Badge</Badge>
|
|
1822
|
+
|
|
1823
|
+
<!-- With persistent glow (can be toggled dynamically for blinking) -->
|
|
1824
|
+
<Badge variant="accent" glow>Glowing Badge</Badge>
|
|
1825
|
+
|
|
1826
|
+
<!-- With icon -->
|
|
1827
|
+
<Badge variant="accent" size="md" hover>
|
|
1828
|
+
{#snippet icon()}
|
|
1829
|
+
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
|
1830
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
|
|
1831
|
+
</svg>
|
|
1832
|
+
{/snippet}
|
|
1833
|
+
Verified
|
|
1834
|
+
</Badge>
|
|
1835
|
+
```
|
|
1836
|
+
|
|
1837
|
+
**Props:**
|
|
1838
|
+
- `variant?: 'default' | 'success' | 'warning' | 'error' | 'accent' | 'feature'` - Badge style (default: 'default')
|
|
1839
|
+
- `size?: 'sm' | 'md'` - Badge size (default: 'md')
|
|
1840
|
+
- `icon?: Snippet` - Optional icon content
|
|
1841
|
+
- `hover?: boolean` - Enable hover effects (darkened background + glow for accent/feature variants) (default: false)
|
|
1842
|
+
- `glow?: boolean` - Apply accent glow effect, can be toggled dynamically for blinking animations (default: false)
|
|
1843
|
+
- `class?: string` - Additional CSS classes
|
|
1844
|
+
|
|
1845
|
+
**Note:** The `feature` variant creates a purple-accented badge perfect for highlighting new features or promotional content. Use `hover={true}` for interactive badges and `glow={true}` for persistent or animated glow effects.
|
|
1846
|
+
|
|
1847
|
+
**Tag** - Removable tag
|
|
1848
|
+
|
|
1849
|
+
```svelte
|
|
1850
|
+
<script>
|
|
1851
|
+
import { Tag } from '@mrintel/villain-ui';
|
|
1852
|
+
</script>
|
|
1853
|
+
|
|
1854
|
+
<Tag onRemove={() => console.log('Removed')}>JavaScript</Tag>
|
|
1855
|
+
<Tag>TypeScript</Tag>
|
|
1856
|
+
```
|
|
1857
|
+
|
|
1858
|
+
**Icon Examples:**
|
|
1859
|
+
```svelte
|
|
1860
|
+
<!-- Tag with icon -->
|
|
1861
|
+
<Tag variant="accent">
|
|
1862
|
+
{#snippet icon()}
|
|
1863
|
+
<StarIcon class="w-4 h-4" />
|
|
1864
|
+
{/snippet}
|
|
1865
|
+
Featured
|
|
1866
|
+
</Tag>
|
|
1867
|
+
|
|
1868
|
+
<!-- Dismissible tag with icon -->
|
|
1869
|
+
<Tag dismissible ondismiss={() => console.log('Removed')}>
|
|
1870
|
+
{#snippet icon()}
|
|
1871
|
+
<TagIcon class="w-4 h-4" />
|
|
1872
|
+
{/snippet}
|
|
1873
|
+
JavaScript
|
|
1874
|
+
</Tag>
|
|
1875
|
+
|
|
1876
|
+
<!-- Multiple tags with different icons -->
|
|
1877
|
+
<div class="flex gap-2">
|
|
1878
|
+
<Tag>
|
|
1879
|
+
{#snippet icon()}
|
|
1880
|
+
<CheckCircleIcon class="w-4 h-4" />
|
|
1881
|
+
{/snippet}
|
|
1882
|
+
Verified
|
|
1883
|
+
</Tag>
|
|
1884
|
+
<Tag variant="accent">
|
|
1885
|
+
{#snippet icon()}
|
|
1886
|
+
<FireIcon class="w-4 h-4" />
|
|
1887
|
+
{/snippet}
|
|
1888
|
+
Trending
|
|
1889
|
+
</Tag>
|
|
1890
|
+
</div>
|
|
1891
|
+
```
|
|
1892
|
+
|
|
1893
|
+
**List** - Styled list
|
|
1894
|
+
|
|
1895
|
+
```svelte
|
|
1896
|
+
<script>
|
|
1897
|
+
import { List } from '@mrintel/villain-ui';
|
|
1898
|
+
|
|
1899
|
+
const items = ['Item 1', 'Item 2', 'Item 3'];
|
|
1900
|
+
</script>
|
|
1901
|
+
|
|
1902
|
+
<List {items} variant="ordered" />
|
|
1903
|
+
```
|
|
1904
|
+
|
|
1905
|
+
**Icon Example:**
|
|
1906
|
+
```svelte
|
|
1907
|
+
<script>
|
|
1908
|
+
import { List } from '@mrintel/villain-ui';
|
|
1909
|
+
import { CheckIcon, XIcon, ClockIcon } from 'your-icon-library';
|
|
1910
|
+
|
|
1911
|
+
const items = [
|
|
1912
|
+
{
|
|
1913
|
+
label: 'Task completed',
|
|
1914
|
+
icon: () => `{@render CheckIcon({ class: 'w-5 h-5 text-success' })}`
|
|
1915
|
+
},
|
|
1916
|
+
{
|
|
1917
|
+
label: 'Task pending',
|
|
1918
|
+
icon: () => `{@render ClockIcon({ class: 'w-5 h-5 text-warning' })}`
|
|
1919
|
+
},
|
|
1920
|
+
{
|
|
1921
|
+
label: 'Task failed',
|
|
1922
|
+
icon: () => `{@render XIcon({ class: 'w-5 h-5 text-error' })}`
|
|
1923
|
+
}
|
|
1924
|
+
];
|
|
1925
|
+
</script>
|
|
1926
|
+
|
|
1927
|
+
<List {items} variant="unordered" />
|
|
1928
|
+
|
|
1929
|
+
<!-- Or with custom rendering -->
|
|
1930
|
+
<List variant="unordered">
|
|
1931
|
+
{#each items as item}
|
|
1932
|
+
<li class="flex items-center gap-3">
|
|
1933
|
+
{#if item.icon}
|
|
1934
|
+
<span class="inline-flex items-center justify-center">
|
|
1935
|
+
{@render item.icon()}
|
|
1936
|
+
</span>
|
|
1937
|
+
{/if}
|
|
1938
|
+
{item.label}
|
|
1939
|
+
</li>
|
|
1940
|
+
{/each}
|
|
1941
|
+
</List>
|
|
1942
|
+
```
|
|
1943
|
+
|
|
1944
|
+
**Avatar** - User avatar
|
|
1945
|
+
|
|
1946
|
+
```svelte
|
|
1947
|
+
<script>
|
|
1948
|
+
import { Avatar } from '@mrintel/villain-ui';
|
|
1949
|
+
</script>
|
|
1950
|
+
|
|
1951
|
+
<Avatar src="/avatar.jpg" alt="User" size="md" />
|
|
1952
|
+
<Avatar initials="JD" size="lg" />
|
|
1953
|
+
```
|
|
1954
|
+
|
|
1955
|
+
**CodeBlock** - Presentational component for displaying syntax-highlighted code
|
|
1956
|
+
|
|
1957
|
+
A luxury-styled code display component that provides layout, styling, and optional features like line numbers, filename headers, and line highlighting. Consumers control syntax highlighting by providing pre-highlighted HTML via the default slot.
|
|
1958
|
+
|
|
1959
|
+
**Key Features:**
|
|
1960
|
+
- Glass morphism aesthetic with custom scrollbars
|
|
1961
|
+
- **Built-in copy button** with visual feedback
|
|
1962
|
+
- Optional line numbers with highlighting support
|
|
1963
|
+
- Optional filename header display
|
|
1964
|
+
- Consumers choose their preferred syntax highlighter
|
|
1965
|
+
- Responsive overflow handling
|
|
1966
|
+
|
|
1967
|
+
**Props:**
|
|
1968
|
+
|
|
1969
|
+
| Prop | Type | Default | Description |
|
|
1970
|
+
|------|------|---------|-------------|
|
|
1971
|
+
| `filename` | `string` | `undefined` | Optional filename to display in the header |
|
|
1972
|
+
| `showLineNumbers` | `boolean` | `false` | Whether to show line numbers in the gutter |
|
|
1973
|
+
| `lineCount` | `number` | `0` | Total number of lines (required when `showLineNumbers` is `true`) |
|
|
1974
|
+
| `highlightLines` | `number[]` | `[]` | Array of 1-indexed line numbers to highlight in the gutter |
|
|
1975
|
+
| `showCopy` | `boolean` | `true` | Whether to show the copy button |
|
|
1976
|
+
| `code` | `string` | `undefined` | Raw code text for copying (if not provided, extracts from rendered content) |
|
|
1977
|
+
|
|
1978
|
+
**Important Notes:**
|
|
1979
|
+
- **Security Warning:** Consumers must sanitize HTML before passing to CodeBlock to prevent XSS attacks. Use trusted syntax highlighters (Shiki, Prism, Highlight.js) and avoid user-generated content without sanitization.
|
|
1980
|
+
- Apply `.line` class to each code line and `.highlighted` class to highlighted lines for consistent styling
|
|
1981
|
+
- Line numbers are 1-indexed (first line is 1, not 0)
|
|
1982
|
+
- When using `showLineNumbers`, provide the `lineCount` prop for proper rendering
|
|
1983
|
+
|
|
1984
|
+
**Basic Usage:**
|
|
1985
|
+
|
|
1986
|
+
```svelte
|
|
1987
|
+
<script>
|
|
1988
|
+
import { CodeBlock } from '@mrintel/villain-ui';
|
|
1989
|
+
|
|
1990
|
+
const code = `function hello() {
|
|
1991
|
+
console.log('Hello, world!');
|
|
1992
|
+
}`;
|
|
1993
|
+
|
|
1994
|
+
const highlightedCode = `<pre><code class="language-javascript">
|
|
1995
|
+
<span class="token keyword">function</span> <span class="token function">hello</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
|
|
1996
|
+
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Hello, world!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
|
1997
|
+
<span class="token punctuation">}</span>
|
|
1998
|
+
</code></pre>`;
|
|
1999
|
+
</script>
|
|
2000
|
+
|
|
2001
|
+
<!-- Copy button shown by default, uses raw code text -->
|
|
2002
|
+
<CodeBlock {code}>
|
|
2003
|
+
{@html highlightedCode}
|
|
2004
|
+
</CodeBlock>
|
|
2005
|
+
|
|
2006
|
+
<!-- Hide copy button if needed -->
|
|
2007
|
+
<CodeBlock showCopy={false}>
|
|
2008
|
+
{@html highlightedCode}
|
|
2009
|
+
</CodeBlock>
|
|
2010
|
+
```
|
|
2011
|
+
|
|
2012
|
+
**With Line Numbers:**
|
|
2013
|
+
|
|
2014
|
+
```svelte
|
|
2015
|
+
<script>
|
|
2016
|
+
import { CodeBlock } from '@mrintel/villain-ui';
|
|
2017
|
+
|
|
2018
|
+
const code = `function greet(name) {
|
|
2019
|
+
return \`Hello, \${name}!\`;
|
|
2020
|
+
}`;
|
|
2021
|
+
|
|
2022
|
+
const lineCount = code.split('\n').length;
|
|
2023
|
+
// Assume you have a highlighter that returns HTML
|
|
2024
|
+
const highlightedCode = yourHighlighter(code, 'javascript');
|
|
2025
|
+
</script>
|
|
2026
|
+
|
|
2027
|
+
<CodeBlock showLineNumbers {lineCount}>
|
|
2028
|
+
{@html highlightedCode}
|
|
2029
|
+
</CodeBlock>
|
|
2030
|
+
```
|
|
2031
|
+
|
|
2032
|
+
**With Highlighted Lines:**
|
|
2033
|
+
|
|
2034
|
+
```svelte
|
|
2035
|
+
<script>
|
|
2036
|
+
import { CodeBlock } from '@mrintel/villain-ui';
|
|
2037
|
+
|
|
2038
|
+
const code = `function calculate(a, b) {
|
|
2039
|
+
const sum = a + b;
|
|
2040
|
+
return sum * 2;
|
|
2041
|
+
}`;
|
|
2042
|
+
|
|
2043
|
+
const lineCount = 4;
|
|
2044
|
+
const highlightedCode = yourHighlighter(code, 'javascript');
|
|
2045
|
+
const highlightLines = [2]; // Highlight line 2
|
|
2046
|
+
</script>
|
|
2047
|
+
|
|
2048
|
+
<CodeBlock showLineNumbers {lineCount} {highlightLines}>
|
|
2049
|
+
{@html highlightedCode}
|
|
2050
|
+
</CodeBlock>
|
|
2051
|
+
```
|
|
2052
|
+
|
|
2053
|
+
**With Filename:**
|
|
2054
|
+
|
|
2055
|
+
```svelte
|
|
2056
|
+
<script>
|
|
2057
|
+
import { CodeBlock } from '@mrintel/villain-ui';
|
|
2058
|
+
|
|
2059
|
+
const code = `export function add(a: number, b: number): number {
|
|
2060
|
+
return a + b;
|
|
2061
|
+
}`;
|
|
2062
|
+
|
|
2063
|
+
const highlightedCode = yourHighlighter(code, 'typescript');
|
|
2064
|
+
</script>
|
|
2065
|
+
|
|
2066
|
+
<CodeBlock filename="utils.ts">
|
|
2067
|
+
{@html highlightedCode}
|
|
2068
|
+
</CodeBlock>
|
|
2069
|
+
```
|
|
2070
|
+
|
|
2071
|
+
**Integration with Shiki:**
|
|
2072
|
+
|
|
2073
|
+
```svelte
|
|
2074
|
+
<script>
|
|
2075
|
+
import { CodeBlock } from '@mrintel/villain-ui';
|
|
2076
|
+
import { codeToHtml } from 'shiki';
|
|
2077
|
+
import { onMount } from 'svelte';
|
|
2078
|
+
|
|
2079
|
+
const code = `function fibonacci(n) {
|
|
2080
|
+
if (n <= 1) return n;
|
|
2081
|
+
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
2082
|
+
}`;
|
|
2083
|
+
|
|
2084
|
+
let highlightedCode = $state('');
|
|
2085
|
+
let lineCount = $state(0);
|
|
2086
|
+
|
|
2087
|
+
onMount(async () => {
|
|
2088
|
+
highlightedCode = await codeToHtml(code, {
|
|
2089
|
+
lang: 'javascript',
|
|
2090
|
+
theme: 'github-dark'
|
|
2091
|
+
});
|
|
2092
|
+
lineCount = code.split('\n').length;
|
|
2093
|
+
});
|
|
2094
|
+
</script>
|
|
2095
|
+
|
|
2096
|
+
<CodeBlock filename="fibonacci.js" showLineNumbers {lineCount} highlightLines={[2, 3]}>
|
|
2097
|
+
{@html highlightedCode}
|
|
2098
|
+
</CodeBlock>
|
|
2099
|
+
```
|
|
2100
|
+
|
|
2101
|
+
**Integration with Prism.js:**
|
|
2102
|
+
|
|
2103
|
+
```svelte
|
|
2104
|
+
<script>
|
|
2105
|
+
import { CodeBlock } from '@mrintel/villain-ui';
|
|
2106
|
+
import Prism from 'prismjs';
|
|
2107
|
+
import 'prismjs/themes/prism-tomorrow.css';
|
|
2108
|
+
|
|
2109
|
+
const code = `const greeting = (name) => {
|
|
2110
|
+
console.log(\`Hello, \${name}!\`);
|
|
2111
|
+
};`;
|
|
2112
|
+
|
|
2113
|
+
const lineCount = code.split('\n').length;
|
|
2114
|
+
const highlightedCode = Prism.highlight(code, Prism.languages.javascript, 'javascript');
|
|
2115
|
+
</script>
|
|
2116
|
+
|
|
2117
|
+
<CodeBlock showLineNumbers {lineCount}>
|
|
2118
|
+
<pre><code class="language-javascript">{@html highlightedCode}</code></pre>
|
|
2119
|
+
</CodeBlock>
|
|
2120
|
+
```
|
|
2121
|
+
|
|
2122
|
+
**Integration with Highlight.js:**
|
|
2123
|
+
|
|
2124
|
+
```svelte
|
|
2125
|
+
<script>
|
|
2126
|
+
import { CodeBlock } from '@mrintel/villain-ui';
|
|
2127
|
+
import hljs from 'highlight.js';
|
|
2128
|
+
import 'highlight.js/styles/github-dark.css';
|
|
2129
|
+
|
|
2130
|
+
const code = `public class HelloWorld {
|
|
2131
|
+
public static void main(String[] args) {
|
|
2132
|
+
System.out.println("Hello, World!");
|
|
2133
|
+
}
|
|
2134
|
+
}`;
|
|
2135
|
+
|
|
2136
|
+
const lineCount = code.split('\n').length;
|
|
2137
|
+
const highlightedCode = hljs.highlight(code, { language: 'java' }).value;
|
|
2138
|
+
</script>
|
|
2139
|
+
|
|
2140
|
+
<CodeBlock filename="HelloWorld.java" showLineNumbers {lineCount}>
|
|
2141
|
+
<pre><code class="language-java">{@html highlightedCode}</code></pre>
|
|
2142
|
+
</CodeBlock>
|
|
2143
|
+
```
|
|
2144
|
+
|
|
2145
|
+
**Stat** - Statistic display
|
|
2146
|
+
|
|
2147
|
+
```svelte
|
|
2148
|
+
<script>
|
|
2149
|
+
import { Stat } from '@mrintel/villain-ui';
|
|
2150
|
+
</script>
|
|
2151
|
+
|
|
2152
|
+
<Stat label="Total Users" value="1,234" change="+12.5%" trend="up" />
|
|
2153
|
+
```
|
|
2154
|
+
|
|
2155
|
+
**Sparkline** - Lightweight micro trend visualizations
|
|
2156
|
+
|
|
2157
|
+
A zero-dependency SVG sparkline component for displaying micro trends in dashboards and data-rich interfaces. Perfect for showing quick visual trends without the overhead of a full charting library.
|
|
2158
|
+
|
|
2159
|
+
**Key Features:**
|
|
2160
|
+
- Pure SVG rendering (zero dependencies)
|
|
2161
|
+
- Fully themed with villain-ui CSS variables
|
|
2162
|
+
- Smooth draw-in animation with reduced motion support
|
|
2163
|
+
- Optional gradient fills and data point dots
|
|
2164
|
+
- Tiny bundle size (~2KB)
|
|
2165
|
+
- Flexible sizing and styling
|
|
2166
|
+
|
|
2167
|
+
**Props:**
|
|
2168
|
+
|
|
2169
|
+
| Prop | Type | Default | Description |
|
|
2170
|
+
|------|------|---------|-------------|
|
|
2171
|
+
| `data` | `number[]` | **required** | Array of numeric data points to plot |
|
|
2172
|
+
| `color` | `string` | `var(--color-accent-soft)` | Line stroke color (CSS color or variable) |
|
|
2173
|
+
| `height` | `number` | `40` | Chart height in pixels |
|
|
2174
|
+
| `width` | `number` | `200` | Chart width in pixels |
|
|
2175
|
+
| `showDots` | `boolean` | `false` | Show dots at each data point |
|
|
2176
|
+
| `showFill` | `boolean` | `false` | Show gradient fill below the line |
|
|
2177
|
+
| `strokeWidth` | `number` | `2` | Line thickness in pixels |
|
|
2178
|
+
| `animationDuration` | `number` | `500` | Animation duration in ms (0 to disable) |
|
|
2179
|
+
|
|
2180
|
+
**Basic Usage:**
|
|
2181
|
+
|
|
2182
|
+
```svelte
|
|
2183
|
+
<script>
|
|
2184
|
+
import { Sparkline } from '@mrintel/villain-ui';
|
|
2185
|
+
|
|
2186
|
+
const weeklyData = [120, 135, 128, 142, 155, 148, 160];
|
|
2187
|
+
</script>
|
|
2188
|
+
|
|
2189
|
+
<Sparkline data={weeklyData} />
|
|
2190
|
+
```
|
|
2191
|
+
|
|
2192
|
+
**With Fill and Dots:**
|
|
2193
|
+
|
|
2194
|
+
```svelte
|
|
2195
|
+
<script>
|
|
2196
|
+
import { Sparkline } from '@mrintel/villain-ui';
|
|
2197
|
+
|
|
2198
|
+
const trendData = [10, 15, 12, 18, 22, 19, 25];
|
|
2199
|
+
</script>
|
|
2200
|
+
|
|
2201
|
+
<Sparkline
|
|
2202
|
+
data={trendData}
|
|
2203
|
+
showFill={true}
|
|
2204
|
+
showDots={true}
|
|
2205
|
+
height={60}
|
|
2206
|
+
color="var(--color-success)"
|
|
2207
|
+
/>
|
|
2208
|
+
```
|
|
2209
|
+
|
|
2210
|
+
**Integrated with Stat Component (Gym Planner Example):**
|
|
2211
|
+
|
|
2212
|
+
```svelte
|
|
2213
|
+
<script>
|
|
2214
|
+
import { Stat, Sparkline } from '@mrintel/villain-ui';
|
|
2215
|
+
|
|
2216
|
+
const volumeTrend = [12500, 13200, 12800, 14100, 15200, 14800, 16000];
|
|
2217
|
+
</script>
|
|
2218
|
+
|
|
2219
|
+
<Stat
|
|
2220
|
+
label="7-Day Volume"
|
|
2221
|
+
value="16,000 lbs"
|
|
2222
|
+
trend="up"
|
|
2223
|
+
change={12.5}
|
|
2224
|
+
description="gym planner example"
|
|
2225
|
+
>
|
|
2226
|
+
<div style="margin-top: 1rem;">
|
|
2227
|
+
<Sparkline
|
|
2228
|
+
data={volumeTrend}
|
|
2229
|
+
height={40}
|
|
2230
|
+
width={180}
|
|
2231
|
+
color="var(--color-success)"
|
|
2232
|
+
showFill={true}
|
|
2233
|
+
/>
|
|
2234
|
+
</div>
|
|
2235
|
+
</Stat>
|
|
2236
|
+
```
|
|
2237
|
+
|
|
2238
|
+
**Custom Colors and Sizes:**
|
|
2239
|
+
|
|
2240
|
+
```svelte
|
|
2241
|
+
<script>
|
|
2242
|
+
import { Sparkline } from '@mrintel/villain-ui';
|
|
2243
|
+
|
|
2244
|
+
const revenueData = [5400, 5800, 5200, 6100, 6500, 6200, 6800];
|
|
2245
|
+
const downtrendData = [180, 175, 165, 158, 150, 145, 138];
|
|
2246
|
+
</script>
|
|
2247
|
+
|
|
2248
|
+
<!-- Success trend (green) -->
|
|
2249
|
+
<Sparkline data={revenueData} color="var(--color-success)" showFill={true} />
|
|
2250
|
+
|
|
2251
|
+
<!-- Error trend (red) -->
|
|
2252
|
+
<Sparkline data={downtrendData} color="var(--color-error)" />
|
|
2253
|
+
|
|
2254
|
+
<!-- Large with thick stroke -->
|
|
2255
|
+
<Sparkline
|
|
2256
|
+
data={revenueData}
|
|
2257
|
+
height={80}
|
|
2258
|
+
width={400}
|
|
2259
|
+
strokeWidth={3}
|
|
2260
|
+
color="var(--color-success)"
|
|
2261
|
+
showFill={true}
|
|
2262
|
+
/>
|
|
2263
|
+
```
|
|
2264
|
+
|
|
2265
|
+
**Accessibility:**
|
|
2266
|
+
- Respects `prefers-reduced-motion` - disables animation when requested
|
|
2267
|
+
- Includes `role="img"` and `aria-label` for screen readers
|
|
2268
|
+
- Color is not the only indicator of information (use with labels/values)
|
|
2269
|
+
|
|
2270
|
+
**For Advanced Charting:**
|
|
2271
|
+
|
|
2272
|
+
Sparkline is designed for simple micro trends. For complex visualization needs (multi-series charts, axes, legends, tooltips, bar/pie/scatter charts), integrate established charting libraries:
|
|
2273
|
+
- [Chart.js](https://www.chartjs.org/) - Versatile canvas-based charts
|
|
2274
|
+
- [uPlot](https://github.com/leeoniya/uPlot) - Extremely fast time-series charts
|
|
2275
|
+
- [Apache ECharts](https://echarts.apache.org/) - Feature-rich enterprise charts
|
|
2276
|
+
|
|
2277
|
+
These libraries integrate easily with villain-ui theming via CSS variables.
|
|
2278
|
+
|
|
2279
|
+
**CalendarGrid** - Interactive monthly calendar with event support
|
|
2280
|
+
|
|
2281
|
+
A fully-featured calendar grid component for displaying events, selecting dates, and navigating months. Perfect for scheduling interfaces, date selection, and event dashboards.
|
|
2282
|
+
|
|
2283
|
+
**Key Features:**
|
|
2284
|
+
- Monthly calendar grid with prev/next month navigation
|
|
2285
|
+
- Event display with customizable rendering
|
|
2286
|
+
- Date selection with keyboard navigation
|
|
2287
|
+
- Configurable week start day (Sunday/Monday)
|
|
2288
|
+
- Optional week numbers
|
|
2289
|
+
- Today highlighting
|
|
2290
|
+
- Custom cell rendering via snippets
|
|
2291
|
+
- Fully accessible with ARIA attributes
|
|
2292
|
+
|
|
2293
|
+
**Props:**
|
|
2294
|
+
|
|
2295
|
+
| Prop | Type | Default | Description |
|
|
2296
|
+
|------|------|---------|-------------|
|
|
2297
|
+
| `month` | `Date` | `new Date()` | Currently displayed month (bindable) |
|
|
2298
|
+
| `events` | `CalendarEvent[]` | `[]` | Array of events to display |
|
|
2299
|
+
| `selectedDate` | `Date` | `undefined` | Currently selected date (bindable) |
|
|
2300
|
+
| `onDateSelect` | `(date: Date) => void` | - | Callback when date is clicked |
|
|
2301
|
+
| `onMonthChange` | `(date: Date) => void` | - | Callback when month changes |
|
|
2302
|
+
| `renderCell` | `Snippet<[CellData]>` | - | Custom cell rendering snippet |
|
|
2303
|
+
| `weekStartsOn` | `0 \| 1` | `0` | Week start day (0=Sunday, 1=Monday) |
|
|
2304
|
+
| `showWeekNumbers` | `boolean` | `false` | Display week numbers |
|
|
2305
|
+
| `highlightToday` | `boolean` | `true` | Highlight current date |
|
|
2306
|
+
| `class` | `string` | `''` | Additional CSS classes |
|
|
2307
|
+
|
|
2308
|
+
**CalendarEvent Interface:**
|
|
2309
|
+
```typescript
|
|
2310
|
+
interface CalendarEvent {
|
|
2311
|
+
id: string;
|
|
2312
|
+
title: string;
|
|
2313
|
+
date: Date | string;
|
|
2314
|
+
color?: string;
|
|
2315
|
+
description?: string;
|
|
2316
|
+
}
|
|
2317
|
+
```
|
|
2318
|
+
|
|
2319
|
+
**CellData Interface (for renderCell):**
|
|
2320
|
+
```typescript
|
|
2321
|
+
interface CellData {
|
|
2322
|
+
date: Date;
|
|
2323
|
+
events: CalendarEvent[];
|
|
2324
|
+
isToday: boolean;
|
|
2325
|
+
isSelected: boolean;
|
|
2326
|
+
isCurrentMonth: boolean;
|
|
2327
|
+
isEmpty: boolean;
|
|
2328
|
+
}
|
|
2329
|
+
```
|
|
2330
|
+
|
|
2331
|
+
**Basic Usage:**
|
|
2332
|
+
|
|
2333
|
+
```svelte
|
|
2334
|
+
<script>
|
|
2335
|
+
import { CalendarGrid } from '@mrintel/villain-ui';
|
|
2336
|
+
|
|
2337
|
+
let currentMonth = $state(new Date());
|
|
2338
|
+
let selectedDate = $state();
|
|
2339
|
+
|
|
2340
|
+
const events = [
|
|
2341
|
+
{ id: '1', title: 'Team Meeting', date: '2024-01-15', color: 'var(--color-accent)' },
|
|
2342
|
+
{ id: '2', title: 'Project Deadline', date: '2024-01-20', color: 'var(--color-error)' }
|
|
2343
|
+
];
|
|
2344
|
+
</script>
|
|
2345
|
+
|
|
2346
|
+
<CalendarGrid
|
|
2347
|
+
bind:month={currentMonth}
|
|
2348
|
+
bind:selectedDate
|
|
2349
|
+
{events}
|
|
2350
|
+
onDateSelect={(date) => console.log('Selected:', date)}
|
|
2351
|
+
onMonthChange={(date) => console.log('Month changed:', date)}
|
|
2352
|
+
/>
|
|
2353
|
+
```
|
|
2354
|
+
|
|
2355
|
+
**With Custom Cell Rendering:**
|
|
2356
|
+
|
|
2357
|
+
```svelte
|
|
2358
|
+
<script>
|
|
2359
|
+
import { CalendarGrid } from '@mrintel/villain-ui';
|
|
2360
|
+
|
|
2361
|
+
let currentMonth = $state(new Date());
|
|
2362
|
+
const events = [...]; // Your events array
|
|
2363
|
+
</script>
|
|
2364
|
+
|
|
2365
|
+
<CalendarGrid bind:month={currentMonth} {events}>
|
|
2366
|
+
{#snippet renderCell(cellData)}
|
|
2367
|
+
<div class="custom-cell" class:has-events={cellData.events.length > 0}>
|
|
2368
|
+
<div class="date-number">{cellData.date.getDate()}</div>
|
|
2369
|
+
{#if cellData.events.length > 0}
|
|
2370
|
+
<div class="event-indicators">
|
|
2371
|
+
{#each cellData.events.slice(0, 3) as event}
|
|
2372
|
+
<div class="event-dot" style="background: {event.color}"></div>
|
|
2373
|
+
{/each}
|
|
2374
|
+
</div>
|
|
2375
|
+
{/if}
|
|
2376
|
+
</div>
|
|
2377
|
+
{/snippet}
|
|
2378
|
+
</CalendarGrid>
|
|
2379
|
+
```
|
|
2380
|
+
|
|
2381
|
+
**With Week Starting on Monday:**
|
|
2382
|
+
|
|
2383
|
+
```svelte
|
|
2384
|
+
<CalendarGrid bind:month={currentMonth} {events} weekStartsOn={1} showWeekNumbers={true} />
|
|
2385
|
+
```
|
|
2386
|
+
|
|
2387
|
+
**Keyboard Navigation:**
|
|
2388
|
+
- `Arrow Keys` - Navigate between dates
|
|
2389
|
+
- `Enter` or `Space` - Select focused date
|
|
2390
|
+
- `Home` - Go to first day of week
|
|
2391
|
+
- `End` - Go to last day of week
|
|
2392
|
+
- `Page Up` - Go to previous month
|
|
2393
|
+
- `Page Down` - Go to next month
|
|
2394
|
+
|
|
2395
|
+
**Accessibility Features:**
|
|
2396
|
+
- Full keyboard navigation support
|
|
2397
|
+
- ARIA labels for dates and navigation
|
|
2398
|
+
- Screen reader announcements for selected dates
|
|
2399
|
+
- Focus management and visual indicators
|
|
2400
|
+
|
|
2401
|
+
### Utilities
|
|
2402
|
+
|
|
2403
|
+
**Hero** - Full-width hero section for page introductions
|
|
2404
|
+
|
|
2405
|
+
```svelte
|
|
2406
|
+
<script>
|
|
2407
|
+
import { Hero } from '@mrintel/villain-ui';
|
|
2408
|
+
</script>
|
|
2409
|
+
|
|
2410
|
+
<Hero>
|
|
2411
|
+
{#snippet title()}
|
|
2412
|
+
<h1 class="text-5xl font-bold">Welcome to Villain UI</h1>
|
|
2413
|
+
{/snippet}
|
|
2414
|
+
|
|
2415
|
+
{#snippet text()}
|
|
2416
|
+
<p class="text-xl text-neutral-400">A luxury Svelte 5 component library with modern villain aesthetics</p>
|
|
2417
|
+
{/snippet}
|
|
2418
|
+
|
|
2419
|
+
{#snippet features()}
|
|
2420
|
+
<div class="flex gap-4 justify-center">
|
|
2421
|
+
<span class="px-4 py-2 bg-accent-500/10 rounded-lg">Svelte 5</span>
|
|
2422
|
+
<span class="px-4 py-2 bg-accent-500/10 rounded-lg">TypeScript</span>
|
|
2423
|
+
<span class="px-4 py-2 bg-accent-500/10 rounded-lg">Accessible</span>
|
|
2424
|
+
</div>
|
|
2425
|
+
{/snippet}
|
|
2426
|
+
</Hero>
|
|
2427
|
+
```
|
|
2428
|
+
|
|
2429
|
+
**Props:**
|
|
2430
|
+
- `title?: Snippet` - Hero title content
|
|
2431
|
+
- `text?: Snippet` - Hero description/subtitle text
|
|
2432
|
+
- `features?: Snippet` - Optional feature tags or highlights
|
|
2433
|
+
- `children?: Snippet` - Additional custom content
|
|
2434
|
+
- `class?: string` - Additional CSS classes
|
|
2435
|
+
|
|
2436
|
+
**Portal** - Render content in different DOM location
|
|
2437
|
+
|
|
2438
|
+
```svelte
|
|
2439
|
+
<script>
|
|
2440
|
+
import { Portal } from '@mrintel/villain-ui';
|
|
2441
|
+
</script>
|
|
2442
|
+
|
|
2443
|
+
<Portal target="body">
|
|
2444
|
+
<div>This renders at the end of body</div>
|
|
2445
|
+
</Portal>
|
|
2446
|
+
```
|
|
2447
|
+
|
|
2448
|
+
**Collapse** - Collapsible content
|
|
2449
|
+
|
|
2450
|
+
```svelte
|
|
2451
|
+
<script>
|
|
2452
|
+
import { Collapse } from '@mrintel/villain-ui';
|
|
2453
|
+
|
|
2454
|
+
let open = $state(false);
|
|
2455
|
+
</script>
|
|
2456
|
+
|
|
2457
|
+
<Collapse title="Click to expand" open={open} onToggle={() => open = !open}>
|
|
2458
|
+
<p>Hidden content that can be toggled</p>
|
|
2459
|
+
</Collapse>
|
|
2460
|
+
```
|
|
2461
|
+
|
|
2462
|
+
**Accordion** - Accordion with multiple items
|
|
2463
|
+
|
|
2464
|
+
```svelte
|
|
2465
|
+
<script>
|
|
2466
|
+
import { Accordion } from '@mrintel/villain-ui';
|
|
2467
|
+
|
|
2468
|
+
const items = [
|
|
2469
|
+
{ id: '1', title: 'Section 1', content: 'Content for section 1' },
|
|
2470
|
+
{ id: '2', title: 'Section 2', content: 'Content for section 2' }
|
|
2471
|
+
];
|
|
2472
|
+
|
|
2473
|
+
let openItems = $state([]);
|
|
2474
|
+
</script>
|
|
2475
|
+
|
|
2476
|
+
<Accordion {items} bind:openItems mode="multiple" />
|
|
2477
|
+
```
|
|
2478
|
+
|
|
2479
|
+
**Carousel** - Image/content carousel
|
|
2480
|
+
|
|
2481
|
+
```svelte
|
|
2482
|
+
<script>
|
|
2483
|
+
import { Carousel } from '@mrintel/villain-ui';
|
|
2484
|
+
|
|
2485
|
+
const items = [
|
|
2486
|
+
{ id: '1', content: 'Slide 1' },
|
|
2487
|
+
{ id: '2', content: 'Slide 2' },
|
|
2488
|
+
{ id: '3', content: 'Slide 3' }
|
|
2489
|
+
];
|
|
2490
|
+
|
|
2491
|
+
let currentIndex = $state(0);
|
|
2492
|
+
</script>
|
|
2493
|
+
|
|
2494
|
+
<Carousel {items} bind:currentIndex autoplay showDots showArrows />
|
|
2495
|
+
```
|
|
2496
|
+
|
|
2497
|
+
**ScrollArea** - Custom scrollable area
|
|
2498
|
+
|
|
2499
|
+
```svelte
|
|
2500
|
+
<script>
|
|
2501
|
+
import { ScrollArea } from '@mrintel/villain-ui';
|
|
2502
|
+
</script>
|
|
2503
|
+
|
|
2504
|
+
<ScrollArea height="300px">
|
|
2505
|
+
<div>Long scrollable content...</div>
|
|
2506
|
+
</ScrollArea>
|
|
2507
|
+
```
|
|
2508
|
+
|
|
2509
|
+
**SystemConsole** - Terminal-style console with message history
|
|
2510
|
+
|
|
2511
|
+
A retro terminal console component with typewriter effects, scanlines, and glow effects. Perfect for command-line interfaces, log viewers, and terminal-style interactions.
|
|
2512
|
+
|
|
2513
|
+
**Key Features:**
|
|
2514
|
+
- Terminal-style message display with user/system roles
|
|
2515
|
+
- Optional typewriter animation for system messages
|
|
2516
|
+
- Configurable scanlines and glow effects
|
|
2517
|
+
- Auto-scroll to latest messages
|
|
2518
|
+
- Timestamp support
|
|
2519
|
+
- Message variants (default, success, warning, error, info)
|
|
2520
|
+
- Command input with submission handling
|
|
2521
|
+
- Customizable prompt character
|
|
2522
|
+
|
|
2523
|
+
**Props:**
|
|
2524
|
+
|
|
2525
|
+
| Prop | Type | Default | Description |
|
|
2526
|
+
|------|------|---------|-------------|
|
|
2527
|
+
| `messages` | `ConsoleMessage[]` | `[]` | Array of console messages |
|
|
2528
|
+
| `prompt` | `string` | `'>'` | Prompt character/text (e.g., '>', '$', 'λ') |
|
|
2529
|
+
| `placeholder` | `string` | `'Enter command...'` | Input placeholder text |
|
|
2530
|
+
| `height` | `string` | `'500px'` | Console height |
|
|
2531
|
+
| `maxHeight` | `string` | `'80vh'` | Maximum console height |
|
|
2532
|
+
| `showTimestamps` | `boolean` | `false` | Display message timestamps |
|
|
2533
|
+
| `autoScroll` | `boolean` | `true` | Auto-scroll to new messages |
|
|
2534
|
+
| `typewriterEffect` | `boolean` | `false` | Animate system messages |
|
|
2535
|
+
| `typewriterSpeed` | `number` | `50` | Characters per second for animation |
|
|
2536
|
+
| `showScanlines` | `boolean` | `true` | Show retro scanline effect |
|
|
2537
|
+
| `glowEffect` | `boolean` | `true` | Show text glow effect |
|
|
2538
|
+
| `onSubmit` | `(input: string) => void` | - | Called when command is submitted |
|
|
2539
|
+
| `disabled` | `boolean` | `false` | Disable input |
|
|
2540
|
+
| `class` | `string` | `''` | Additional CSS classes |
|
|
2541
|
+
|
|
2542
|
+
**ConsoleMessage Interface:**
|
|
2543
|
+
```typescript
|
|
2544
|
+
interface ConsoleMessage {
|
|
2545
|
+
id: string;
|
|
2546
|
+
role: 'user' | 'system';
|
|
2547
|
+
content: string;
|
|
2548
|
+
timestamp?: Date;
|
|
2549
|
+
variant?: 'default' | 'success' | 'warning' | 'error' | 'info';
|
|
2550
|
+
}
|
|
2551
|
+
```
|
|
2552
|
+
|
|
2553
|
+
**Basic Usage:**
|
|
2554
|
+
|
|
2555
|
+
```svelte
|
|
2556
|
+
<script>
|
|
2557
|
+
import { SystemConsole } from '@mrintel/villain-ui';
|
|
2558
|
+
|
|
2559
|
+
let messages = $state([
|
|
2560
|
+
{ id: '1', role: 'system', content: 'System initialized...', timestamp: new Date() },
|
|
2561
|
+
{ id: '2', role: 'user', content: 'help', timestamp: new Date() },
|
|
2562
|
+
{ id: '3', role: 'system', content: 'Available commands: help, status, exit', variant: 'info' }
|
|
2563
|
+
]);
|
|
2564
|
+
|
|
2565
|
+
function handleCommand(input) {
|
|
2566
|
+
// Add user message
|
|
2567
|
+
messages = [...messages, {
|
|
2568
|
+
id: Date.now().toString(),
|
|
2569
|
+
role: 'user',
|
|
2570
|
+
content: input,
|
|
2571
|
+
timestamp: new Date()
|
|
2572
|
+
}];
|
|
2573
|
+
|
|
2574
|
+
// Process command and add system response
|
|
2575
|
+
setTimeout(() => {
|
|
2576
|
+
messages = [...messages, {
|
|
2577
|
+
id: (Date.now() + 1).toString(),
|
|
2578
|
+
role: 'system',
|
|
2579
|
+
content: `Executed: ${input}`,
|
|
2580
|
+
timestamp: new Date(),
|
|
2581
|
+
variant: 'success'
|
|
2582
|
+
}];
|
|
2583
|
+
}, 500);
|
|
2584
|
+
}
|
|
2585
|
+
</script>
|
|
2586
|
+
|
|
2587
|
+
<SystemConsole
|
|
2588
|
+
{messages}
|
|
2589
|
+
prompt="$"
|
|
2590
|
+
showTimestamps={true}
|
|
2591
|
+
onSubmit={handleCommand}
|
|
2592
|
+
/>
|
|
2593
|
+
```
|
|
2594
|
+
|
|
2595
|
+
**With Typewriter Effect:**
|
|
2596
|
+
|
|
2597
|
+
```svelte
|
|
2598
|
+
<SystemConsole
|
|
2599
|
+
{messages}
|
|
2600
|
+
typewriterEffect={true}
|
|
2601
|
+
typewriterSpeed={80}
|
|
2602
|
+
showScanlines={true}
|
|
2603
|
+
glowEffect={true}
|
|
2604
|
+
height="600px"
|
|
2605
|
+
onSubmit={handleCommand}
|
|
2606
|
+
/>
|
|
2607
|
+
```
|
|
2608
|
+
|
|
2609
|
+
**SystemInterface** - Advanced system interface with panels
|
|
2610
|
+
|
|
2611
|
+
A sophisticated system interface component with multi-panel layout, staged message revelation, and processing states. Ideal for AI assistants, command centers, and advanced terminal interfaces.
|
|
2612
|
+
|
|
2613
|
+
**Key Features:**
|
|
2614
|
+
- Multi-panel layout (left, right, top, bottom)
|
|
2615
|
+
- Staged message revelation with delays
|
|
2616
|
+
- Processing animation states
|
|
2617
|
+
- Tiered message system (informational, analysis, directive, warning, critical)
|
|
2618
|
+
- Rich message content types (text, code, data, status)
|
|
2619
|
+
- Timestamp display with millisecond precision
|
|
2620
|
+
- Auto-scroll support
|
|
2621
|
+
- Customizable height
|
|
2622
|
+
|
|
2623
|
+
**Props:**
|
|
2624
|
+
|
|
2625
|
+
| Prop | Type | Default | Description |
|
|
2626
|
+
|------|------|---------|-------------|
|
|
2627
|
+
| `messages` | `SystemMessage[]` | `[]` | Array of system messages |
|
|
2628
|
+
| `onSubmit` | `(input: string) => void` | - | Called when directive is submitted |
|
|
2629
|
+
| `processing` | `boolean` | `false` | Show processing animation |
|
|
2630
|
+
| `placeholder` | `string` | `'ENTER DIRECTIVE'` | Input placeholder text |
|
|
2631
|
+
| `height` | `string` | `'600px'` | Interface height |
|
|
2632
|
+
| `autoScroll` | `boolean` | `true` | Auto-scroll to new messages |
|
|
2633
|
+
| `stagingDelay` | `number` | `150` | Delay (ms) between message reveals |
|
|
2634
|
+
| `leftPanel` | `Snippet` | - | Left sidebar content |
|
|
2635
|
+
| `rightPanel` | `Snippet` | - | Right sidebar content |
|
|
2636
|
+
| `topPanel` | `Snippet` | - | Top panel content |
|
|
2637
|
+
| `bottomPanel` | `Snippet` | - | Bottom panel content |
|
|
2638
|
+
| `class` | `string` | `''` | Additional CSS classes |
|
|
2639
|
+
|
|
2640
|
+
**SystemMessage Interface:**
|
|
2641
|
+
```typescript
|
|
2642
|
+
interface SystemMessage {
|
|
2643
|
+
id: string;
|
|
2644
|
+
content: string | MessageContentType;
|
|
2645
|
+
timestamp?: number;
|
|
2646
|
+
tier?: 'informational' | 'analysis' | 'directive' | 'warning' | 'critical';
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
type MessageContentType =
|
|
2650
|
+
| { type: 'text'; value: string }
|
|
2651
|
+
| { type: 'code'; value: string; language?: string }
|
|
2652
|
+
| { type: 'data'; value: Record<string, any> }
|
|
2653
|
+
| { type: 'status'; label: string; value: string; status: 'ok' | 'warning' | 'error' | 'info' };
|
|
2654
|
+
```
|
|
2655
|
+
|
|
2656
|
+
**Basic Usage:**
|
|
2657
|
+
|
|
2658
|
+
```svelte
|
|
2659
|
+
<script>
|
|
2660
|
+
import { SystemInterface } from '@mrintel/villain-ui';
|
|
2661
|
+
|
|
2662
|
+
let messages = $state([
|
|
2663
|
+
{
|
|
2664
|
+
id: '1',
|
|
2665
|
+
content: 'SYSTEM ONLINE',
|
|
2666
|
+
timestamp: Date.now(),
|
|
2667
|
+
tier: 'directive'
|
|
2668
|
+
},
|
|
2669
|
+
{
|
|
2670
|
+
id: '2',
|
|
2671
|
+
content: 'Analyzing input parameters...',
|
|
2672
|
+
timestamp: Date.now() + 100,
|
|
2673
|
+
tier: 'analysis'
|
|
2674
|
+
}
|
|
2675
|
+
]);
|
|
2676
|
+
|
|
2677
|
+
let processing = $state(false);
|
|
2678
|
+
|
|
2679
|
+
function handleDirective(input) {
|
|
2680
|
+
processing = true;
|
|
2681
|
+
|
|
2682
|
+
messages = [...messages, {
|
|
2683
|
+
id: Date.now().toString(),
|
|
2684
|
+
content: { type: 'text', value: input },
|
|
2685
|
+
timestamp: Date.now(),
|
|
2686
|
+
tier: 'directive'
|
|
2687
|
+
}];
|
|
2688
|
+
|
|
2689
|
+
// Simulate processing
|
|
2690
|
+
setTimeout(() => {
|
|
2691
|
+
messages = [...messages, {
|
|
2692
|
+
id: (Date.now() + 1).toString(),
|
|
2693
|
+
content: `Directive executed: ${input}`,
|
|
2694
|
+
timestamp: Date.now(),
|
|
2695
|
+
tier: 'informational'
|
|
2696
|
+
}];
|
|
2697
|
+
processing = false;
|
|
2698
|
+
}, 1500);
|
|
2699
|
+
}
|
|
2700
|
+
</script>
|
|
2701
|
+
|
|
2702
|
+
<SystemInterface
|
|
2703
|
+
{messages}
|
|
2704
|
+
{processing}
|
|
2705
|
+
onSubmit={handleDirective}
|
|
2706
|
+
/>
|
|
2707
|
+
```
|
|
2708
|
+
|
|
2709
|
+
**With Side Panels:**
|
|
2710
|
+
|
|
2711
|
+
```svelte
|
|
2712
|
+
<SystemInterface {messages} onSubmit={handleDirective}>
|
|
2713
|
+
{#snippet leftPanel()}
|
|
2714
|
+
<div class="panel-content">
|
|
2715
|
+
<h3>System Status</h3>
|
|
2716
|
+
<div>CPU: 45%</div>
|
|
2717
|
+
<div>Memory: 2.1GB</div>
|
|
2718
|
+
</div>
|
|
2719
|
+
{/snippet}
|
|
2720
|
+
|
|
2721
|
+
{#snippet rightPanel()}
|
|
2722
|
+
<div class="panel-content">
|
|
2723
|
+
<h3>Active Processes</h3>
|
|
2724
|
+
<div>Process 1: Running</div>
|
|
2725
|
+
<div>Process 2: Idle</div>
|
|
2726
|
+
</div>
|
|
2727
|
+
{/snippet}
|
|
2728
|
+
</SystemInterface>
|
|
2729
|
+
```
|
|
2730
|
+
|
|
2731
|
+
**With Rich Content Types:**
|
|
2732
|
+
|
|
2733
|
+
```svelte
|
|
2734
|
+
<script>
|
|
2735
|
+
const messages = [
|
|
2736
|
+
{
|
|
2737
|
+
id: '1',
|
|
2738
|
+
content: { type: 'text', value: 'Starting analysis...' },
|
|
2739
|
+
tier: 'informational'
|
|
2740
|
+
},
|
|
2741
|
+
{
|
|
2742
|
+
id: '2',
|
|
2743
|
+
content: {
|
|
2744
|
+
type: 'code',
|
|
2745
|
+
value: 'const result = await analyze(data);',
|
|
2746
|
+
language: 'javascript'
|
|
2747
|
+
},
|
|
2748
|
+
tier: 'analysis'
|
|
2749
|
+
},
|
|
2750
|
+
{
|
|
2751
|
+
id: '3',
|
|
2752
|
+
content: {
|
|
2753
|
+
type: 'status',
|
|
2754
|
+
label: 'Analysis',
|
|
2755
|
+
value: 'Complete',
|
|
2756
|
+
status: 'ok'
|
|
2757
|
+
},
|
|
2758
|
+
tier: 'directive'
|
|
2759
|
+
},
|
|
2760
|
+
{
|
|
2761
|
+
id: '4',
|
|
2762
|
+
content: {
|
|
2763
|
+
type: 'data',
|
|
2764
|
+
value: { accuracy: 0.95, confidence: 0.88 }
|
|
2765
|
+
},
|
|
2766
|
+
tier: 'informational'
|
|
2767
|
+
}
|
|
2768
|
+
];
|
|
2769
|
+
</script>
|
|
2770
|
+
|
|
2771
|
+
<SystemInterface {messages} onSubmit={handleDirective} />
|
|
2772
|
+
```
|
|
2773
|
+
|
|
2774
|
+
## 🎨 Theming
|
|
2775
|
+
|
|
2776
|
+
### Global Styles
|
|
2777
|
+
|
|
2778
|
+
The library includes comprehensive global styles that provide the foundation for the luxury dark aesthetic. When you import `theme.css`, you get:
|
|
2779
|
+
|
|
2780
|
+
**Base HTML/Body Styling:**
|
|
2781
|
+
- Dark background (`--color-base-0`: #0a0a0a - Onyx Black)
|
|
2782
|
+
- Optimized font rendering with antialiasing
|
|
2783
|
+
- Body text styling with Inter font family
|
|
2784
|
+
- Proper box-sizing and reset for consistent rendering
|
|
2785
|
+
|
|
2786
|
+
**Typography System:**
|
|
2787
|
+
- Automatic heading styles (h1-h6) with Inter font family
|
|
2788
|
+
- Optimized font sizes, line heights, weights, and letter spacing
|
|
2789
|
+
- Zero default margins/padding for precise control
|
|
2790
|
+
- Monospace font (JetBrains Mono) for code elements
|
|
2791
|
+
|
|
2792
|
+
**Built-in Animations:**
|
|
2793
|
+
The theme includes several keyframe animations ready to use:
|
|
2794
|
+
- `fade-in` - Simple opacity fade
|
|
2795
|
+
- `fade-out` - Opacity fade out
|
|
2796
|
+
- `fade-up` - Fade in with upward movement
|
|
2797
|
+
- `glow-pulse` - Pulsing accent glow effect
|
|
2798
|
+
- `slide-in-left/right/top/bottom` - Directional slide animations
|
|
2799
|
+
|
|
2800
|
+
**Custom Utility Classes:**
|
|
2801
|
+
- `.text-glow` - Apply accent text glow effect
|
|
2802
|
+
- `.text-gradient` - Gradient text with purple-to-soft-purple gradient and transparent fill
|
|
2803
|
+
- `.glass-panel` - Glass morphism with backdrop blur and borders
|
|
2804
|
+
- `.accent-glow` - Accent color box-shadow glow
|
|
2805
|
+
- `.hover-lift` - Lift element on hover with glow and shadow
|
|
2806
|
+
- `.hover-lift-enhanced` - Enhanced lift on hover with accent border and glow (stronger effect)
|
|
2807
|
+
- `.card-icon` - Centered icon container with accent color and 4xl sizing
|
|
2808
|
+
- `.hero-section` - Centered hero section layout with padding (4rem top, 3rem bottom, 3rem margin-bottom). Used by the `Hero` component as its base layout.
|
|
2809
|
+
- `.feature-tags` - Horizontal flexbox container for feature tags with center alignment and wrapping. Used by the `Hero` component for its features slot.
|
|
2810
|
+
- `.feature-tag` - Pill-shaped feature badge with accent background, border glow, and hover effect
|
|
2811
|
+
- `.metal-edge` - Specular metallic border highlights
|
|
2812
|
+
- `.obsidian-surface` - Flat black surface with subtle gradient reflection
|
|
2813
|
+
- `.sidebar-collapsed-icon` - Centered icon display for collapsed sidebar states
|
|
2814
|
+
- `.sidebar-collapsed-letter` - Accent-colored circle with first letter for collapsed sidebar items without icons
|
|
2815
|
+
|
|
2816
|
+
### CSS Variable System
|
|
2817
|
+
|
|
2818
|
+
@mrintel/villain-ui uses a comprehensive CSS variable system that allows complete customization without touching component code. All theme variables are defined in `theme.css` and can be overridden in your own CSS.
|
|
2819
|
+
|
|
2820
|
+
### Custom Theme Example
|
|
2821
|
+
|
|
2822
|
+
Create a custom CSS file (e.g., `custom-theme.css`) and import it **after** the library theme:
|
|
2823
|
+
|
|
2824
|
+
```css
|
|
2825
|
+
/* custom-theme.css */
|
|
2826
|
+
|
|
2827
|
+
/* Override accent color from purple to blue */
|
|
2828
|
+
:root {
|
|
2829
|
+
--color-accent: #3B82F6;
|
|
2830
|
+
--color-accent-soft: #60A5FA;
|
|
2831
|
+
--color-accent-dark: #1E40AF;
|
|
2832
|
+
|
|
2833
|
+
--shadow-accent-glow:
|
|
2834
|
+
0 0 20px rgba(59, 130, 246, 0.4),
|
|
2835
|
+
0 0 40px rgba(59, 130, 246, 0.2),
|
|
2836
|
+
0 0 60px rgba(59, 130, 246, 0.1);
|
|
2837
|
+
|
|
2838
|
+
--shadow-text-glow:
|
|
2839
|
+
0 0 20px rgba(59, 130, 246, 0.5),
|
|
2840
|
+
0 0 40px rgba(59, 130, 246, 0.3);
|
|
2841
|
+
}
|
|
2842
|
+
```
|
|
2843
|
+
|
|
2844
|
+
Import order in your app:
|
|
2845
|
+
|
|
2846
|
+
```typescript
|
|
2847
|
+
import '@mrintel/villain-ui/theme.css';
|
|
2848
|
+
import './custom-theme.css'; // Your overrides
|
|
2849
|
+
```
|
|
2850
|
+
|
|
2851
|
+
### Typography Customization
|
|
2852
|
+
|
|
2853
|
+
```css
|
|
2854
|
+
:root {
|
|
2855
|
+
--font-heading: 'Montserrat', sans-serif;
|
|
2856
|
+
--font-body: 'Open Sans', sans-serif;
|
|
2857
|
+
--font-mono: 'Fira Code', monospace;
|
|
2858
|
+
|
|
2859
|
+
--text-h1-size: 4rem;
|
|
2860
|
+
--text-body-size: 1.125rem;
|
|
2861
|
+
}
|
|
2862
|
+
```
|
|
2863
|
+
|
|
2864
|
+
### Border Radius Customization
|
|
2865
|
+
|
|
2866
|
+
The library uses calculated, precision border radii for the "villain" aesthetic:
|
|
2867
|
+
|
|
2868
|
+
```css
|
|
2869
|
+
:root {
|
|
2870
|
+
--radius-sm: 3px; /* Subtle taper */
|
|
2871
|
+
--radius-md: 6px; /* Controlled precision */
|
|
2872
|
+
--radius-lg: 8px; /* Engineered corners */
|
|
2873
|
+
--radius-xl: 10px; /* Maximum rounding - still calculated */
|
|
2874
|
+
--radius-2xl: 14px; /* Reserved for large surfaces */
|
|
2875
|
+
--radius-pill: 999px; /* Intentional full-round (use sparingly) */
|
|
2876
|
+
}
|
|
2877
|
+
```
|
|
2878
|
+
|
|
2879
|
+
### Complete Variable Reference
|
|
2880
|
+
|
|
2881
|
+
#### Colors
|
|
2882
|
+
|
|
2883
|
+
**Base Colors (Modern Villain Luxury)**
|
|
2884
|
+
- `--color-base-0`: #0a0a0a - Onyx Black (deepest background)
|
|
2885
|
+
- `--color-base-1`: #121212 - Elevated surface
|
|
2886
|
+
- `--color-base-2`: #1a1a1a - Panel layer
|
|
2887
|
+
- `--color-base-3`: #242424 - Highest elevation
|
|
2888
|
+
- `--color-surface`: Alias for base-1
|
|
2889
|
+
- `--color-panel`: Alias for base-2
|
|
2890
|
+
- `--color-overlay`: rgba(0, 0, 0, 0.75) - Modal backdrop
|
|
2891
|
+
|
|
2892
|
+
**Accent Colors (Royal Purple & Crimson)**
|
|
2893
|
+
- `--color-accent`: #6b21a8 - Royal Purple (primary)
|
|
2894
|
+
- `--color-accent-soft`: #8b5cf6 - Lighter purple variant
|
|
2895
|
+
- `--color-accent-dark`: #581c87 - Darker purple variant
|
|
2896
|
+
- `--color-secondary`: #3b82f6 - Electric Blue
|
|
2897
|
+
- `--color-crimson`: #ef4444 - Crimson Red accent
|
|
2898
|
+
|
|
2899
|
+
**Text Colors**
|
|
2900
|
+
- `--color-text`: #e0e0e0 - Primary text (refined gray)
|
|
2901
|
+
- `--color-text-soft`: #a3a3a3 - Secondary text
|
|
2902
|
+
- `--color-text-muted`: #737373 - Muted/disabled text
|
|
2903
|
+
|
|
2904
|
+
**State Colors (Bold & Commanding)**
|
|
2905
|
+
- `--color-success`: #10b981 - Emerald green
|
|
2906
|
+
- `--color-warning`: #f59e0b - Amber
|
|
2907
|
+
- `--color-error`: #ef4444 - Crimson Red
|
|
2908
|
+
|
|
2909
|
+
**Border Colors (Neon Edges)**
|
|
2910
|
+
- `--color-border`: rgba(255, 255, 255, 0.10) - Default border
|
|
2911
|
+
- `--color-border-strong`: rgba(255, 255, 255, 0.20) - Emphasized border
|
|
2912
|
+
- `--color-border-glow`: rgba(107, 33, 168, 0.30) - Purple glow edge
|
|
2913
|
+
|
|
2914
|
+
**Overlay Colors (Alpha Transparency)**
|
|
2915
|
+
- `--color-accent-overlay-5`: rgba(107, 33, 168, 0.05) - Very subtle accent tint
|
|
2916
|
+
- `--color-accent-overlay-10`: rgba(107, 33, 168, 0.1) - Subtle accent background
|
|
2917
|
+
- `--color-accent-overlay-15`: rgba(107, 33, 168, 0.15) - Light accent overlay
|
|
2918
|
+
- `--color-accent-overlay-20`: rgba(107, 33, 168, 0.2) - Medium accent overlay
|
|
2919
|
+
- `--color-accent-overlay-30`: rgba(107, 33, 168, 0.3) - Strong accent overlay
|
|
2920
|
+
- `--color-accent-overlay-50`: rgba(107, 33, 168, 0.5) - Semi-transparent accent
|
|
2921
|
+
- `--color-accent-overlay-70`: rgba(107, 33, 168, 0.7) - Dense accent overlay
|
|
2922
|
+
- `--color-secondary-overlay-10`: rgba(127, 61, 255, 0.1) - Subtle secondary tint
|
|
2923
|
+
- `--color-secondary-overlay-20`: rgba(127, 61, 255, 0.2) - Medium secondary overlay
|
|
2924
|
+
- `--color-neutral-overlay-2`: rgba(255, 255, 255, 0.02) - Subtle white tint
|
|
2925
|
+
- `--color-shadow-overlay-20`: rgba(0, 0, 0, 0.2) - Shadow layer
|
|
2926
|
+
- `--color-success-overlay-15`: rgba(0, 232, 151, 0.15) - Success state background
|
|
2927
|
+
- `--color-warning-overlay-15`: rgba(255, 200, 97, 0.15) - Warning state background
|
|
2928
|
+
- `--color-error-overlay-15`: rgba(255, 74, 106, 0.15) - Error state background
|
|
2929
|
+
|
|
2930
|
+
#### Typography
|
|
2931
|
+
|
|
2932
|
+
**Font Families (Modern Villain Luxury)**
|
|
2933
|
+
- `--font-heading`: 'Inter', sans-serif - Unified typography for headings
|
|
2934
|
+
- `--font-body`: 'Inter', sans-serif - Body text
|
|
2935
|
+
- `--font-mono`: 'JetBrains Mono', monospace - Code and numeric display
|
|
2936
|
+
|
|
2937
|
+
**Heading Text Scales (h1-h6)**
|
|
2938
|
+
- `--text-h1-size`: 3.5rem (56px)
|
|
2939
|
+
- `--text-h1-line-height`: 1.2
|
|
2940
|
+
- `--text-h1-weight`: 700
|
|
2941
|
+
- `--text-h1-letter-spacing`: -0.02em
|
|
2942
|
+
|
|
2943
|
+
- `--text-h2-size`: 2.5rem (40px)
|
|
2944
|
+
- `--text-h2-line-height`: 1.25
|
|
2945
|
+
- `--text-h2-weight`: 600
|
|
2946
|
+
- `--text-h2-letter-spacing`: -0.015em
|
|
2947
|
+
|
|
2948
|
+
- `--text-h3-size`: 2rem (32px)
|
|
2949
|
+
- `--text-h3-line-height`: 1.3
|
|
2950
|
+
- `--text-h3-weight`: 600
|
|
2951
|
+
- `--text-h3-letter-spacing`: -0.01em
|
|
2952
|
+
|
|
2953
|
+
- `--text-h4-size`: 1.5rem (24px)
|
|
2954
|
+
- `--text-h4-line-height`: 1.35
|
|
2955
|
+
- `--text-h4-weight`: 600
|
|
2956
|
+
- `--text-h4-letter-spacing`: -0.01em
|
|
2957
|
+
|
|
2958
|
+
- `--text-h5-size`: 1.25rem (20px)
|
|
2959
|
+
- `--text-h5-line-height`: 1.4
|
|
2960
|
+
- `--text-h5-weight`: 500
|
|
2961
|
+
- `--text-h5-letter-spacing`: -0.005em
|
|
2962
|
+
|
|
2963
|
+
- `--text-h6-size`: 1rem (16px)
|
|
2964
|
+
- `--text-h6-line-height`: 1.5
|
|
2965
|
+
- `--text-h6-weight`: 500
|
|
2966
|
+
- `--text-h6-letter-spacing`: 0
|
|
2967
|
+
|
|
2968
|
+
**Body Text Scales**
|
|
2969
|
+
- `--text-body-size`: 1rem
|
|
2970
|
+
- `--text-body-line-height`: 1.6
|
|
2971
|
+
- `--text-body-weight`: 400
|
|
2972
|
+
- `--text-body-letter-spacing`: 0.01em
|
|
2973
|
+
|
|
2974
|
+
- `--text-caption-size`: 0.875rem
|
|
2975
|
+
- `--text-caption-line-height`: 1.5
|
|
2976
|
+
- `--text-caption-weight`: 400
|
|
2977
|
+
- `--text-caption-letter-spacing`: 0.015em
|
|
2978
|
+
|
|
2979
|
+
**Tailwind-Compatible Text Sizes**
|
|
2980
|
+
- `--text-xs` through `--text-9xl` with corresponding line heights
|
|
2981
|
+
- Example: `--text-2xl`: 1.5rem, `--text-2xl--line-height`: 2rem
|
|
2982
|
+
|
|
2983
|
+
#### Layout
|
|
2984
|
+
|
|
2985
|
+
**Border Radii (Rounded 2XL Corners)**
|
|
2986
|
+
- `--radius-none`: 0px
|
|
2987
|
+
- `--radius-sm`: 6px - Small rounded
|
|
2988
|
+
- `--radius-md`: 10px - Medium rounded
|
|
2989
|
+
- `--radius-lg`: 14px - Large rounded
|
|
2990
|
+
- `--radius-xl`: 18px - Extra large
|
|
2991
|
+
- `--radius-2xl`: 24px - **2XL signature rounded corners**
|
|
2992
|
+
- `--radius-pill`: 999px - Full round
|
|
2993
|
+
|
|
2994
|
+
**Spacing**
|
|
2995
|
+
- `--spacing`: 0.25rem - Base spacing unit
|
|
2996
|
+
- `--spacing-4.5`: 1.125rem
|
|
2997
|
+
- `--spacing-18`: 4.5rem
|
|
2998
|
+
|
|
2999
|
+
**Z-Index Scale**
|
|
3000
|
+
- `--z-0`: 0 - Base layer (default)
|
|
3001
|
+
- `--z-10`: 10 - Slightly elevated
|
|
3002
|
+
- `--z-20`: 20 - Elevated content
|
|
3003
|
+
- `--z-30`: 30 - Dropdowns and popovers
|
|
3004
|
+
- `--z-40`: 40 - Sidebar navigation
|
|
3005
|
+
- `--z-50`: 50 - Top navigation, modals, highest overlays
|
|
3006
|
+
|
|
3007
|
+
**Usage Example:**
|
|
3008
|
+
```css
|
|
3009
|
+
.custom-overlay {
|
|
3010
|
+
z-index: var(--z-50);
|
|
3011
|
+
background: var(--color-accent-overlay-20);
|
|
3012
|
+
border-radius: var(--radius-lg);
|
|
3013
|
+
backdrop-filter: blur(12px);
|
|
3014
|
+
}
|
|
3015
|
+
```
|
|
3016
|
+
|
|
3017
|
+
#### Effects
|
|
3018
|
+
|
|
3019
|
+
**Shadows (Glow & Depth)**
|
|
3020
|
+
- `--shadow-accent-glow`: Layered Royal Purple glow effect (20px/40px/60px)
|
|
3021
|
+
- `--shadow-crimson-glow`: Crimson Red glow effect (20px/40px)
|
|
3022
|
+
- `--shadow-deep`: 0 10px 40px rgba(0, 0, 0, 0.6)
|
|
3023
|
+
- `--shadow-text-glow`: Text-specific Royal Purple glow (20px/40px)
|
|
3024
|
+
|
|
3025
|
+
**Glass Effect (Modern Blur)**
|
|
3026
|
+
- `--glass-panel-background`: rgba(255, 255, 255, 0.05) - Subtle glass panel
|
|
3027
|
+
- `--glass-panel-blur`: 12px - Modern blur effect
|
|
3028
|
+
|
|
3029
|
+
#### Motion
|
|
3030
|
+
|
|
3031
|
+
**Easing Curves**
|
|
3032
|
+
- `--ease-luxe`: cubic-bezier(0.23, 1, 0.32, 1) - Smooth luxury motion
|
|
3033
|
+
- `--ease-sharp`: cubic-bezier(0.4, 0.1, 0.2, 1) - Snappy transitions
|
|
3034
|
+
|
|
3035
|
+
**Duration Scale**
|
|
3036
|
+
- `--duration-75` through `--duration-1000` (75ms, 100ms, 150ms, 200ms, 300ms, 500ms, 700ms, 1000ms)
|
|
3037
|
+
|
|
3038
|
+
**Opacity Scale**
|
|
3039
|
+
- `--opacity-0` through `--opacity-100` in increments of 5-10
|
|
3040
|
+
|
|
3041
|
+
### Custom Utility Classes
|
|
3042
|
+
|
|
3043
|
+
The library provides custom utility classes you can use directly in your markup:
|
|
3044
|
+
|
|
3045
|
+
**Visual Effects:**
|
|
3046
|
+
- `.text-glow` - Apply accent glow to text with shadow effect
|
|
3047
|
+
- `.accent-glow` - Accent color glow box-shadow effect
|
|
3048
|
+
- `.glass-panel` - Glass morphism with backdrop blur (14px), semi-transparent background, border, and deep shadow
|
|
3049
|
+
- `.hover-lift` - Lift element 2px on hover with combined glow and shadow
|
|
3050
|
+
|
|
3051
|
+
**Surface Treatments:**
|
|
3052
|
+
- `.metal-edge` - Specular metallic highlights on top and left borders
|
|
3053
|
+
- `.obsidian-surface` - Flat black surface with subtle diagonal gradient reflection
|
|
3054
|
+
|
|
3055
|
+
Example usage:
|
|
3056
|
+
|
|
3057
|
+
```svelte
|
|
3058
|
+
<div class="glass-panel accent-glow hover-lift">
|
|
3059
|
+
<h2 class="text-glow">Glowing Title</h2>
|
|
3060
|
+
<div class="obsidian-surface metal-edge">
|
|
3061
|
+
<p>Premium surface treatment</p>
|
|
3062
|
+
</div>
|
|
3063
|
+
</div>
|
|
3064
|
+
```
|
|
3065
|
+
|
|
3066
|
+
### Animation System
|
|
3067
|
+
|
|
3068
|
+
The theme includes pre-defined keyframe animations ready to use with Tailwind's `animate-*` utilities or custom CSS:
|
|
3069
|
+
|
|
3070
|
+
**Opacity Animations:**
|
|
3071
|
+
- `fade-in` - Fade from 0 to full opacity
|
|
3072
|
+
- `fade-out` - Fade from full to 0 opacity
|
|
3073
|
+
|
|
3074
|
+
**Combined Animations:**
|
|
3075
|
+
- `fade-up` - Fade in while moving up 20px
|
|
3076
|
+
|
|
3077
|
+
**Glow Effects:**
|
|
3078
|
+
- `glow-pulse` - Pulsing accent glow (40%-60% intensity cycle)
|
|
3079
|
+
|
|
3080
|
+
**Directional Slides:**
|
|
3081
|
+
- `slide-in-left` - Slide in from left with fade
|
|
3082
|
+
- `slide-in-right` - Slide in from right with fade
|
|
3083
|
+
- `slide-in-top` - Slide in from top with fade
|
|
3084
|
+
- `slide-in-bottom` - Slide in from bottom with fade
|
|
3085
|
+
|
|
3086
|
+
Example usage:
|
|
3087
|
+
|
|
3088
|
+
```css
|
|
3089
|
+
.my-element {
|
|
3090
|
+
animation: fade-up 0.3s var(--ease-luxe);
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
.my-modal {
|
|
3094
|
+
animation: fade-in 0.2s var(--ease-sharp);
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
.my-button:hover {
|
|
3098
|
+
animation: glow-pulse 2s infinite;
|
|
3099
|
+
}
|
|
3100
|
+
```
|
|
3101
|
+
|
|
3102
|
+
### Theme Persistence
|
|
3103
|
+
|
|
3104
|
+
The brand structure (depth system, spacing, motion curves) persists even when colors change, maintaining the luxury aesthetic regardless of your chosen palette.
|
|
3105
|
+
|
|
3106
|
+
## 🏗️ Layout Best Practices
|
|
3107
|
+
|
|
3108
|
+
### Navbar + Sidebar Layout
|
|
3109
|
+
|
|
3110
|
+
When using both Navbar and Sidebar together, they automatically coordinate their positioning. The Sidebar detects the Navbar's presence and adjusts its top position to start just below it. Here's the recommended structure:
|
|
3111
|
+
|
|
3112
|
+
```svelte
|
|
3113
|
+
<script>
|
|
3114
|
+
import { page } from '$app/stores';
|
|
3115
|
+
import { Navbar, Sidebar } from '@mrintel/villain-ui';
|
|
3116
|
+
|
|
3117
|
+
let sidebarOpen = $state(true);
|
|
3118
|
+
$: currentPath = $page.url.pathname;
|
|
3119
|
+
</script>
|
|
3120
|
+
|
|
3121
|
+
<!-- Navbar sits on top with z-50 and gets data-navbar attribute automatically -->
|
|
3122
|
+
<Navbar position="sticky" height="md" currentPath={currentPath}>
|
|
3123
|
+
{#snippet logo()}
|
|
3124
|
+
<YourLogo />
|
|
3125
|
+
{/snippet}
|
|
3126
|
+
|
|
3127
|
+
<a href="/">Home</a>
|
|
3128
|
+
<a href="/about">About</a>
|
|
3129
|
+
<a href="/contact">Contact</a>
|
|
3130
|
+
</Navbar>
|
|
3131
|
+
|
|
3132
|
+
<!-- Sidebar automatically detects Navbar and positions below it (z-40) -->
|
|
3133
|
+
<Sidebar bind:open={sidebarOpen} side="left" width="md" currentPath={currentPath}>
|
|
3134
|
+
<nav>
|
|
3135
|
+
<a href="/dashboard">
|
|
3136
|
+
<DashboardIcon class="w-5 h-5" />
|
|
3137
|
+
<span>Dashboard</span>
|
|
3138
|
+
</a>
|
|
3139
|
+
<a href="/analytics">
|
|
3140
|
+
<ChartIcon class="w-5 h-5" />
|
|
3141
|
+
<span>Analytics</span>
|
|
3142
|
+
</a>
|
|
3143
|
+
<a href="/settings">
|
|
3144
|
+
<SettingsIcon class="w-5 h-5" />
|
|
3145
|
+
<span>Settings</span>
|
|
3146
|
+
</a>
|
|
3147
|
+
</nav>
|
|
3148
|
+
</Sidebar>
|
|
3149
|
+
|
|
3150
|
+
<!-- Main content with appropriate margin/padding -->
|
|
3151
|
+
<main class="ml-64 p-6">
|
|
3152
|
+
<!-- Adjust ml-64 based on sidebar width (sm: 56, md: 64, lg: 80) -->
|
|
3153
|
+
<!-- No margin-top needed - Sidebar handles its own positioning! -->
|
|
3154
|
+
<slot />
|
|
3155
|
+
</main>
|
|
3156
|
+
```
|
|
3157
|
+
|
|
3158
|
+
**Tips:**
|
|
3159
|
+
- **Automatic positioning**: Sidebar detects Navbar via `data-navbar` attribute and adjusts its `top` position automatically. No manual `margin-top` needed on Sidebar.
|
|
3160
|
+
- **Z-index layering**: Navbar (z-50) appears above Sidebar (z-40), which appears above regular content
|
|
3161
|
+
- **Responsive behavior**: Sidebar's position updates automatically when Navbar height changes (window resize, responsive breakpoints)
|
|
3162
|
+
- **Main content spacing**: Add `margin-left` to your main content based on sidebar width:
|
|
3163
|
+
- `width="sm"` (224px/56 Tailwind units): Use `ml-56`
|
|
3164
|
+
- `width="md"` (256px/64 Tailwind units): Use `ml-64` (default)
|
|
3165
|
+
- `width="lg"` (320px/80 Tailwind units): Use `ml-80`
|
|
3166
|
+
- Collapsed sidebar: Reduce margin to `ml-14`, `ml-16`, or `ml-20` respectively
|
|
3167
|
+
- **Mobile responsive**: Use Tailwind responsive classes: `md:ml-64` to adjust layout on mobile
|
|
3168
|
+
- **Collapsed sidebar**: Consider making sidebar collapsed by default on mobile: `let sidebarOpen = $state(window.innerWidth >= 768)`
|
|
3169
|
+
- **Without Navbar**: When Navbar is not present, Sidebar automatically starts from the top (top: 0)
|
|
3170
|
+
- **Active state**: Use the `currentPath` prop on both components for automatic active state management (see Active State Management section)
|
|
3171
|
+
|
|
3172
|
+
#### Layout Variations
|
|
3173
|
+
|
|
3174
|
+
**Navbar Only (No Sidebar):**
|
|
3175
|
+
```svelte
|
|
3176
|
+
<Navbar position="sticky" currentPath={currentPath}>
|
|
3177
|
+
<!-- Navigation links -->
|
|
3178
|
+
</Navbar>
|
|
3179
|
+
|
|
3180
|
+
<main class="p-6">
|
|
3181
|
+
<!-- No margin-left needed -->
|
|
3182
|
+
<slot />
|
|
3183
|
+
</main>
|
|
3184
|
+
```
|
|
3185
|
+
|
|
3186
|
+
**Sidebar Only (No Navbar):**
|
|
3187
|
+
```svelte
|
|
3188
|
+
<Sidebar open={true} currentPath={currentPath}>
|
|
3189
|
+
<!-- Navigation links -->
|
|
3190
|
+
</Sidebar>
|
|
3191
|
+
|
|
3192
|
+
<main class="ml-64 p-6">
|
|
3193
|
+
<!-- Sidebar starts from top automatically -->
|
|
3194
|
+
<slot />
|
|
3195
|
+
</main>
|
|
3196
|
+
```
|
|
3197
|
+
|
|
3198
|
+
**Both with Collapsible Sidebar:**
|
|
3199
|
+
```svelte
|
|
3200
|
+
<script>
|
|
3201
|
+
let sidebarOpen = $state(true);
|
|
3202
|
+
$: mainMargin = sidebarOpen ? 'ml-64' : 'ml-16'; // Adjust for collapsed width
|
|
3203
|
+
</script>
|
|
3204
|
+
|
|
3205
|
+
<Navbar position="sticky" currentPath={currentPath}>
|
|
3206
|
+
<button onclick={() => sidebarOpen = !sidebarOpen}>
|
|
3207
|
+
<MenuIcon />
|
|
3208
|
+
</button>
|
|
3209
|
+
<!-- Other nav items -->
|
|
3210
|
+
</Navbar>
|
|
3211
|
+
|
|
3212
|
+
<Sidebar bind:open={sidebarOpen} currentPath={currentPath}>
|
|
3213
|
+
<!-- Navigation links -->
|
|
3214
|
+
</Sidebar>
|
|
3215
|
+
|
|
3216
|
+
<main class="{mainMargin} p-6 transition-all duration-300">
|
|
3217
|
+
<slot />
|
|
3218
|
+
</main>
|
|
3219
|
+
```
|
|
3220
|
+
|
|
3221
|
+
### Active State Management with currentPath Prop
|
|
3222
|
+
|
|
3223
|
+
Navbar and Sidebar components now support automatic active state management via the optional `currentPath` prop. When provided, the components automatically add the `active` class to child `<a>` and `<button>` elements whose `href` attribute matches the current path. This eliminates the need for manual class management while preserving support for manual `.active` class usage.
|
|
3224
|
+
|
|
3225
|
+
#### How It Works
|
|
3226
|
+
|
|
3227
|
+
- When `currentPath` is provided, components use a `$effect` to query child links and buttons
|
|
3228
|
+
- Elements with `href` matching `currentPath` automatically receive the `active` class
|
|
3229
|
+
- The effect tracks which elements it manages to avoid removing manually-added `active` classes
|
|
3230
|
+
- Works with both `href` attributes and `data-href` attributes (for buttons)
|
|
3231
|
+
- Reactive: updates automatically when `currentPath` changes
|
|
3232
|
+
- SSR-safe: only runs client-side
|
|
3233
|
+
|
|
3234
|
+
#### With SvelteKit
|
|
3235
|
+
|
|
3236
|
+
```svelte
|
|
3237
|
+
<script>
|
|
3238
|
+
import { page } from '$app/stores';
|
|
3239
|
+
import { Navbar, Sidebar } from '@mrintel/villain-ui';
|
|
3240
|
+
|
|
3241
|
+
// Automatically reactive - updates when route changes
|
|
3242
|
+
$: currentPath = $page.url.pathname;
|
|
3243
|
+
</script>
|
|
3244
|
+
|
|
3245
|
+
<!-- Navbar with automatic active state -->
|
|
3246
|
+
<Navbar currentPath={currentPath}>
|
|
3247
|
+
<a href="/">Home</a>
|
|
3248
|
+
<a href="/about">About</a>
|
|
3249
|
+
<a href="/contact">Contact</a>
|
|
3250
|
+
</Navbar>
|
|
3251
|
+
|
|
3252
|
+
<!-- Sidebar with automatic active state -->
|
|
3253
|
+
<Sidebar currentPath={currentPath}>
|
|
3254
|
+
<a href="/dashboard">Dashboard</a>
|
|
3255
|
+
<a href="/settings">Settings</a>
|
|
3256
|
+
<a href="/profile">Profile</a>
|
|
3257
|
+
</Sidebar>
|
|
3258
|
+
|
|
3259
|
+
<!-- No need to manually add class={currentPath === '/dashboard' ? 'active' : ''} -->
|
|
3260
|
+
```
|
|
3261
|
+
|
|
3262
|
+
#### With SvelteKit (Advanced Matching)
|
|
3263
|
+
|
|
3264
|
+
```svelte
|
|
3265
|
+
<script>
|
|
3266
|
+
import { page } from '$app/stores';
|
|
3267
|
+
import { Sidebar } from '@mrintel/villain-ui';
|
|
3268
|
+
|
|
3269
|
+
// For nested routes, you might want to match path prefixes
|
|
3270
|
+
$: currentPath = $page.url.pathname;
|
|
3271
|
+
|
|
3272
|
+
// Helper to check if a path is active (including sub-routes)
|
|
3273
|
+
function isActive(path: string) {
|
|
3274
|
+
return currentPath === path || currentPath.startsWith(path + '/');
|
|
3275
|
+
}
|
|
3276
|
+
</script>
|
|
3277
|
+
|
|
3278
|
+
<Sidebar currentPath={currentPath}>
|
|
3279
|
+
<a href="/dashboard">Dashboard</a>
|
|
3280
|
+
<!-- For nested routes, use manual class for prefix matching -->
|
|
3281
|
+
<a href="/settings" class={isActive('/settings') ? 'active' : ''}>
|
|
3282
|
+
Settings
|
|
3283
|
+
</a>
|
|
3284
|
+
</Sidebar>
|
|
3285
|
+
```
|
|
3286
|
+
|
|
3287
|
+
#### With Manual State
|
|
3288
|
+
|
|
3289
|
+
```svelte
|
|
3290
|
+
<script>
|
|
3291
|
+
import { Navbar } from '@mrintel/villain-ui';
|
|
3292
|
+
|
|
3293
|
+
let currentPath = $state('/home');
|
|
3294
|
+
|
|
3295
|
+
function navigate(path: string) {
|
|
3296
|
+
currentPath = path;
|
|
3297
|
+
// Your custom navigation logic
|
|
3298
|
+
}
|
|
3299
|
+
</script>
|
|
3300
|
+
|
|
3301
|
+
<Navbar currentPath={currentPath}>
|
|
3302
|
+
<a href="/home" onclick={(e) => { e.preventDefault(); navigate('/home'); }}>Home</a>
|
|
3303
|
+
<a href="/about" onclick={(e) => { e.preventDefault(); navigate('/about'); }}>About</a>
|
|
3304
|
+
<a href="/contact" onclick={(e) => { e.preventDefault(); navigate('/contact'); }}>Contact</a>
|
|
3305
|
+
</Navbar>
|
|
3306
|
+
```
|
|
3307
|
+
|
|
3308
|
+
#### Hybrid Approach (Manual + Automatic)
|
|
3309
|
+
|
|
3310
|
+
```svelte
|
|
3311
|
+
<script>
|
|
3312
|
+
import { page } from '$app/stores';
|
|
3313
|
+
import { Sidebar } from '@mrintel/villain-ui';
|
|
3314
|
+
|
|
3315
|
+
$: currentPath = $page.url.pathname;
|
|
3316
|
+
</script>
|
|
3317
|
+
|
|
3318
|
+
<Sidebar currentPath={currentPath}>
|
|
3319
|
+
<!-- These use automatic active state via currentPath -->
|
|
3320
|
+
<a href="/dashboard">Dashboard</a>
|
|
3321
|
+
<a href="/analytics">Analytics</a>
|
|
3322
|
+
|
|
3323
|
+
<!-- This uses manual active class (takes precedence) -->
|
|
3324
|
+
<a href="/special" class="active">Special Page (Always Active)</a>
|
|
3325
|
+
|
|
3326
|
+
<!-- Buttons work too with data-href -->
|
|
3327
|
+
<button data-href="/action" onclick={handleAction}>Action</button>
|
|
3328
|
+
</Sidebar>
|
|
3329
|
+
```
|
|
3330
|
+
|
|
3331
|
+
#### Best Practices
|
|
3332
|
+
|
|
3333
|
+
- Use `currentPath` prop for simple exact-match scenarios (most common)
|
|
3334
|
+
- For nested routes or prefix matching, combine `currentPath` with manual classes
|
|
3335
|
+
- Manual `.active` classes always take precedence over automatic management
|
|
3336
|
+
- The `currentPath` prop is optional - components work perfectly without it
|
|
3337
|
+
- For buttons, use `data-href` attribute to enable automatic active state
|
|
3338
|
+
- Consider using `$page.url.pathname` in SvelteKit for automatic reactivity
|
|
3339
|
+
|
|
3340
|
+
## 📘 TypeScript Support
|
|
3341
|
+
|
|
3342
|
+
@mrintel/villain-ui is built with TypeScript in strict mode and includes complete type definitions.
|
|
3343
|
+
|
|
3344
|
+
- ✅ Full TypeScript support
|
|
3345
|
+
- ✅ Strict mode enabled
|
|
3346
|
+
- ✅ Type definitions included in `dist/index.d.ts`
|
|
3347
|
+
- ✅ All components have typed Props interfaces
|
|
3348
|
+
- ✅ IntelliSense support in VS Code and other editors
|
|
3349
|
+
|
|
3350
|
+
### Using Component Types
|
|
3351
|
+
|
|
3352
|
+
Import types directly from components:
|
|
3353
|
+
|
|
3354
|
+
```typescript
|
|
3355
|
+
import type { Button } from '@mrintel/villain-ui';
|
|
3356
|
+
|
|
3357
|
+
// Component props are fully typed
|
|
3358
|
+
const props: ComponentProps<typeof Button> = {
|
|
3359
|
+
variant: 'primary',
|
|
3360
|
+
size: 'md',
|
|
3361
|
+
disabled: false
|
|
3362
|
+
};
|
|
3363
|
+
```
|
|
3364
|
+
|
|
3365
|
+
### Importing Component Prop Types
|
|
3366
|
+
|
|
3367
|
+
For type-safe usage, import component prop types directly:
|
|
3368
|
+
|
|
3369
|
+
```typescript
|
|
3370
|
+
import type {
|
|
3371
|
+
ButtonProps,
|
|
3372
|
+
InputProps,
|
|
3373
|
+
ModalProps,
|
|
3374
|
+
TabsProps
|
|
3375
|
+
} from '@mrintel/villain-ui';
|
|
3376
|
+
|
|
3377
|
+
// Use in your components
|
|
3378
|
+
const buttonConfig: ButtonProps = {
|
|
3379
|
+
variant: 'primary',
|
|
3380
|
+
size: 'md',
|
|
3381
|
+
disabled: false
|
|
3382
|
+
};
|
|
3383
|
+
|
|
3384
|
+
const modalConfig: ModalProps = {
|
|
3385
|
+
open: true,
|
|
3386
|
+
title: 'Confirmation',
|
|
3387
|
+
closeOnEscape: true
|
|
3388
|
+
};
|
|
3389
|
+
```
|
|
3390
|
+
|
|
3391
|
+
### Available Prop Type Exports
|
|
3392
|
+
|
|
3393
|
+
The following component prop types are exported for TypeScript users:
|
|
3394
|
+
|
|
3395
|
+
**Button Types:**
|
|
3396
|
+
- `ButtonProps` - Standard button component
|
|
3397
|
+
- `IconButtonProps` - Icon-only button
|
|
3398
|
+
- `FloatingActionButtonProps` - Floating action button (FAB)
|
|
3399
|
+
- `LinkButtonProps` - Link styled as button
|
|
3400
|
+
|
|
3401
|
+
**Form Types:**
|
|
3402
|
+
- `InputProps` - Text input component
|
|
3403
|
+
- `TextareaProps` - Multi-line text input
|
|
3404
|
+
- `SelectProps` - Dropdown select
|
|
3405
|
+
- `CheckboxProps` - Checkbox input
|
|
3406
|
+
- `SwitchProps` - Toggle switch
|
|
3407
|
+
- `RadioGroupProps` - Radio button group
|
|
3408
|
+
- `DatePickerProps` - Date picker component
|
|
3409
|
+
- `TimePickerProps` - Time picker component
|
|
3410
|
+
- `DateTimePickerProps` - Combined date and time picker
|
|
3411
|
+
|
|
3412
|
+
**Layout Types:**
|
|
3413
|
+
- `CardProps` - Content card
|
|
3414
|
+
|
|
3415
|
+
**Navigation Types:**
|
|
3416
|
+
- `TabsProps` - Tabbed interface
|
|
3417
|
+
|
|
3418
|
+
**Overlay Types:**
|
|
3419
|
+
- `ModalProps` - Modal dialog
|
|
3420
|
+
- `DrawerProps` - Slide-out drawer
|
|
3421
|
+
- `AlertProps` - Alert message
|
|
3422
|
+
- `TooltipProps` - Hover tooltip
|
|
3423
|
+
|
|
3424
|
+
**Utility Types:**
|
|
3425
|
+
- `AccordionProps` - Accordion container
|
|
3426
|
+
|
|
3427
|
+
**Data Types:**
|
|
3428
|
+
- `SparklineProps` - Sparkline chart component
|
|
3429
|
+
|
|
3430
|
+
**Note:** Additional component prop types may be added in future releases. Components without exported prop types can still be used with TypeScript through Svelte's built-in type inference.
|
|
3431
|
+
|
|
3432
|
+
### Type-Safe Event Handlers
|
|
3433
|
+
|
|
3434
|
+
All components support lowercase event handlers with proper typing:
|
|
3435
|
+
|
|
3436
|
+
```typescript
|
|
3437
|
+
import { Button } from '@mrintel/villain-ui';
|
|
3438
|
+
|
|
3439
|
+
<Button onclick={(event: MouseEvent) => {
|
|
3440
|
+
console.log('Button clicked', event);
|
|
3441
|
+
}}>
|
|
3442
|
+
Click Me
|
|
3443
|
+
</Button>
|
|
3444
|
+
```
|
|
3445
|
+
|
|
3446
|
+
## 🌐 Browser Support
|
|
3447
|
+
|
|
3448
|
+
@mrintel/villain-ui targets modern browsers that support:
|
|
3449
|
+
|
|
3450
|
+
- ✅ CSS Variables (Custom Properties)
|
|
3451
|
+
- ✅ CSS `backdrop-filter` for glass morphism
|
|
3452
|
+
- ✅ ES2022+ JavaScript features
|
|
3453
|
+
- ✅ Tailwind CSS v4 requirements
|
|
3454
|
+
|
|
3455
|
+
**Supported Browsers:**
|
|
3456
|
+
- Chrome/Edge 88+
|
|
3457
|
+
- Firefox 94+
|
|
3458
|
+
- Safari 15.4+
|
|
3459
|
+
- Opera 74+
|
|
3460
|
+
|
|
3461
|
+
## 📄 License
|
|
3462
|
+
|
|
3463
|
+
MIT License - see LICENSE file for details
|
|
3464
|
+
|
|
3465
|
+
## 🤝 Contributing
|
|
3466
|
+
|
|
3467
|
+
Contributions are welcome! Please feel free to submit issues and pull requests.
|
|
3468
|
+
|
|
3469
|
+
### Contributor Guidelines
|
|
3470
|
+
|
|
3471
|
+
1. **Before submitting a PR**, run `npm run validate` to ensure all checks pass
|
|
3472
|
+
2. Follow the existing code style and component patterns
|
|
3473
|
+
3. Add TypeScript types for all new components and props
|
|
3474
|
+
4. Export component prop interfaces as public types in `src/index.ts`
|
|
3475
|
+
5. Avoid creating circular imports between components
|
|
3476
|
+
6. Test your changes with the demo app in `demo-ui/`
|
|
3477
|
+
|
|
3478
|
+
### CI Enforcement
|
|
3479
|
+
|
|
3480
|
+
All pull requests are automatically validated via GitHub Actions CI, which runs:
|
|
3481
|
+
- TypeScript type checking
|
|
3482
|
+
- Circular import detection with madge
|
|
3483
|
+
- Full production build
|
|
3484
|
+
- Build artifact verification
|
|
3485
|
+
|
|
3486
|
+
The CI must pass before a PR can be merged.
|
|
3487
|
+
|
|
3488
|
+
---
|
|
3489
|
+
|
|
3490
|
+
**Built with ❤️ for the modern web**
|