react-18-ui-library 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +600 -78
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,41 +1,106 @@
|
|
|
1
1
|
# react-18-ui-library
|
|
2
2
|
|
|
3
|
-
A fully customizable, theme-aware React 18 enterprise UI component library
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
> A fully customizable, theme-aware React 18 enterprise UI component library — 70+ components, schema-driven forms, fluent validation, drag-and-drop file upload, and zero runtime CSS-in-JS overhead.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/react-18-ui-library)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](https://react.dev/)
|
|
8
|
+
[](https://tailwindcss.com/)
|
|
9
|
+
[](https://snyk.io/)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Quick Start](#quick-start)
|
|
17
|
+
- [Theme System](#theme-system)
|
|
18
|
+
- [Component Reference](#component-reference)
|
|
19
|
+
- [Layout](#layout)
|
|
20
|
+
- [Navigation](#navigation)
|
|
21
|
+
- [Inputs](#inputs)
|
|
22
|
+
- [Forms](#forms)
|
|
23
|
+
- [Display](#display)
|
|
24
|
+
- [Feedback](#feedback)
|
|
25
|
+
- [Overlay](#overlay)
|
|
26
|
+
- [Typography](#typography)
|
|
27
|
+
- [Actions](#actions)
|
|
28
|
+
- [FormValidator — Fluent Validation API](#formvalidator--fluent-validation-api)
|
|
29
|
+
- [Hooks](#hooks)
|
|
30
|
+
- [CSS Token Reference](#css-token-reference)
|
|
31
|
+
- [Security — Snyk](#security--snyk)
|
|
32
|
+
- [Development](#development)
|
|
33
|
+
- [Peer Dependencies](#peer-dependencies)
|
|
34
|
+
- [License](#license)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
6
39
|
|
|
7
40
|
```bash
|
|
8
41
|
npm install react-18-ui-library
|
|
42
|
+
# or
|
|
43
|
+
yarn add react-18-ui-library
|
|
44
|
+
# or
|
|
45
|
+
pnpm add react-18-ui-library
|
|
9
46
|
```
|
|
10
47
|
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
11
52
|
```tsx
|
|
12
|
-
// 1. Import
|
|
53
|
+
// 1. Import default theme tokens once at your app root
|
|
13
54
|
import 'react-18-ui-library/styles'
|
|
14
55
|
|
|
15
|
-
// 2.
|
|
16
|
-
import { Button, TextField,
|
|
56
|
+
// 2. Import and use components
|
|
57
|
+
import { Button, TextField, JSONForm } from 'react-18-ui-library'
|
|
58
|
+
|
|
59
|
+
export function App() {
|
|
60
|
+
return (
|
|
61
|
+
<div>
|
|
62
|
+
<TextField label="Name" placeholder="John Doe" />
|
|
63
|
+
<Button variant="primary">Submit</Button>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
17
67
|
```
|
|
18
68
|
|
|
69
|
+
> The styles import injects CSS custom property defaults into `:root`. You can override any token in your own stylesheet — see [Theme System](#theme-system).
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
19
73
|
## Theme System
|
|
20
74
|
|
|
21
|
-
|
|
75
|
+
All components read CSS custom properties from the parent app's `:root`. There are **no hardcoded colors** and no runtime CSS-in-JS overhead.
|
|
76
|
+
|
|
77
|
+
### Override tokens in CSS
|
|
22
78
|
|
|
23
79
|
```css
|
|
24
|
-
/*
|
|
80
|
+
/* globals.css */
|
|
25
81
|
:root {
|
|
26
|
-
--color-primary:
|
|
27
|
-
--color-primary-hover:
|
|
82
|
+
--color-primary: #7c3aed;
|
|
83
|
+
--color-primary-hover: #6d28d9;
|
|
28
84
|
--color-primary-foreground: #ffffff;
|
|
29
|
-
--color-background:
|
|
30
|
-
--color-surface:
|
|
31
|
-
--color-text:
|
|
32
|
-
--
|
|
33
|
-
--
|
|
34
|
-
|
|
85
|
+
--color-background: #fafafa;
|
|
86
|
+
--color-surface: #ffffff;
|
|
87
|
+
--color-text: #111827;
|
|
88
|
+
--color-text-muted: #6b7280;
|
|
89
|
+
--color-border: #e5e7eb;
|
|
90
|
+
--color-error: #ef4444;
|
|
91
|
+
--color-success: #22c55e;
|
|
92
|
+
--color-warning: #f59e0b;
|
|
93
|
+
--radius-sm: 0.25rem;
|
|
94
|
+
--radius-md: 0.5rem;
|
|
95
|
+
--radius-lg: 0.75rem;
|
|
96
|
+
--font-family-base: 'Inter', sans-serif;
|
|
97
|
+
--navbar-height: 64px;
|
|
98
|
+
--sidebar-width: 260px;
|
|
99
|
+
--sidebar-collapsed-width: 64px;
|
|
35
100
|
}
|
|
36
101
|
```
|
|
37
102
|
|
|
38
|
-
Or use
|
|
103
|
+
### Or use `ThemeProvider` programmatically
|
|
39
104
|
|
|
40
105
|
```tsx
|
|
41
106
|
import { ThemeProvider } from 'react-18-ui-library'
|
|
@@ -45,22 +110,22 @@ import { ThemeProvider } from 'react-18-ui-library'
|
|
|
45
110
|
</ThemeProvider>
|
|
46
111
|
```
|
|
47
112
|
|
|
48
|
-
|
|
113
|
+
---
|
|
49
114
|
|
|
50
|
-
|
|
51
|
-
|---|---|
|
|
52
|
-
| **Layout** | `AppShell`, `Navbar`, `Sidebar`, `Container`, `Stack`, `Grid`, `Divider`, `Spacer` |
|
|
53
|
-
| **Navigation** | `Tabs`, `Breadcrumb`, `Pagination`, `StepIndicator` |
|
|
54
|
-
| **Inputs** | `Button`, `IconButton`, `TextField`, `TextArea`, `Select`, `Checkbox`, `RadioGroup`, `Switch` |
|
|
55
|
-
| **Forms** | `FormField`, `JSONForm`, `FileUpload` |
|
|
56
|
-
| **Display** | `Card`, `Box`, `Image`, `Avatar`, `Badge`, `Tag`, `Icon`, `SVG`, `Table`, `List`, `Timeline`, `Stat`, `EmptyState` |
|
|
57
|
-
| **Feedback** | `Alert`, `ToastProvider`, `Spinner`, `Skeleton`, `ProgressBar`, `ErrorBoundary` |
|
|
58
|
-
| **Overlay** | `Modal`, `Drawer`, `Tooltip`, `Popover`, `ContextMenu` |
|
|
59
|
-
| **Typography** | `Heading`, `Text`, `Label`, `Code`, `Link` |
|
|
115
|
+
## Component Reference
|
|
60
116
|
|
|
61
|
-
|
|
117
|
+
### Layout
|
|
62
118
|
|
|
63
|
-
|
|
119
|
+
| Component | Description |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `AppShell` | Full-page layout combining Navbar + Sidebar + main content area |
|
|
122
|
+
| `Navbar` | Top navigation bar with logo, links, and action slots |
|
|
123
|
+
| `Sidebar` | Collapsible sidebar with icons, badges, and nested items |
|
|
124
|
+
| `Container` | Max-width content wrapper with `xs`/`sm`/`md`/`lg`/`xl`/`full` sizes |
|
|
125
|
+
| `Stack` | Flexbox stack — `direction`, `gap`, `align`, `justify` props |
|
|
126
|
+
| `Grid` | CSS grid with responsive column count and gap control |
|
|
127
|
+
| `Divider` | Horizontal or vertical separator with optional label |
|
|
128
|
+
| `Spacer` | Flexible whitespace filler |
|
|
64
129
|
|
|
65
130
|
```tsx
|
|
66
131
|
import { AppShell } from 'react-18-ui-library'
|
|
@@ -68,68 +133,195 @@ import { LayoutDashboard, Users, Settings } from 'lucide-react'
|
|
|
68
133
|
|
|
69
134
|
<AppShell
|
|
70
135
|
navbar={{
|
|
71
|
-
logo: <Logo />,
|
|
136
|
+
logo: <img src="/logo.svg" alt="Logo" height={32} />,
|
|
72
137
|
links: [{ label: 'Home', href: '/', active: true }],
|
|
73
138
|
actions: <Button size="sm">Sign Out</Button>,
|
|
74
139
|
}}
|
|
75
140
|
sidebar={{
|
|
76
141
|
items: [
|
|
77
|
-
{ id: 'dash',
|
|
78
|
-
{ id: 'users',
|
|
79
|
-
{ id: 'settings', label: 'Settings',
|
|
142
|
+
{ id: 'dash', label: 'Dashboard', icon: <LayoutDashboard size={16} />, active: true },
|
|
143
|
+
{ id: 'users', label: 'Users', icon: <Users size={16} />, badge: 5 },
|
|
144
|
+
{ id: 'settings', label: 'Settings', icon: <Settings size={16} /> },
|
|
80
145
|
],
|
|
81
146
|
}}
|
|
82
147
|
>
|
|
83
|
-
<main>
|
|
148
|
+
<main>Page content</main>
|
|
84
149
|
</AppShell>
|
|
85
150
|
```
|
|
86
151
|
|
|
87
|
-
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### Navigation
|
|
155
|
+
|
|
156
|
+
| Component | Description |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `Tabs` | Horizontal/vertical tab panels with `line`, `pill`, `boxed` variants |
|
|
159
|
+
| `Breadcrumb` | Accessible breadcrumb trail with custom separator |
|
|
160
|
+
| `Pagination` | Page navigation with configurable page size and sibling count |
|
|
161
|
+
| `StepIndicator` | Multi-step wizard progress with `completed`/`current`/`upcoming` states |
|
|
88
162
|
|
|
89
163
|
```tsx
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
164
|
+
import { Tabs, StepIndicator, Pagination } from 'react-18-ui-library'
|
|
165
|
+
|
|
166
|
+
<Tabs
|
|
167
|
+
items={[
|
|
168
|
+
{ id: 'profile', label: 'Profile', content: <ProfilePanel /> },
|
|
169
|
+
{ id: 'billing', label: 'Billing', content: <BillingPanel /> },
|
|
170
|
+
{ id: 'security', label: 'Security', content: <SecurityPanel /> },
|
|
171
|
+
]}
|
|
172
|
+
variant="pill"
|
|
173
|
+
/>
|
|
174
|
+
|
|
175
|
+
<StepIndicator
|
|
176
|
+
steps={[
|
|
177
|
+
{ id: '1', label: 'Account', status: 'completed' },
|
|
178
|
+
{ id: '2', label: 'Profile', status: 'current' },
|
|
179
|
+
{ id: '3', label: 'Review', status: 'upcoming' },
|
|
180
|
+
]}
|
|
181
|
+
/>
|
|
182
|
+
|
|
183
|
+
<Pagination
|
|
184
|
+
page={currentPage}
|
|
185
|
+
totalPages={20}
|
|
186
|
+
onPageChange={setCurrentPage}
|
|
95
187
|
/>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
96
191
|
|
|
192
|
+
### Inputs
|
|
193
|
+
|
|
194
|
+
| Component | Description |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `Button` | `primary`, `secondary`, `ghost`, `outline`, `destructive` variants + `xs`/`sm`/`md`/`lg` sizes |
|
|
197
|
+
| `IconButton` | Square icon-only button with same variants |
|
|
198
|
+
| `TextField` | Text input — prefix/suffix icon/text/image, clearable, password toggle, `showMaxLength` |
|
|
199
|
+
| `TextArea` | Auto-resize textarea with character count (`showCharCount` / `showMaxLength`) |
|
|
200
|
+
| `Select` | Radix-powered dropdown with option groups |
|
|
201
|
+
| `MultiSelect` | Multi-value select with chip display |
|
|
202
|
+
| `SearchSelect` | Combobox with single/multi mode and async search |
|
|
203
|
+
| `ChipSelect` | Inline chip-toggle multi-select |
|
|
204
|
+
| `Checkbox` | Accessible checkbox with indeterminate state |
|
|
205
|
+
| `RadioGroup` | Accessible radio group |
|
|
206
|
+
| `Switch` | Toggle switch |
|
|
207
|
+
| `Slider` | Range slider with `min`/`max`/`step` |
|
|
208
|
+
| `NumberInput` | Numeric input — increment/decrement controls, `hideControls`, blocks non-numeric keys, `showMaxLength` |
|
|
209
|
+
| `PhoneInput` | Phone input with country code dropdown (30+ countries), digit-only enforcement, `showMaxLength` |
|
|
210
|
+
| `OTPInput` | One-time password input with configurable digit count and auto-advance |
|
|
211
|
+
| `Rating` | Star rating with half-star support |
|
|
212
|
+
| `TagInput` | Free-form tag entry — Enter/comma separators, duplicate prevention |
|
|
213
|
+
| `DatePicker` | Calendar picker — single/range mode, month+year dropdowns, configurable year range, portal rendering |
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
import { TextField, PhoneInput, NumberInput, DatePicker, OTPInput } from 'react-18-ui-library'
|
|
217
|
+
import { Search } from 'lucide-react'
|
|
218
|
+
|
|
219
|
+
// Text field with icon prefix and max-length counter
|
|
97
220
|
<TextField
|
|
98
|
-
label="
|
|
221
|
+
label="Username"
|
|
99
222
|
prefixIcon={<Search size={14} />}
|
|
223
|
+
maxLength={30}
|
|
224
|
+
showMaxLength
|
|
100
225
|
clearable
|
|
101
226
|
/>
|
|
102
227
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
228
|
+
// Phone input with country code
|
|
229
|
+
<PhoneInput
|
|
230
|
+
label="Mobile Number"
|
|
231
|
+
defaultCountry="IN"
|
|
232
|
+
maxLength={10}
|
|
233
|
+
showMaxLength
|
|
234
|
+
onChange={(val) => console.log(val.full)} // "+91 9876543210"
|
|
235
|
+
/>
|
|
236
|
+
|
|
237
|
+
// Number input — hide stepper buttons
|
|
238
|
+
<NumberInput
|
|
239
|
+
label="Quantity"
|
|
240
|
+
hideControls
|
|
241
|
+
min={0}
|
|
242
|
+
max={999}
|
|
108
243
|
/>
|
|
244
|
+
|
|
245
|
+
// Date picker — restrict year range
|
|
246
|
+
<DatePicker
|
|
247
|
+
label="Date of Birth"
|
|
248
|
+
yearRangeBefore={80}
|
|
249
|
+
yearRangeAfter={0}
|
|
250
|
+
onChange={(date) => console.log(date)}
|
|
251
|
+
/>
|
|
252
|
+
|
|
253
|
+
// OTP input — 6 digits
|
|
254
|
+
<OTPInput length={6} onComplete={(code) => verifyOTP(code)} />
|
|
109
255
|
```
|
|
110
256
|
|
|
111
|
-
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### Forms
|
|
260
|
+
|
|
261
|
+
#### `JSONForm` — Schema-Driven Forms
|
|
262
|
+
|
|
263
|
+
Build complete, validated forms from a JSON schema array. Powered by `react-hook-form`.
|
|
264
|
+
|
|
265
|
+
**Supported `type` values:** `text` · `email` · `password` · `url` · `tel` · `number` · `textarea` · `select` · `multiselect` · `radio` · `checkbox` · `switch` · `slider` · `chipselect` · `taginput` · `datepicker` · `file`
|
|
112
266
|
|
|
113
267
|
```tsx
|
|
114
|
-
import { JSONForm } from 'react-18-ui-library'
|
|
268
|
+
import { JSONForm, v } from 'react-18-ui-library'
|
|
115
269
|
import type { JSONFormField } from 'react-18-ui-library'
|
|
116
270
|
|
|
117
271
|
const schema: JSONFormField[] = [
|
|
118
|
-
{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
272
|
+
{
|
|
273
|
+
name: 'firstName',
|
|
274
|
+
type: 'text',
|
|
275
|
+
label: 'First Name',
|
|
276
|
+
colSpan: 1,
|
|
277
|
+
validator: v.string().required().lengthBetween(2, 50),
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: 'email',
|
|
281
|
+
type: 'email',
|
|
282
|
+
label: 'Email',
|
|
283
|
+
colSpan: 1,
|
|
284
|
+
validator: v.email().required().blockedDomains(['mailinator.com']),
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'role',
|
|
288
|
+
type: 'select',
|
|
289
|
+
label: 'Role',
|
|
290
|
+
options: [
|
|
291
|
+
{ value: 'admin', label: 'Admin' },
|
|
292
|
+
{ value: 'editor', label: 'Editor' },
|
|
293
|
+
{ value: 'viewer', label: 'Viewer' },
|
|
294
|
+
],
|
|
295
|
+
validator: v.select().required(),
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: 'bio',
|
|
299
|
+
type: 'textarea',
|
|
300
|
+
label: 'Bio',
|
|
301
|
+
colSpan: 2,
|
|
302
|
+
showCharCount: true,
|
|
303
|
+
validator: v.string().maxLength(300),
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'terms',
|
|
307
|
+
type: 'checkbox',
|
|
308
|
+
label: 'I agree to the Terms and Conditions',
|
|
309
|
+
colSpan: 2,
|
|
310
|
+
validator: v.boolean().mustBeTrue('You must accept the terms'),
|
|
311
|
+
},
|
|
122
312
|
]
|
|
123
313
|
|
|
124
314
|
<JSONForm
|
|
125
315
|
schema={schema}
|
|
126
316
|
columns={2}
|
|
127
317
|
onSubmit={(data) => console.log(data)}
|
|
128
|
-
|
|
318
|
+
onCancel={() => router.back()}
|
|
319
|
+
submitLabel="Create Account"
|
|
320
|
+
cancelLabel="Cancel"
|
|
129
321
|
/>
|
|
130
322
|
```
|
|
131
323
|
|
|
132
|
-
|
|
324
|
+
#### `FileUpload` — Drag & Drop
|
|
133
325
|
|
|
134
326
|
```tsx
|
|
135
327
|
import { FileUpload } from 'react-18-ui-library'
|
|
@@ -145,58 +337,388 @@ import { FileUpload } from 'react-18-ui-library'
|
|
|
145
337
|
/>
|
|
146
338
|
```
|
|
147
339
|
|
|
148
|
-
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## FormValidator — Fluent Validation API
|
|
343
|
+
|
|
344
|
+
A chainable, type-safe validation builder that compiles to `react-hook-form` rules. Import `v` and attach a `.validator` to any `JSONFormField`.
|
|
345
|
+
|
|
346
|
+
### Validator reference
|
|
347
|
+
|
|
348
|
+
| Builder | Field types | Methods |
|
|
349
|
+
|---|---|---|
|
|
350
|
+
| `v.string()` | `text` `textarea` | `.required()` `.minLength(n)` `.maxLength(n)` `.lengthBetween(min,max)` `.length(n)` `.shouldMatch(regex)` `.shouldBeIn([...])` `.notEmpty()` `.noSpaces()` `.alphanumeric()` `.startsWith(s)` `.endsWith(s)` `.custom(name, fn)` |
|
|
351
|
+
| `v.number()` | `number` | `.required()` `.min(n)` `.max(n)` `.between(min,max)` `.integer()` `.positive()` `.negative()` `.nonZero()` `.multipleOf(n)` `.length(digits)` `.custom(name, fn)` |
|
|
352
|
+
| `v.password()` | `password` | `.required()` `.minLength(n)` `.maxLength(n)` `.hasUppercase()` `.hasLowercase()` `.hasDigit()` `.hasSpecialChar()` `.noSpaces()` `.confirmMatch(getter)` `.strong()` `.custom(name, fn)` |
|
|
353
|
+
| `v.email()` | `email` | `.required()` `.domain([...])` `.blockedDomains([...])` `.shouldMatch(regex)` `.custom(name, fn)` |
|
|
354
|
+
| `v.url()` | `url` | `.required()` `.httpsOnly()` `.allowedDomains([...])` `.noTrailingSlash()` `.custom(name, fn)` |
|
|
355
|
+
| `v.tel()` | `tel` | `.required()` `.length(n)` `.minLength(n)` `.maxLength(n)` `.shouldMatch(regex)` `.custom(name, fn)` |
|
|
356
|
+
| `v.date()` | `datepicker` `text` | `.required()` `.validDate()` `.minDate(d)` `.maxDate(d)` `.notInPast()` `.notInFuture()` `.between(min,max)` `.custom(name, fn)` |
|
|
357
|
+
| `v.select()` | `select` `radio` | `.required()` `.shouldBeIn([...])` `.notEmpty()` `.custom(name, fn)` |
|
|
358
|
+
| `v.boolean()` | `checkbox` `switch` | `.required()` `.mustBeTrue(msg)` `.custom(name, fn)` |
|
|
359
|
+
| `v.array()` | `chipselect` `taginput` `multiselect` | `.required()` `.minItems(n)` `.maxItems(n)` `.exactItems(n)` `.shouldBeIn([...])` `.noEmpty()` `.custom(name, fn)` |
|
|
360
|
+
| `v.file()` | `file` | `.required()` `.maxSize(bytes)` `.allowedTypes([...])` `.allowedExtensions([...])` `.maxFiles(n)` `.minFiles(n)` `.custom(name, fn)` |
|
|
361
|
+
|
|
362
|
+
### Usage examples
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
import { v } from 'react-18-ui-library'
|
|
366
|
+
|
|
367
|
+
// String
|
|
368
|
+
v.string().required().lengthBetween(3, 20).noSpaces()
|
|
369
|
+
v.string().required().shouldBeIn(['admin', 'editor', 'viewer'])
|
|
370
|
+
v.string().required().shouldMatch(/^\d{5}(-\d{4})?$/, 'Invalid ZIP code')
|
|
371
|
+
v.string().required().startsWith('INVITE-').alphanumeric()
|
|
372
|
+
|
|
373
|
+
// Number
|
|
374
|
+
v.number().required().between(18, 120).integer()
|
|
375
|
+
v.number().required().positive().nonZero().multipleOf(5)
|
|
376
|
+
v.number().required().length(6) // exactly 6 digits (PIN)
|
|
377
|
+
|
|
378
|
+
// Password with confirm-match
|
|
379
|
+
const passwordRef = useRef('')
|
|
380
|
+
// field 1
|
|
381
|
+
v.password().required().strong()
|
|
382
|
+
// field 2 — reads field 1's value via getter
|
|
383
|
+
v.password().required().confirmMatch(() => passwordRef.current)
|
|
384
|
+
|
|
385
|
+
// Email
|
|
386
|
+
v.email().required()
|
|
387
|
+
v.email().required().domain(['company.com', 'company.org']) // whitelist
|
|
388
|
+
v.email().required().blockedDomains(['mailinator.com', 'yopmail.com']) // blacklist
|
|
389
|
+
|
|
390
|
+
// URL
|
|
391
|
+
v.url().required().httpsOnly().noTrailingSlash()
|
|
392
|
+
v.url().required().allowedDomains(['github.com'])
|
|
393
|
+
|
|
394
|
+
// Phone
|
|
395
|
+
v.tel().required().length(10)
|
|
396
|
+
v.tel().required().minLength(7).maxLength(15)
|
|
397
|
+
|
|
398
|
+
// Date
|
|
399
|
+
v.date().required().notInPast()
|
|
400
|
+
v.date().required().notInFuture()
|
|
401
|
+
v.date().required().between(new Date('2020-01-01'), new Date('2030-12-31'))
|
|
402
|
+
|
|
403
|
+
// Select / Radio
|
|
404
|
+
v.select().required().shouldBeIn(['us', 'gb', 'in', 'au'])
|
|
405
|
+
|
|
406
|
+
// Boolean
|
|
407
|
+
v.boolean().mustBeTrue('You must accept the terms')
|
|
408
|
+
|
|
409
|
+
// Array (chipselect / taginput)
|
|
410
|
+
v.array().required().minItems(2).maxItems(5)
|
|
411
|
+
v.array().required().exactItems(3).noEmpty()
|
|
412
|
+
|
|
413
|
+
// File upload
|
|
414
|
+
v.file().required()
|
|
415
|
+
.maxSize(5 * 1024 * 1024)
|
|
416
|
+
.allowedTypes(['image/*', 'application/pdf'])
|
|
417
|
+
.maxFiles(3)
|
|
418
|
+
|
|
419
|
+
// Custom rule
|
|
420
|
+
v.string().required().custom(
|
|
421
|
+
'noBadWords',
|
|
422
|
+
(val) => !String(val).includes('spam'),
|
|
423
|
+
'Inappropriate content detected'
|
|
424
|
+
)
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
### Display
|
|
430
|
+
|
|
431
|
+
| Component | Description |
|
|
432
|
+
|---|---|
|
|
433
|
+
| `Card` | Surface container with optional header, footer, and padding variants |
|
|
434
|
+
| `Box` | Generic styled `div` with spacing and color props |
|
|
435
|
+
| `Image` | Responsive image with aspect ratio (`square`/`video`/`portrait`) and `fit` control |
|
|
436
|
+
| `Avatar` / `AvatarGroup` | User avatar with fallback initials, status dot, and group stacking |
|
|
437
|
+
| `Badge` / `BadgeAnchor` | Status badge chip and notification dot anchor |
|
|
438
|
+
| `Tag` | Dismissible label chip with color variants |
|
|
439
|
+
| `Icon` | Lucide icon wrapper with `xs`/`sm`/`md`/`lg`/`xl` sizes |
|
|
440
|
+
| `SVG` | Raw SVG wrapper with size props |
|
|
441
|
+
| `Table` | Sortable, selectable data table with sticky header |
|
|
442
|
+
| `DataTable` | Full-featured table with server-side pagination and column visibility |
|
|
443
|
+
| `List` / `ListItem` | Semantic list with icon, description, and action slots |
|
|
444
|
+
| `Timeline` | Vertical event timeline with icon and color variants |
|
|
445
|
+
| `Stat` | Metric card with label, value, delta, and trend indicator |
|
|
446
|
+
| `EmptyState` | Zero-state placeholder with icon, title, description, and CTA |
|
|
447
|
+
| `MarkdownReader` | Renders Markdown with GFM tables and syntax-highlighted code blocks |
|
|
448
|
+
| `Collapsible` | Animated expand/collapse panel |
|
|
449
|
+
| `TreeView` | Recursive tree with expand/collapse nodes and selection |
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
### Feedback
|
|
454
|
+
|
|
455
|
+
| Component | Description |
|
|
456
|
+
|---|---|
|
|
457
|
+
| `Alert` | Inline status message — `info` / `success` / `warning` / `error` |
|
|
458
|
+
| `ToastProvider` + `useToast` | Global toast notification system with position control |
|
|
459
|
+
| `Spinner` | Loading spinner — `xs` through `xl` sizes |
|
|
460
|
+
| `Skeleton` / `SkeletonCard` | Content placeholder shimmer animations |
|
|
461
|
+
| `ProgressBar` | Linear progress with `default` / `success` / `warning` / `error` variants |
|
|
462
|
+
| `ErrorBoundary` | React error boundary with customizable fallback UI |
|
|
463
|
+
| `FullScreenLoader` + `useFullScreenLoader` | Full-viewport loading overlay with programmatic control |
|
|
149
464
|
|
|
150
465
|
```tsx
|
|
151
466
|
import { ToastProvider, useToast } from 'react-18-ui-library'
|
|
152
467
|
|
|
153
|
-
// Wrap your app
|
|
468
|
+
// Wrap your app root
|
|
154
469
|
<ToastProvider toasts={toasts} onDismiss={dismiss} position="bottom-right" />
|
|
155
470
|
|
|
156
471
|
// In any component
|
|
157
|
-
const { toast } = useToast()
|
|
158
|
-
toast({ title: 'Saved!',
|
|
472
|
+
const { toast, dismiss } = useToast()
|
|
473
|
+
toast({ title: 'Saved!', description: 'Changes saved.', variant: 'success' })
|
|
474
|
+
toast({ title: 'Error', description: 'Something went wrong.', variant: 'error' })
|
|
475
|
+
toast({ title: 'Warning', description: 'Low disk space.', variant: 'warning' })
|
|
159
476
|
```
|
|
160
477
|
|
|
161
|
-
|
|
478
|
+
---
|
|
162
479
|
|
|
163
|
-
|
|
164
|
-
# Install dependencies
|
|
165
|
-
npm install
|
|
480
|
+
### Overlay
|
|
166
481
|
|
|
167
|
-
|
|
168
|
-
|
|
482
|
+
| Component | Description |
|
|
483
|
+
|---|---|
|
|
484
|
+
| `Modal` | Radix Dialog modal — `sm`/`md`/`lg`/`xl`/`full` sizes |
|
|
485
|
+
| `Drawer` | Slide-in panel from `top`/`right`/`bottom`/`left` |
|
|
486
|
+
| `Tooltip` | Hover tooltip with `top`/`right`/`bottom`/`left` placement |
|
|
487
|
+
| `Popover` | Click-triggered floating panel |
|
|
488
|
+
| `ContextMenu` | Right-click context menu with icons and dividers |
|
|
489
|
+
| `ConfirmDialog` + `useConfirm` | Programmatic async confirmation dialog |
|
|
490
|
+
| `CommandPalette` + `useCommandPalette` | ⌘K-style command palette with groups and keyboard navigation, portal-rendered |
|
|
169
491
|
|
|
170
|
-
|
|
171
|
-
|
|
492
|
+
```tsx
|
|
493
|
+
import { useConfirm, ConfirmDialogProvider } from 'react-18-ui-library'
|
|
172
494
|
|
|
173
|
-
|
|
174
|
-
|
|
495
|
+
// Wrap app root
|
|
496
|
+
<ConfirmDialogProvider>
|
|
497
|
+
<App />
|
|
498
|
+
</ConfirmDialogProvider>
|
|
499
|
+
|
|
500
|
+
// In any component
|
|
501
|
+
const confirm = useConfirm()
|
|
502
|
+
const ok = await confirm({
|
|
503
|
+
title: 'Delete record?',
|
|
504
|
+
description: 'This action cannot be undone.',
|
|
505
|
+
confirmLabel: 'Delete',
|
|
506
|
+
variant: 'destructive',
|
|
507
|
+
})
|
|
508
|
+
if (ok) deleteRecord()
|
|
175
509
|
```
|
|
176
510
|
|
|
511
|
+
```tsx
|
|
512
|
+
import { CommandPalette, useCommandPalette } from 'react-18-ui-library'
|
|
513
|
+
|
|
514
|
+
const { open, close, isOpen } = useCommandPalette()
|
|
515
|
+
|
|
516
|
+
// Bind ⌘K
|
|
517
|
+
useKeyboard([{ key: 'k', meta: true, handler: open }])
|
|
518
|
+
|
|
519
|
+
<CommandPalette
|
|
520
|
+
open={isOpen}
|
|
521
|
+
onClose={close}
|
|
522
|
+
groups={[
|
|
523
|
+
{
|
|
524
|
+
id: 'nav',
|
|
525
|
+
label: 'Navigation',
|
|
526
|
+
items: [
|
|
527
|
+
{ id: 'dashboard', label: 'Dashboard', icon: <LayoutDashboard size={14} />, onSelect: () => router.push('/') },
|
|
528
|
+
{ id: 'settings', label: 'Settings', icon: <Settings size={14} />, onSelect: () => router.push('/settings') },
|
|
529
|
+
],
|
|
530
|
+
},
|
|
531
|
+
]}
|
|
532
|
+
/>
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
### Typography
|
|
538
|
+
|
|
539
|
+
| Component | Description |
|
|
540
|
+
|---|---|
|
|
541
|
+
| `Heading` | `h1`–`h6` with independent `size` (`xs`–`4xl`) and `weight` props |
|
|
542
|
+
| `Text` | Paragraph / span with `size`, `weight`, `color`, and polymorphic `as` prop |
|
|
543
|
+
| `Label` | Accessible form label |
|
|
544
|
+
| `Code` | Inline code or syntax-highlighted fenced code block (Prism) |
|
|
545
|
+
| `Link` | Anchor with `none`/`hover`/`always` underline variants |
|
|
546
|
+
| `Kbd` | Keyboard shortcut key display |
|
|
547
|
+
|
|
548
|
+
```tsx
|
|
549
|
+
import { Heading, Text, Code, Kbd, Link } from 'react-18-ui-library'
|
|
550
|
+
|
|
551
|
+
<Heading level="h1" size="3xl" weight="bold">Welcome back</Heading>
|
|
552
|
+
<Text color="muted" size="sm">Last updated 2 minutes ago</Text>
|
|
553
|
+
<Text>
|
|
554
|
+
Press <Kbd>⌘</Kbd> + <Kbd>K</Kbd> to open the command palette.
|
|
555
|
+
</Text>
|
|
556
|
+
<Code block language="tsx">{`const x: number = 42`}</Code>
|
|
557
|
+
<Link href="/docs" underline="hover">Read the docs</Link>
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
### Actions
|
|
563
|
+
|
|
564
|
+
| Component | Description |
|
|
565
|
+
|---|---|
|
|
566
|
+
| `CopyButton` | One-click copy to clipboard with animated feedback state |
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## Hooks
|
|
571
|
+
|
|
572
|
+
| Hook | Signature | Description |
|
|
573
|
+
|---|---|---|
|
|
574
|
+
| `useToast` | `() => { toast, dismiss, toasts }` | Trigger and dismiss toast notifications |
|
|
575
|
+
| `useTheme` | `() => { tokens, setToken }` | Read and update CSS token values at runtime |
|
|
576
|
+
| `useMediaQuery` | `(query: string) => boolean` | Reactive media query boolean |
|
|
577
|
+
| `useClickOutside` | `(ref, handler)` | Fire handler when clicking outside a ref |
|
|
578
|
+
| `useDebounce` | `(value, delay) => value` | Debounce any value |
|
|
579
|
+
| `useLocalStorage` | `(key, initial) => [value, setter]` | Persistent state backed by `localStorage` |
|
|
580
|
+
| `useClipboard` | `() => { copy, copied }` | Copy text with `copied` feedback state |
|
|
581
|
+
| `useWindowSize` | `() => { width, height }` | Reactive window dimensions |
|
|
582
|
+
| `useIntersectionObserver` | `(ref, options) => entry` | Intersection Observer hook |
|
|
583
|
+
| `useKeyboard` | `(shortcuts[]) => void` | Bind keyboard shortcuts declaratively |
|
|
584
|
+
| `useConfirm` | `() => confirmFn` | Programmatic confirm dialog (needs `ConfirmDialogProvider`) |
|
|
585
|
+
| `useCommandPalette` | `() => { open, close, isOpen }` | Control the command palette |
|
|
586
|
+
| `useFullScreenLoader` | `() => { show, hide, isVisible }` | Show/hide the full-screen loading overlay |
|
|
587
|
+
|
|
588
|
+
```tsx
|
|
589
|
+
import { useDebounce, useLocalStorage, useKeyboard, useClipboard } from 'react-18-ui-library'
|
|
590
|
+
|
|
591
|
+
const debouncedQuery = useDebounce(searchQuery, 300)
|
|
592
|
+
const [theme, setTheme] = useLocalStorage('theme', 'light')
|
|
593
|
+
const { copy, copied } = useClipboard()
|
|
594
|
+
|
|
595
|
+
useKeyboard([
|
|
596
|
+
{ key: 'k', meta: true, handler: () => openPalette() },
|
|
597
|
+
{ key: 'Escape', handler: () => closeModal() },
|
|
598
|
+
])
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
177
603
|
## CSS Token Reference
|
|
178
604
|
|
|
179
605
|
| Token | Default | Purpose |
|
|
180
606
|
|---|---|---|
|
|
181
607
|
| `--color-primary` | `#6366f1` | Primary brand color |
|
|
608
|
+
| `--color-primary-hover` | `#4f46e5` | Primary hover state |
|
|
609
|
+
| `--color-primary-foreground` | `#ffffff` | Text on primary background |
|
|
182
610
|
| `--color-background` | `#f8fafc` | Page background |
|
|
183
|
-
| `--color-surface` | `#ffffff` | Card/panel background |
|
|
611
|
+
| `--color-surface` | `#ffffff` | Card / panel background |
|
|
184
612
|
| `--color-text` | `#0f172a` | Primary text |
|
|
185
|
-
| `--color-text-muted` | `#64748b` | Secondary text |
|
|
186
|
-
| `--color-border` | `#e2e8f0` |
|
|
187
|
-
| `--color-error` | `#ef4444` | Error state |
|
|
188
|
-
| `--color-success` | `#22c55e` | Success state |
|
|
613
|
+
| `--color-text-muted` | `#64748b` | Secondary / helper text |
|
|
614
|
+
| `--color-border` | `#e2e8f0` | Default border color |
|
|
615
|
+
| `--color-error` | `#ef4444` | Error / destructive state |
|
|
616
|
+
| `--color-success` | `#22c55e` | Success / positive state |
|
|
617
|
+
| `--color-warning` | `#f59e0b` | Warning / caution state |
|
|
618
|
+
| `--radius-sm` | `0.25rem` | Small border radius |
|
|
189
619
|
| `--radius-md` | `0.375rem` | Default border radius |
|
|
190
|
-
| `--
|
|
620
|
+
| `--radius-lg` | `0.5rem` | Large border radius |
|
|
621
|
+
| `--font-family-base` | `system-ui` | Base font family |
|
|
191
622
|
| `--navbar-height` | `64px` | Navbar height |
|
|
192
623
|
| `--sidebar-width` | `260px` | Sidebar expanded width |
|
|
193
624
|
| `--sidebar-collapsed-width` | `64px` | Sidebar collapsed width |
|
|
194
625
|
|
|
626
|
+
---
|
|
627
|
+
|
|
628
|
+
## Security — Snyk
|
|
629
|
+
|
|
630
|
+
This library uses [Snyk](https://snyk.io) for dependency vulnerability scanning. Snyk runs automatically as a `prebuild` hook — the build is blocked if any **high** or **critical** severity vulnerabilities are found.
|
|
631
|
+
|
|
632
|
+
### Setup (one-time)
|
|
633
|
+
|
|
634
|
+
```bash
|
|
635
|
+
# Authenticate with Snyk (opens browser)
|
|
636
|
+
npx snyk auth
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### Available security scripts
|
|
640
|
+
|
|
641
|
+
| Script | Description |
|
|
642
|
+
|---|---|
|
|
643
|
+
| `npm run snyk:test` | Scan for high + critical vulnerabilities (used in `prebuild`) |
|
|
644
|
+
| `npm run snyk:test:all` | Scan for all severities including low and medium |
|
|
645
|
+
| `npm run snyk:monitor` | Upload snapshot to Snyk dashboard for continuous monitoring |
|
|
646
|
+
| `npm run snyk:report` | Run scan and generate `reports/snyk-report.html` |
|
|
647
|
+
| `npm run snyk:report:html` | Save raw JSON results then generate HTML report |
|
|
648
|
+
|
|
649
|
+
### How the build gate works
|
|
650
|
+
|
|
651
|
+
```
|
|
652
|
+
npm run build
|
|
653
|
+
└─ prebuild → snyk test --severity-threshold=high
|
|
654
|
+
├─ PASS → build proceeds normally
|
|
655
|
+
└─ FAIL → build aborted (exit code 1)
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
The `.snyk` policy file at the project root can be used to suppress known/accepted vulnerabilities:
|
|
659
|
+
|
|
660
|
+
```yaml
|
|
661
|
+
# .snyk
|
|
662
|
+
ignore:
|
|
663
|
+
SNYK-JS-EXAMPLE-12345:
|
|
664
|
+
- '*':
|
|
665
|
+
reason: Not exploitable in this context
|
|
666
|
+
expires: 2026-12-31T00:00:00.000Z
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
## Development
|
|
672
|
+
|
|
673
|
+
```bash
|
|
674
|
+
# Install dependencies
|
|
675
|
+
npm install
|
|
676
|
+
|
|
677
|
+
# Authenticate Snyk (first time only)
|
|
678
|
+
npx snyk auth
|
|
679
|
+
|
|
680
|
+
# Start Storybook dev server (port 6006)
|
|
681
|
+
npm run dev
|
|
682
|
+
|
|
683
|
+
# Type check
|
|
684
|
+
npm run type-check
|
|
685
|
+
|
|
686
|
+
# Run tests
|
|
687
|
+
npm test
|
|
688
|
+
|
|
689
|
+
# Run tests with coverage
|
|
690
|
+
npm run test:coverage
|
|
691
|
+
|
|
692
|
+
# Lint
|
|
693
|
+
npm run lint
|
|
694
|
+
|
|
695
|
+
# Build library (runs snyk:test first via prebuild hook)
|
|
696
|
+
npm run build
|
|
697
|
+
|
|
698
|
+
# Build Storybook static site
|
|
699
|
+
npm run build-storybook
|
|
700
|
+
|
|
701
|
+
# Run Snyk vulnerability scan
|
|
702
|
+
npm run snyk:test
|
|
703
|
+
|
|
704
|
+
# Generate HTML vulnerability report
|
|
705
|
+
npm run snyk:report:html
|
|
706
|
+
# → opens reports/snyk-report.html
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
195
711
|
## Peer Dependencies
|
|
196
712
|
|
|
197
|
-
|
|
198
|
-
|
|
713
|
+
| Package | Version |
|
|
714
|
+
|---|---|
|
|
715
|
+
| `react` | `>= 18.0.0` |
|
|
716
|
+
| `react-dom` | `>= 18.0.0` |
|
|
717
|
+
|
|
718
|
+
> TailwindCSS is **not** a peer dependency — the library ships pre-compiled CSS. You do not need Tailwind in your consuming app.
|
|
719
|
+
|
|
720
|
+
---
|
|
199
721
|
|
|
200
722
|
## License
|
|
201
723
|
|
|
202
|
-
MIT
|
|
724
|
+
MIT © react-18-ui-library contributors
|