@mrintel/villain-ui 0.2.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3490 -1296
  3. package/dist/components/buttons/Button.svelte +27 -0
  4. package/dist/components/buttons/Button.svelte.d.ts +14 -0
  5. package/dist/components/buttons/ButtonGroup.svelte +17 -0
  6. package/dist/components/buttons/ButtonGroup.svelte.d.ts +8 -0
  7. package/dist/components/buttons/FloatingActionButton.svelte +20 -0
  8. package/dist/components/buttons/FloatingActionButton.svelte.d.ts +12 -0
  9. package/dist/components/buttons/IconButton.svelte +23 -0
  10. package/dist/components/buttons/IconButton.svelte.d.ts +14 -0
  11. package/dist/components/buttons/LinkButton.svelte +24 -0
  12. package/dist/components/buttons/LinkButton.svelte.d.ts +15 -0
  13. package/dist/components/buttons/buttonClasses.d.ts +15 -0
  14. package/dist/components/buttons/buttonClasses.js +15 -0
  15. package/dist/components/buttons/index.d.ts +5 -0
  16. package/dist/components/buttons/index.js +5 -0
  17. package/dist/components/cards/Card.svelte +60 -0
  18. package/dist/components/cards/Card.svelte.d.ts +15 -0
  19. package/dist/components/cards/Container.svelte +17 -0
  20. package/dist/components/cards/Container.svelte.d.ts +10 -0
  21. package/dist/components/cards/Divider.svelte +36 -0
  22. package/dist/components/cards/Divider.svelte.d.ts +11 -0
  23. package/dist/components/cards/Grid.svelte +55 -0
  24. package/dist/components/cards/Grid.svelte.d.ts +10 -0
  25. package/dist/components/cards/Panel.svelte +18 -0
  26. package/dist/components/cards/Panel.svelte.d.ts +11 -0
  27. package/dist/components/cards/SectionHeader.svelte +24 -0
  28. package/dist/components/cards/SectionHeader.svelte.d.ts +12 -0
  29. package/dist/components/cards/index.d.ts +6 -0
  30. package/dist/components/cards/index.js +6 -0
  31. package/dist/components/data/Avatar.svelte +48 -0
  32. package/dist/components/data/Avatar.svelte.d.ts +10 -0
  33. package/dist/components/data/Badge.svelte +45 -0
  34. package/dist/components/data/Badge.svelte.d.ts +14 -0
  35. package/dist/components/data/CalendarGrid.svelte +433 -0
  36. package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
  37. package/dist/components/data/CalendarGrid.types.d.ts +7 -0
  38. package/dist/components/data/CalendarGrid.types.js +1 -0
  39. package/dist/components/data/CodeBlock.svelte +119 -0
  40. package/dist/components/data/CodeBlock.svelte.d.ts +40 -0
  41. package/dist/components/data/List.svelte +87 -0
  42. package/dist/components/data/List.svelte.d.ts +15 -0
  43. package/dist/components/data/Pagination.svelte +121 -0
  44. package/dist/components/data/Pagination.svelte.d.ts +14 -0
  45. package/dist/components/data/Sparkline.svelte +117 -0
  46. package/dist/components/data/Sparkline.svelte.d.ts +43 -0
  47. package/dist/components/data/Stat.svelte +92 -0
  48. package/dist/components/data/Stat.svelte.d.ts +11 -0
  49. package/dist/components/data/Table.svelte +443 -0
  50. package/dist/components/data/Table.svelte.d.ts +30 -0
  51. package/dist/components/data/Table.types.d.ts +14 -0
  52. package/dist/components/data/Table.types.js +1 -0
  53. package/dist/components/data/Tag.svelte +51 -0
  54. package/dist/components/data/Tag.svelte.d.ts +13 -0
  55. package/dist/components/data/index.d.ts +12 -0
  56. package/dist/components/data/index.js +10 -0
  57. package/dist/components/forms/Checkbox.svelte +39 -0
  58. package/dist/components/forms/Checkbox.svelte.d.ts +12 -0
  59. package/dist/components/forms/DatePicker.svelte +61 -0
  60. package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
  61. package/dist/components/forms/DateTimePicker.svelte +63 -0
  62. package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
  63. package/dist/components/forms/FileUpload.svelte +136 -0
  64. package/dist/components/forms/FileUpload.svelte.d.ts +23 -0
  65. package/dist/components/forms/Input.svelte +282 -0
  66. package/dist/components/forms/Input.svelte.d.ts +19 -0
  67. package/dist/components/forms/InputGroup.svelte +7 -0
  68. package/dist/components/forms/InputGroup.svelte.d.ts +20 -0
  69. package/dist/components/forms/RadioGroup.svelte +77 -0
  70. package/dist/components/forms/RadioGroup.svelte.d.ts +17 -0
  71. package/dist/components/forms/RangeSlider.svelte +90 -0
  72. package/dist/components/forms/RangeSlider.svelte.d.ts +14 -0
  73. package/dist/components/forms/Select.svelte +106 -0
  74. package/dist/components/forms/Select.svelte.d.ts +18 -0
  75. package/dist/components/forms/Switch.svelte +44 -0
  76. package/dist/components/forms/Switch.svelte.d.ts +12 -0
  77. package/dist/components/forms/Textarea.svelte +52 -0
  78. package/dist/components/forms/Textarea.svelte.d.ts +15 -0
  79. package/dist/components/forms/TimePicker.svelte +63 -0
  80. package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
  81. package/dist/components/forms/formClasses.d.ts +3 -0
  82. package/dist/components/forms/formClasses.js +3 -0
  83. package/dist/components/forms/index.d.ts +12 -0
  84. package/dist/components/forms/index.js +12 -0
  85. package/dist/components/navigation/Breadcrumbs.svelte +56 -0
  86. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +15 -0
  87. package/dist/components/navigation/ContextMenu.svelte +133 -0
  88. package/dist/components/navigation/ContextMenu.svelte.d.ts +18 -0
  89. package/dist/components/navigation/DropdownMenu.svelte +139 -0
  90. package/dist/components/navigation/DropdownMenu.svelte.d.ts +17 -0
  91. package/dist/components/navigation/Menu.svelte +72 -0
  92. package/dist/components/navigation/Menu.svelte.d.ts +15 -0
  93. package/dist/components/navigation/Navbar.svelte +111 -0
  94. package/dist/components/navigation/Navbar.svelte.d.ts +15 -0
  95. package/dist/components/navigation/Sidebar.svelte +236 -0
  96. package/dist/components/navigation/Sidebar.svelte.d.ts +12 -0
  97. package/dist/components/navigation/Tabs.svelte +86 -0
  98. package/dist/components/navigation/Tabs.svelte.d.ts +19 -0
  99. package/dist/components/navigation/index.d.ts +7 -0
  100. package/dist/components/navigation/index.js +7 -0
  101. package/dist/components/overlays/Alert.svelte +81 -0
  102. package/dist/components/overlays/Alert.svelte.d.ts +15 -0
  103. package/dist/components/overlays/CommandPalette.svelte +182 -0
  104. package/dist/components/overlays/CommandPalette.svelte.d.ts +16 -0
  105. package/dist/components/overlays/Drawer.svelte +158 -0
  106. package/dist/components/overlays/Drawer.svelte.d.ts +16 -0
  107. package/dist/components/overlays/Dropdown.svelte +62 -0
  108. package/dist/components/overlays/Dropdown.svelte.d.ts +11 -0
  109. package/dist/components/overlays/Modal.svelte +125 -0
  110. package/dist/components/overlays/Modal.svelte.d.ts +15 -0
  111. package/dist/components/overlays/Popover.svelte +106 -0
  112. package/dist/components/overlays/Popover.svelte.d.ts +11 -0
  113. package/dist/components/overlays/ProgressBar.svelte +29 -0
  114. package/dist/components/overlays/ProgressBar.svelte.d.ts +10 -0
  115. package/dist/components/overlays/SkeletonLoader.svelte +66 -0
  116. package/dist/components/overlays/SkeletonLoader.svelte.d.ts +9 -0
  117. package/dist/components/overlays/Spinner.svelte +33 -0
  118. package/dist/components/overlays/Spinner.svelte.d.ts +7 -0
  119. package/dist/components/overlays/Toast.svelte +111 -0
  120. package/dist/components/overlays/Toast.svelte.d.ts +16 -0
  121. package/dist/components/overlays/Tooltip.svelte +94 -0
  122. package/dist/components/overlays/Tooltip.svelte.d.ts +12 -0
  123. package/dist/components/overlays/index.d.ts +11 -0
  124. package/dist/components/overlays/index.js +11 -0
  125. package/dist/components/typography/Code.svelte +10 -0
  126. package/dist/components/typography/Code.svelte.d.ts +6 -0
  127. package/dist/components/typography/Heading.svelte +15 -0
  128. package/dist/components/typography/Heading.svelte.d.ts +10 -0
  129. package/dist/components/typography/Text.svelte +21 -0
  130. package/dist/components/typography/Text.svelte.d.ts +10 -0
  131. package/dist/components/typography/index.d.ts +3 -0
  132. package/dist/components/typography/index.js +3 -0
  133. package/dist/components/utilities/Accordion.svelte +54 -0
  134. package/dist/components/utilities/Accordion.svelte.d.ts +17 -0
  135. package/dist/components/utilities/Carousel.svelte +124 -0
  136. package/dist/components/utilities/Carousel.svelte.d.ts +16 -0
  137. package/dist/components/utilities/Collapse.svelte +46 -0
  138. package/dist/components/utilities/Collapse.svelte.d.ts +10 -0
  139. package/dist/components/utilities/Hero.svelte +42 -0
  140. package/dist/components/utilities/Hero.svelte.d.ts +10 -0
  141. package/dist/components/utilities/Portal.svelte +47 -0
  142. package/dist/components/utilities/Portal.svelte.d.ts +21 -0
  143. package/dist/components/utilities/ScrollArea.svelte +33 -0
  144. package/dist/components/utilities/ScrollArea.svelte.d.ts +8 -0
  145. package/dist/components/utilities/SystemConsole.svelte +310 -0
  146. package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
  147. package/dist/components/utilities/SystemInterface.svelte +726 -0
  148. package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
  149. package/dist/components/utilities/index.d.ts +9 -0
  150. package/dist/components/utilities/index.js +8 -0
  151. package/dist/components/utilities/utilities.types.d.ts +46 -0
  152. package/dist/components/utilities/utilities.types.js +4 -0
  153. package/dist/index.d.ts +60 -175
  154. package/dist/index.js +24 -4560
  155. package/dist/lib/internal/id.d.ts +12 -0
  156. package/dist/lib/internal/id.js +15 -0
  157. package/dist/theme.css +2821 -0
  158. package/package.json +83 -75
  159. package/dist/index.css +0 -1
package/README.md CHANGED
@@ -1,1296 +1,3490 @@
1
- # @mrintel/villain-ui
2
-
3
- [![npm version](https://img.shields.io/npm/v/@mrintel/villain-ui.svg)](https://www.npmjs.com/package/@mrintel/villain-ui)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
-
6
- A luxury Svelte 5 component library with a distinctive dark aesthetic. Built for modern web applications that demand elegance, performance, 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
- - **🎨 Luxury Dark Aesthetic** - Premium glass morphism, accent glows, and sophisticated depth
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
- ## 📚 Components
87
-
88
- ### Buttons
89
-
90
- **Button** - Primary interactive element with variants
91
-
92
- ```svelte
93
- <script>
94
- import { Button } from '@mrintel/villain-ui';
95
- </script>
96
-
97
- <Button variant="primary" size="md">Primary Button</Button>
98
- <Button variant="secondary" size="md">Secondary Button</Button>
99
- <Button variant="ghost" size="sm">Ghost Button</Button>
100
- <Button variant="primary" size="lg" disabled>Disabled</Button>
101
- ```
102
-
103
- **IconButton** - Compact button for icon-only interactions
104
-
105
- ```svelte
106
- <script>
107
- import { IconButton } from '@mrintel/villain-ui';
108
- </script>
109
-
110
- <IconButton variant="primary" size="md" aria-label="Settings">
111
- <SettingsIcon />
112
- </IconButton>
113
- ```
114
-
115
- **ButtonGroup** - Group related buttons together
116
-
117
- ```svelte
118
- <script>
119
- import { ButtonGroup, Button } from '@mrintel/villain-ui';
120
- </script>
121
-
122
- <ButtonGroup>
123
- <Button variant="secondary">Left</Button>
124
- <Button variant="secondary">Center</Button>
125
- <Button variant="secondary">Right</Button>
126
- </ButtonGroup>
127
- ```
128
-
129
- **LinkButton** - Button styled as link
130
-
131
- ```svelte
132
- <script>
133
- import { LinkButton } from '@mrintel/villain-ui';
134
- </script>
135
-
136
- <LinkButton href="/docs" variant="primary">View Documentation</LinkButton>
137
- ```
138
-
139
- **FloatingActionButton** - Prominent floating action button
140
-
141
- ```svelte
142
- <script>
143
- import { FloatingActionButton } from '@mrintel/villain-ui';
144
- </script>
145
-
146
- <FloatingActionButton position="bottom-right" onclick={() => console.log('FAB clicked')}>
147
- <PlusIcon />
148
- </FloatingActionButton>
149
- ```
150
-
151
- ### Forms
152
-
153
- **Input** - Text input with label and error states
154
-
155
- ```svelte
156
- <script>
157
- import { Input } from '@mrintel/villain-ui';
158
-
159
- let email = $state('');
160
- let hasError = $state(false);
161
- </script>
162
-
163
- <Input
164
- type="email"
165
- label="Email Address"
166
- placeholder="you@example.com"
167
- bind:value={email}
168
- error={hasError}
169
- />
170
- ```
171
-
172
- **Textarea** - Multi-line text input
173
-
174
- ```svelte
175
- <script>
176
- import { Textarea } from '@mrintel/villain-ui';
177
-
178
- let comment = $state('');
179
- </script>
180
-
181
- <Textarea
182
- label="Comment"
183
- placeholder="Enter your comment..."
184
- rows={5}
185
- bind:value={comment}
186
- />
187
- ```
188
-
189
- **Select** - Dropdown selection
190
-
191
- ```svelte
192
- <script>
193
- import { Select } from '@mrintel/villain-ui';
194
-
195
- let selected = $state('');
196
- const options = [
197
- { value: 'option1', label: 'Option 1' },
198
- { value: 'option2', label: 'Option 2' },
199
- { value: 'option3', label: 'Option 3' }
200
- ];
201
- </script>
202
-
203
- <Select label="Choose an option" {options} bind:value={selected} />
204
- ```
205
-
206
- **Checkbox** - Boolean selection
207
-
208
- ```svelte
209
- <script>
210
- import { Checkbox } from '@mrintel/villain-ui';
211
-
212
- let accepted = $state(false);
213
- </script>
214
-
215
- <Checkbox label="I accept the terms and conditions" bind:checked={accepted} />
216
- ```
217
-
218
- **Switch** - Toggle switch
219
-
220
- ```svelte
221
- <script>
222
- import { Switch } from '@mrintel/villain-ui';
223
-
224
- let enabled = $state(false);
225
- </script>
226
-
227
- <Switch label="Enable notifications" bind:checked={enabled} />
228
- ```
229
-
230
- **RadioGroup** - Single selection from multiple options
231
-
232
- ```svelte
233
- <script>
234
- import { RadioGroup } from '@mrintel/villain-ui';
235
-
236
- let selected = $state('');
237
- const options = [
238
- { value: 'small', label: 'Small' },
239
- { value: 'medium', label: 'Medium' },
240
- { value: 'large', label: 'Large' }
241
- ];
242
- </script>
243
-
244
- <RadioGroup label="Select size" {options} bind:value={selected} />
245
- ```
246
-
247
- **RangeSlider** - Numeric range selection
248
-
249
- ```svelte
250
- <script>
251
- import { RangeSlider } from '@mrintel/villain-ui';
252
-
253
- let volume = $state(50);
254
- </script>
255
-
256
- <RangeSlider label="Volume" min={0} max={100} bind:value={volume} />
257
- ```
258
-
259
- **FileUpload** - File selection with drag & drop
260
-
261
- ```svelte
262
- <script>
263
- import { FileUpload } from '@mrintel/villain-ui';
264
-
265
- function handleUpload(files) {
266
- console.log('Files:', files);
267
- }
268
- </script>
269
-
270
- <FileUpload
271
- accept="image/*"
272
- multiple
273
- onchange={handleUpload}
274
- label="Upload Images"
275
- />
276
- ```
277
-
278
- **InputGroup** - Grouped input with addons
279
-
280
- ```svelte
281
- <script>
282
- import { InputGroup } from '@mrintel/villain-ui';
283
- </script>
284
-
285
- <InputGroup>
286
- {#snippet prepend()}
287
- https://
288
- {/snippet}
289
-
290
- <input type="text" placeholder="example.com" />
291
-
292
- {#snippet append()}
293
- .com
294
- {/snippet}
295
- </InputGroup>
296
- ```
297
-
298
- ### Layout
299
-
300
- **Card** - Content container with optional header and footer
301
-
302
- ```svelte
303
- <script>
304
- import { Card } from '@mrintel/villain-ui';
305
- </script>
306
-
307
- <Card padding="lg" hoverable>
308
- {#snippet header()}
309
- <h2>Card Title</h2>
310
- {/snippet}
311
-
312
- <p>Card content goes here with beautiful glass morphism effect.</p>
313
-
314
- {#snippet footer()}
315
- <Button variant="primary">Action</Button>
316
- {/snippet}
317
- </Card>
318
- ```
319
-
320
- **Panel** - Simple content panel
321
-
322
- ```svelte
323
- <script>
324
- import { Panel } from '@mrintel/villain-ui';
325
- </script>
326
-
327
- <Panel>
328
- <p>Panel content with default styling</p>
329
- </Panel>
330
- ```
331
-
332
- **Grid** - Responsive grid layout
333
-
334
- ```svelte
335
- <script>
336
- import { Grid, Card } from '@mrintel/villain-ui';
337
- </script>
338
-
339
- <Grid columns={3} gap="lg">
340
- <Card>Item 1</Card>
341
- <Card>Item 2</Card>
342
- <Card>Item 3</Card>
343
- </Grid>
344
- ```
345
-
346
- **Container** - Centered content container
347
-
348
- ```svelte
349
- <script>
350
- import { Container } from '@mrintel/villain-ui';
351
- </script>
352
-
353
- <Container maxWidth="lg">
354
- <h1>Centered Content</h1>
355
- </Container>
356
- ```
357
-
358
- **SectionHeader** - Section heading with divider
359
-
360
- ```svelte
361
- <script>
362
- import { SectionHeader } from '@mrintel/villain-ui';
363
- </script>
364
-
365
- <SectionHeader title="Features" subtitle="What makes us different" />
366
- ```
367
-
368
- **Divider** - Visual separator
369
-
370
- ```svelte
371
- <script>
372
- import { Divider } from '@mrintel/villain-ui';
373
- </script>
374
-
375
- <Divider />
376
- <Divider orientation="vertical" />
377
- ```
378
-
379
- ### Navigation
380
-
381
- **Navbar** - Top navigation bar
382
-
383
- ```svelte
384
- <script>
385
- import { Navbar } from '@mrintel/villain-ui';
386
- </script>
387
-
388
- <Navbar>
389
- {#snippet logo()}
390
- <Logo />
391
- {/snippet}
392
-
393
- {#snippet links()}
394
- <a href="/">Home</a>
395
- <a href="/about">About</a>
396
- <a href="/contact">Contact</a>
397
- {/snippet}
398
-
399
- {#snippet actions()}
400
- <Button variant="primary">Sign In</Button>
401
- {/snippet}
402
- </Navbar>
403
- ```
404
-
405
- **Sidebar** - Side navigation with collapsible state
406
-
407
- ```svelte
408
- <script>
409
- import { Sidebar } from '@mrintel/villain-ui';
410
-
411
- let collapsed = $state(false);
412
- </script>
413
-
414
- <Sidebar bind:collapsed>
415
- <nav>
416
- <a href="/dashboard">Dashboard</a>
417
- <a href="/settings">Settings</a>
418
- <a href="/profile">Profile</a>
419
- </nav>
420
- </Sidebar>
421
- ```
422
-
423
- **Tabs** - Tabbed interface
424
-
425
- ```svelte
426
- <script>
427
- import { Tabs } from '@mrintel/villain-ui';
428
-
429
- let activeTab = $state('tab1');
430
- const tabs = [
431
- { id: 'tab1', label: 'Overview' },
432
- { id: 'tab2', label: 'Analytics' },
433
- { id: 'tab3', label: 'Reports' }
434
- ];
435
- </script>
436
-
437
- <Tabs {tabs} bind:activeTab>
438
- {#if activeTab === 'tab1'}
439
- <div>Overview content</div>
440
- {:else if activeTab === 'tab2'}
441
- <div>Analytics content</div>
442
- {:else}
443
- <div>Reports content</div>
444
- {/if}
445
- </Tabs>
446
- ```
447
-
448
- **Breadcrumbs** - Navigation breadcrumb trail
449
-
450
- ```svelte
451
- <script>
452
- import { Breadcrumbs } from '@mrintel/villain-ui';
453
-
454
- const items = [
455
- { label: 'Home', href: '/' },
456
- { label: 'Products', href: '/products' },
457
- { label: 'Category', href: '/products/category' },
458
- { label: 'Item' }
459
- ];
460
- </script>
461
-
462
- <Breadcrumbs {items} />
463
- ```
464
-
465
- **Menu** - Vertical navigation menu
466
-
467
- ```svelte
468
- <script>
469
- import { Menu } from '@mrintel/villain-ui';
470
-
471
- const items = [
472
- { label: 'Dashboard', icon: DashboardIcon, href: '/dashboard' },
473
- { label: 'Settings', icon: SettingsIcon, href: '/settings' }
474
- ];
475
- </script>
476
-
477
- <Menu {items} />
478
- ```
479
-
480
- **DropdownMenu** - Dropdown menu with items
481
-
482
- ```svelte
483
- <script>
484
- import { DropdownMenu } from '@mrintel/villain-ui';
485
-
486
- const items = [
487
- { label: 'Edit', onclick: () => console.log('Edit') },
488
- { label: 'Delete', onclick: () => console.log('Delete') }
489
- ];
490
- </script>
491
-
492
- <DropdownMenu {items} trigger="Options" />
493
- ```
494
-
495
- **ContextMenu** - Right-click context menu
496
-
497
- ```svelte
498
- <script>
499
- import { ContextMenu } from '@mrintel/villain-ui';
500
-
501
- const items = [
502
- { label: 'Copy', onclick: () => console.log('Copy') },
503
- { label: 'Paste', onclick: () => console.log('Paste') }
504
- ];
505
- </script>
506
-
507
- <ContextMenu {items}>
508
- <div>Right click me</div>
509
- </ContextMenu>
510
- ```
511
-
512
- ### Overlays & Feedback
513
-
514
- **Modal** - Modal dialog with backdrop
515
-
516
- ```svelte
517
- <script>
518
- import { Modal, Button } from '@mrintel/villain-ui';
519
-
520
- let open = $state(false);
521
- </script>
522
-
523
- <Button onclick={() => open = true}>Open Modal</Button>
524
-
525
- <Modal bind:open title="Confirm Action">
526
- <p>Are you sure you want to proceed?</p>
527
-
528
- {#snippet footer()}
529
- <Button variant="ghost" onclick={() => open = false}>Cancel</Button>
530
- <Button variant="primary" onclick={() => open = false}>Confirm</Button>
531
- {/snippet}
532
- </Modal>
533
- ```
534
-
535
- **Alert** - Alert message with variants
536
-
537
- ```svelte
538
- <script>
539
- import { Alert } from '@mrintel/villain-ui';
540
- </script>
541
-
542
- <Alert variant="success" title="Success">
543
- Operation completed successfully!
544
- </Alert>
545
-
546
- <Alert variant="warning" title="Warning">
547
- Please review your changes.
548
- </Alert>
549
-
550
- <Alert variant="error" title="Error">
551
- An error occurred.
552
- </Alert>
553
- ```
554
-
555
- **Spinner** - Loading spinner
556
-
557
- ```svelte
558
- <script>
559
- import { Spinner } from '@mrintel/villain-ui';
560
- </script>
561
-
562
- <Spinner size="lg" />
563
- ```
564
-
565
- **Tooltip** - Hover tooltip
566
-
567
- ```svelte
568
- <script>
569
- import { Tooltip } from '@mrintel/villain-ui';
570
- </script>
571
-
572
- <Tooltip content="This is a helpful tip" position="top">
573
- <Button>Hover me</Button>
574
- </Tooltip>
575
- ```
576
-
577
- **ProgressBar** - Progress indicator
578
-
579
- ```svelte
580
- <script>
581
- import { ProgressBar } from '@mrintel/villain-ui';
582
-
583
- let progress = $state(45);
584
- </script>
585
-
586
- <ProgressBar value={progress} max={100} showLabel />
587
- ```
588
-
589
- **SkeletonLoader** - Content loading placeholder
590
-
591
- ```svelte
592
- <script>
593
- import { SkeletonLoader } from '@mrintel/villain-ui';
594
- </script>
595
-
596
- <SkeletonLoader variant="text" count={3} />
597
- <SkeletonLoader variant="circle" width="60px" height="60px" />
598
- <SkeletonLoader variant="rectangle" width="100%" height="200px" />
599
- ```
600
-
601
- **Toast** - Notification toast
602
-
603
- ```svelte
604
- <script>
605
- import { Toast } from '@mrintel/villain-ui';
606
-
607
- let show = $state(false);
608
- </script>
609
-
610
- <Toast bind:show variant="success" duration={3000}>
611
- Changes saved successfully!
612
- </Toast>
613
- ```
614
-
615
- **Drawer** - Slide-out drawer panel
616
-
617
- ```svelte
618
- <script>
619
- import { Drawer, Button } from '@mrintel/villain-ui';
620
-
621
- let open = $state(false);
622
- </script>
623
-
624
- <Button onclick={() => open = true}>Open Drawer</Button>
625
-
626
- <Drawer bind:open position="right">
627
- <h2>Drawer Content</h2>
628
- <p>This slides in from the side.</p>
629
- </Drawer>
630
- ```
631
-
632
- **Popover** - Popover content
633
-
634
- ```svelte
635
- <script>
636
- import { Popover } from '@mrintel/villain-ui';
637
- </script>
638
-
639
- <Popover>
640
- {#snippet trigger()}
641
- <Button>Click me</Button>
642
- {/snippet}
643
-
644
- {#snippet content()}
645
- <div>Popover content here</div>
646
- {/snippet}
647
- </Popover>
648
- ```
649
-
650
- **Dropdown** - Generic dropdown container
651
-
652
- ```svelte
653
- <script>
654
- import { Dropdown } from '@mrintel/villain-ui';
655
- </script>
656
-
657
- <Dropdown trigger="Select Option">
658
- <a href="#">Option 1</a>
659
- <a href="#">Option 2</a>
660
- <a href="#">Option 3</a>
661
- </Dropdown>
662
- ```
663
-
664
- **CommandPalette** - Command palette with fuzzy search
665
-
666
- ```svelte
667
- <script>
668
- import { CommandPalette } from '@mrintel/villain-ui';
669
-
670
- let open = $state(false);
671
- const commands = [
672
- { id: '1', label: 'New File', onSelect: () => console.log('New File') },
673
- { id: '2', label: 'Open Settings', onSelect: () => console.log('Settings') }
674
- ];
675
- </script>
676
-
677
- <CommandPalette bind:open {commands} placeholder="Search commands..." />
678
- ```
679
-
680
- ### Typography
681
-
682
- **Heading** - Semantic heading levels
683
-
684
- ```svelte
685
- <script>
686
- import { Heading } from '@mrintel/villain-ui';
687
- </script>
688
-
689
- <Heading level={1}>Main Title</Heading>
690
- <Heading level={2}>Section Title</Heading>
691
- <Heading level={3}>Subsection</Heading>
692
- ```
693
-
694
- **Text** - Text with variants
695
-
696
- ```svelte
697
- <script>
698
- import { Text } from '@mrintel/villain-ui';
699
- </script>
700
-
701
- <Text variant="body">Regular body text</Text>
702
- <Text variant="caption">Caption text</Text>
703
- <Text variant="muted">Muted text</Text>
704
- ```
705
-
706
- **Code** - Inline code
707
-
708
- ```svelte
709
- <script>
710
- import { Code } from '@mrintel/villain-ui';
711
- </script>
712
-
713
- <p>Install with <Code>npm install @mrintel/villain-ui</Code></p>
714
- ```
715
-
716
- ### Data Display
717
-
718
- **Table** - Data table
719
-
720
- ```svelte
721
- <script>
722
- import { Table } from '@mrintel/villain-ui';
723
-
724
- const columns = [
725
- { key: 'name', label: 'Name' },
726
- { key: 'email', label: 'Email' },
727
- { key: 'role', label: 'Role' }
728
- ];
729
-
730
- const data = [
731
- { name: 'John Doe', email: 'john@example.com', role: 'Admin' },
732
- { name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
733
- ];
734
- </script>
735
-
736
- <Table {columns} {data} hoverable striped />
737
- ```
738
-
739
- **Pagination** - Page navigation
740
-
741
- ```svelte
742
- <script>
743
- import { Pagination } from '@mrintel/villain-ui';
744
-
745
- let currentPage = $state(1);
746
- const totalPages = 10;
747
- </script>
748
-
749
- <Pagination bind:currentPage {totalPages} />
750
- ```
751
-
752
- **Badge** - Status badge
753
-
754
- ```svelte
755
- <script>
756
- import { Badge } from '@mrintel/villain-ui';
757
- </script>
758
-
759
- <Badge variant="success">Active</Badge>
760
- <Badge variant="warning">Pending</Badge>
761
- <Badge variant="error">Error</Badge>
762
- ```
763
-
764
- **Tag** - Removable tag
765
-
766
- ```svelte
767
- <script>
768
- import { Tag } from '@mrintel/villain-ui';
769
- </script>
770
-
771
- <Tag onRemove={() => console.log('Removed')}>JavaScript</Tag>
772
- <Tag>TypeScript</Tag>
773
- ```
774
-
775
- **List** - Styled list
776
-
777
- ```svelte
778
- <script>
779
- import { List } from '@mrintel/villain-ui';
780
-
781
- const items = ['Item 1', 'Item 2', 'Item 3'];
782
- </script>
783
-
784
- <List {items} variant="ordered" />
785
- ```
786
-
787
- **Avatar** - User avatar
788
-
789
- ```svelte
790
- <script>
791
- import { Avatar } from '@mrintel/villain-ui';
792
- </script>
793
-
794
- <Avatar src="/avatar.jpg" alt="User" size="md" />
795
- <Avatar initials="JD" size="lg" />
796
- ```
797
-
798
- **CodeBlock** - Presentational component for displaying syntax-highlighted code
799
-
800
- 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.
801
-
802
- **Key Features:**
803
- - Glass morphism aesthetic with custom scrollbars
804
- - Optional line numbers with highlighting support
805
- - Optional filename header display
806
- - Consumers choose their preferred syntax highlighter
807
- - Responsive overflow handling
808
-
809
- **Props:**
810
-
811
- | Prop | Type | Default | Description |
812
- |------|------|---------|-------------|
813
- | `filename` | `string` | `undefined` | Optional filename to display in the header |
814
- | `showLineNumbers` | `boolean` | `false` | Whether to show line numbers in the gutter |
815
- | `lineCount` | `number` | `0` | Total number of lines (required when `showLineNumbers` is `true`) |
816
- | `highlightLines` | `number[]` | `[]` | Array of 1-indexed line numbers to highlight in the gutter |
817
-
818
- **Important Notes:**
819
- - Consumers are responsible for sanitizing HTML to prevent XSS attacks
820
- - Apply `.line` class to each code line and `.highlighted` class to highlighted lines for consistent styling
821
- - Line numbers are 1-indexed (first line is 1, not 0)
822
- - When using `showLineNumbers`, provide the `lineCount` prop for proper rendering
823
-
824
- **Basic Usage:**
825
-
826
- ```svelte
827
- <script>
828
- import { CodeBlock } from '@mrintel/villain-ui';
829
-
830
- const highlightedCode = `<pre><code class="language-javascript">
831
- <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>
832
- 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>
833
- <span class="token punctuation">}</span>
834
- </code></pre>`;
835
- </script>
836
-
837
- <CodeBlock>
838
- {@html highlightedCode}
839
- </CodeBlock>
840
- ```
841
-
842
- **With Line Numbers:**
843
-
844
- ```svelte
845
- <script>
846
- import { CodeBlock } from '@mrintel/villain-ui';
847
-
848
- const code = `function greet(name) {
849
- return \`Hello, \${name}!\`;
850
- }`;
851
-
852
- const lineCount = code.split('\n').length;
853
- // Assume you have a highlighter that returns HTML
854
- const highlightedCode = yourHighlighter(code, 'javascript');
855
- </script>
856
-
857
- <CodeBlock showLineNumbers {lineCount}>
858
- {@html highlightedCode}
859
- </CodeBlock>
860
- ```
861
-
862
- **With Highlighted Lines:**
863
-
864
- ```svelte
865
- <script>
866
- import { CodeBlock } from '@mrintel/villain-ui';
867
-
868
- const code = `function calculate(a, b) {
869
- const sum = a + b;
870
- return sum * 2;
871
- }`;
872
-
873
- const lineCount = 4;
874
- const highlightedCode = yourHighlighter(code, 'javascript');
875
- const highlightLines = [2]; // Highlight line 2
876
- </script>
877
-
878
- <CodeBlock showLineNumbers {lineCount} {highlightLines}>
879
- {@html highlightedCode}
880
- </CodeBlock>
881
- ```
882
-
883
- **With Filename:**
884
-
885
- ```svelte
886
- <script>
887
- import { CodeBlock } from '@mrintel/villain-ui';
888
-
889
- const code = `export function add(a: number, b: number): number {
890
- return a + b;
891
- }`;
892
-
893
- const highlightedCode = yourHighlighter(code, 'typescript');
894
- </script>
895
-
896
- <CodeBlock filename="utils.ts">
897
- {@html highlightedCode}
898
- </CodeBlock>
899
- ```
900
-
901
- **Integration with Shiki:**
902
-
903
- ```svelte
904
- <script>
905
- import { CodeBlock } from '@mrintel/villain-ui';
906
- import { codeToHtml } from 'shiki';
907
- import { onMount } from 'svelte';
908
-
909
- const code = `function fibonacci(n) {
910
- if (n <= 1) return n;
911
- return fibonacci(n - 1) + fibonacci(n - 2);
912
- }`;
913
-
914
- let highlightedCode = $state('');
915
- let lineCount = $state(0);
916
-
917
- onMount(async () => {
918
- highlightedCode = await codeToHtml(code, {
919
- lang: 'javascript',
920
- theme: 'github-dark'
921
- });
922
- lineCount = code.split('\n').length;
923
- });
924
- </script>
925
-
926
- <CodeBlock filename="fibonacci.js" showLineNumbers {lineCount} highlightLines={[2, 3]}>
927
- {@html highlightedCode}
928
- </CodeBlock>
929
- ```
930
-
931
- **Integration with Prism.js:**
932
-
933
- ```svelte
934
- <script>
935
- import { CodeBlock } from '@mrintel/villain-ui';
936
- import Prism from 'prismjs';
937
- import 'prismjs/themes/prism-tomorrow.css';
938
-
939
- const code = `const greeting = (name) => {
940
- console.log(\`Hello, \${name}!\`);
941
- };`;
942
-
943
- const lineCount = code.split('\n').length;
944
- const highlightedCode = Prism.highlight(code, Prism.languages.javascript, 'javascript');
945
- </script>
946
-
947
- <CodeBlock showLineNumbers {lineCount}>
948
- <pre><code class="language-javascript">{@html highlightedCode}</code></pre>
949
- </CodeBlock>
950
- ```
951
-
952
- **Integration with Highlight.js:**
953
-
954
- ```svelte
955
- <script>
956
- import { CodeBlock } from '@mrintel/villain-ui';
957
- import hljs from 'highlight.js';
958
- import 'highlight.js/styles/github-dark.css';
959
-
960
- const code = `public class HelloWorld {
961
- public static void main(String[] args) {
962
- System.out.println("Hello, World!");
963
- }
964
- }`;
965
-
966
- const lineCount = code.split('\n').length;
967
- const highlightedCode = hljs.highlight(code, { language: 'java' }).value;
968
- </script>
969
-
970
- <CodeBlock filename="HelloWorld.java" showLineNumbers {lineCount}>
971
- <pre><code class="language-java">{@html highlightedCode}</code></pre>
972
- </CodeBlock>
973
- ```
974
-
975
- **Stat** - Statistic display
976
-
977
- ```svelte
978
- <script>
979
- import { Stat } from '@mrintel/villain-ui';
980
- </script>
981
-
982
- <Stat label="Total Users" value="1,234" change="+12.5%" trend="up" />
983
- ```
984
-
985
- ### Utilities
986
-
987
- **Portal** - Render content in different DOM location
988
-
989
- ```svelte
990
- <script>
991
- import { Portal } from '@mrintel/villain-ui';
992
- </script>
993
-
994
- <Portal target="body">
995
- <div>This renders at the end of body</div>
996
- </Portal>
997
- ```
998
-
999
- **Collapse** - Collapsible content
1000
-
1001
- ```svelte
1002
- <script>
1003
- import { Collapse } from '@mrintel/villain-ui';
1004
-
1005
- let open = $state(false);
1006
- </script>
1007
-
1008
- <Collapse title="Click to expand" open={open} onToggle={() => open = !open}>
1009
- <p>Hidden content that can be toggled</p>
1010
- </Collapse>
1011
- ```
1012
-
1013
- **Accordion** - Accordion with multiple items
1014
-
1015
- ```svelte
1016
- <script>
1017
- import { Accordion } from '@mrintel/villain-ui';
1018
-
1019
- const items = [
1020
- { id: '1', title: 'Section 1', content: 'Content for section 1' },
1021
- { id: '2', title: 'Section 2', content: 'Content for section 2' }
1022
- ];
1023
-
1024
- let openItems = $state([]);
1025
- </script>
1026
-
1027
- <Accordion {items} bind:openItems mode="multiple" />
1028
- ```
1029
-
1030
- **Carousel** - Image/content carousel
1031
-
1032
- ```svelte
1033
- <script>
1034
- import { Carousel } from '@mrintel/villain-ui';
1035
-
1036
- const items = [
1037
- { id: '1', content: 'Slide 1' },
1038
- { id: '2', content: 'Slide 2' },
1039
- { id: '3', content: 'Slide 3' }
1040
- ];
1041
-
1042
- let currentIndex = $state(0);
1043
- </script>
1044
-
1045
- <Carousel {items} bind:currentIndex autoplay showDots showArrows />
1046
- ```
1047
-
1048
- **ScrollArea** - Custom scrollable area
1049
-
1050
- ```svelte
1051
- <script>
1052
- import { ScrollArea } from '@mrintel/villain-ui';
1053
- </script>
1054
-
1055
- <ScrollArea height="300px">
1056
- <div>Long scrollable content...</div>
1057
- </ScrollArea>
1058
- ```
1059
-
1060
- ## 🎨 Theming
1061
-
1062
- ### CSS Variable System
1063
-
1064
- @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.
1065
-
1066
- ### Custom Theme Example
1067
-
1068
- Create a custom CSS file (e.g., `custom-theme.css`) and import it **after** the library theme:
1069
-
1070
- ```css
1071
- /* custom-theme.css */
1072
-
1073
- /* Override accent color from purple to blue */
1074
- :root {
1075
- --color-accent: #3B82F6;
1076
- --color-accent-soft: #60A5FA;
1077
- --color-accent-dark: #1E40AF;
1078
-
1079
- --shadow-accent-glow:
1080
- 0 0 20px rgba(59, 130, 246, 0.4),
1081
- 0 0 40px rgba(59, 130, 246, 0.2),
1082
- 0 0 60px rgba(59, 130, 246, 0.1);
1083
-
1084
- --shadow-text-glow:
1085
- 0 0 20px rgba(59, 130, 246, 0.5),
1086
- 0 0 40px rgba(59, 130, 246, 0.3);
1087
- }
1088
- ```
1089
-
1090
- Import order in your app:
1091
-
1092
- ```typescript
1093
- import '@mrintel/villain-ui/theme.css';
1094
- import './custom-theme.css'; // Your overrides
1095
- ```
1096
-
1097
- ### Typography Customization
1098
-
1099
- ```css
1100
- :root {
1101
- --font-heading: 'Montserrat', sans-serif;
1102
- --font-body: 'Open Sans', sans-serif;
1103
- --font-mono: 'Fira Code', monospace;
1104
-
1105
- --text-h1-size: 4rem;
1106
- --text-body-size: 1.125rem;
1107
- }
1108
- ```
1109
-
1110
- ### Border Radius Customization
1111
-
1112
- ```css
1113
- :root {
1114
- --radius-sm: 6px;
1115
- --radius-md: 10px;
1116
- --radius-lg: 14px;
1117
- --radius-xl: 20px;
1118
- --radius-2xl: 28px;
1119
- }
1120
- ```
1121
-
1122
- ### Complete Variable Reference
1123
-
1124
- #### Colors
1125
-
1126
- **Base Colors**
1127
- - `--color-base-0` through `--color-base-3`: Background layers
1128
- - `--color-surface`: Default surface color
1129
- - `--color-panel`: Panel background
1130
- - `--color-overlay`: Modal/overlay backdrop
1131
-
1132
- **Accent Colors**
1133
- - `--color-accent`: Primary accent color
1134
- - `--color-accent-soft`: Lighter accent variant
1135
- - `--color-accent-dark`: Darker accent variant
1136
-
1137
- **Text Colors**
1138
- - `--color-text`: Primary text color
1139
- - `--color-text-soft`: Secondary text color
1140
- - `--color-text-muted`: Muted/disabled text
1141
-
1142
- **State Colors**
1143
- - `--color-success`: Success state
1144
- - `--color-warning`: Warning state
1145
- - `--color-error`: Error state
1146
-
1147
- **Border Colors**
1148
- - `--color-border`: Default border
1149
- - `--color-border-strong`: Emphasized border
1150
-
1151
- #### Typography
1152
-
1153
- **Font Families**
1154
- - `--font-heading`: Heading font stack
1155
- - `--font-body`: Body text font stack
1156
- - `--font-mono`: Monospace font stack
1157
-
1158
- **Text Scales** (h1-h6, body, caption)
1159
- - `--text-{level}-size`: Font size
1160
- - `--text-{level}-line-height`: Line height
1161
- - `--text-{level}-weight`: Font weight
1162
-
1163
- #### Layout
1164
-
1165
- **Border Radii**
1166
- - `--radius-none` through `--radius-2xl`, `--radius-pill`
1167
-
1168
- **Spacing**
1169
- - `--spacing-4.5`: 1.125rem
1170
- - `--spacing-18`: 4.5rem
1171
-
1172
- #### Effects
1173
-
1174
- **Shadows**
1175
- - `--shadow-accent-glow`: Accent glow effect
1176
- - `--shadow-deep`: Deep shadow
1177
- - `--shadow-text-glow`: Text glow effect
1178
-
1179
- **Glass Effect**
1180
- - `--glass-panel-background`: Glass panel background
1181
-
1182
- #### Motion
1183
-
1184
- **Easing Curves**
1185
- - `--ease-luxe`: Primary luxury easing
1186
- - `--ease-sharp`: Sharp transitions
1187
-
1188
- ### Custom Utility Classes
1189
-
1190
- The library provides custom utility classes you can use:
1191
-
1192
- - `.text-glow` - Apply accent glow to text
1193
- - `.glass-panel` - Glass morphism effect with backdrop blur
1194
- - `.accent-glow` - Accent color glow effect
1195
- - `.hover-lift` - Lift and glow on hover
1196
-
1197
- Example usage:
1198
-
1199
- ```svelte
1200
- <div class="glass-panel accent-glow">
1201
- <h2 class="text-glow">Glowing Title</h2>
1202
- </div>
1203
- ```
1204
-
1205
- ### Theme Persistence
1206
-
1207
- The brand structure (depth system, spacing, motion curves) persists even when colors change, maintaining the luxury aesthetic regardless of your chosen palette.
1208
-
1209
- ## 🛠️ Development
1210
-
1211
- ### Clone and Setup
1212
-
1213
- ```bash
1214
- git clone <repository-url>
1215
- cd villain-ui
1216
- npm install
1217
- ```
1218
-
1219
- ### Available Scripts
1220
-
1221
- - `npm run dev` - Start Vite dev server
1222
- - `npm run build` - Build the library for production
1223
- - `npm run type-check` - Run TypeScript type checking
1224
- - `npm run check` - Alias for type-check
1225
-
1226
- ### Build Output
1227
-
1228
- The build creates a `dist/` directory with:
1229
-
1230
- ```
1231
- dist/
1232
- ├── index.js # Compiled components
1233
- ├── index.d.ts # TypeScript declarations
1234
- └── theme.css # Compiled theme styles
1235
- ```
1236
-
1237
- ### Manual Build Before Publishing
1238
-
1239
- If you want to verify the build manually:
1240
-
1241
- ```bash
1242
- npm run build
1243
- npm pack
1244
- ```
1245
-
1246
- This creates a `.tgz` file you can inspect before publishing.
1247
-
1248
- ## 📘 TypeScript Support
1249
-
1250
- @mrintel/villain-ui is built with TypeScript in strict mode and includes complete type definitions.
1251
-
1252
- - Full TypeScript support
1253
- - ✅ Strict mode enabled
1254
- - ✅ Type definitions included in `dist/index.d.ts`
1255
- - All components have typed Props interfaces
1256
- - IntelliSense support in VS Code and other editors
1257
-
1258
- Import types directly from components:
1259
-
1260
- ```typescript
1261
- import type { Button } from '@mrintel/villain-ui';
1262
-
1263
- // Component props are fully typed
1264
- const props: ComponentProps<typeof Button> = {
1265
- variant: 'primary',
1266
- size: 'md',
1267
- disabled: false
1268
- };
1269
- ```
1270
-
1271
- ## 🌐 Browser Support
1272
-
1273
- @mrintel/villain-ui targets modern browsers that support:
1274
-
1275
- - CSS Variables (Custom Properties)
1276
- - ✅ CSS `backdrop-filter` for glass morphism
1277
- - ✅ ES2022+ JavaScript features
1278
- - Tailwind CSS v4 requirements
1279
-
1280
- **Supported Browsers:**
1281
- - Chrome/Edge 88+
1282
- - Firefox 94+
1283
- - Safari 15.4+
1284
- - Opera 74+
1285
-
1286
- ## 📄 License
1287
-
1288
- MIT License - see LICENSE file for details
1289
-
1290
- ## 🤝 Contributing
1291
-
1292
- Contributions are welcome! Please feel free to submit issues and pull requests.
1293
-
1294
- ---
1295
-
1296
- **Built with ❤️ for the modern web**
1
+ # @mrintel/villain-ui
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@mrintel/villain-ui.svg)](https://www.npmjs.com/package/@mrintel/villain-ui)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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**