@stackloop/ui 1.0.7
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 +638 -0
- package/dist/AudioRecorder.d.ts +9 -0
- package/dist/Badge.d.ts +12 -0
- package/dist/BottomSheet.d.ts +10 -0
- package/dist/Button.d.ts +13 -0
- package/dist/Card.d.ts +26 -0
- package/dist/Checkbox.d.ts +7 -0
- package/dist/DatePicker.d.ts +13 -0
- package/dist/Drawer.d.ts +10 -0
- package/dist/Dropdown.d.ts +19 -0
- package/dist/DualSlider.d.ts +15 -0
- package/dist/FileUpload.d.ts +19 -0
- package/dist/FloatingActionButton.d.ts +19 -0
- package/dist/Input.d.ts +9 -0
- package/dist/Modal.d.ts +18 -0
- package/dist/Pagination.d.ts +10 -0
- package/dist/RadioPills.d.ts +14 -0
- package/dist/Slider.d.ts +14 -0
- package/dist/StatusBadges.d.ts +12 -0
- package/dist/StepProgress.d.ts +11 -0
- package/dist/Table.d.ts +17 -0
- package/dist/Textarea.d.ts +7 -0
- package/dist/ThumbnailGrid.d.ts +16 -0
- package/dist/Toggle.d.ts +7 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +4478 -0
- package/dist/index.js.map +1 -0
- package/dist/lib-entry.d.ts +2 -0
- package/dist/stackloop-ui.css +1 -0
- package/dist/ui.css +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/vite.svg +1 -0
- package/package.json +74 -0
package/README.md
ADDED
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
## Installation
|
|
2
|
+
|
|
3
|
+
### NPM
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install --save @stackloop/ui
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### Peer Dependencies
|
|
10
|
+
|
|
11
|
+
Ensure `react` and `react-dom` (>=18) are installed.
|
|
12
|
+
|
|
13
|
+
### Next.js Setup
|
|
14
|
+
|
|
15
|
+
#### 1. Import Theme CSS
|
|
16
|
+
|
|
17
|
+
Import the library CSS in your root layout or `_app.tsx`:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// app/layout.tsx (App Router)
|
|
21
|
+
import '@stackloop/ui/theme.css'
|
|
22
|
+
|
|
23
|
+
export default function RootLayout({ children }) {
|
|
24
|
+
return (
|
|
25
|
+
<html lang="en">
|
|
26
|
+
<body>{children}</body>
|
|
27
|
+
</html>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// pages/_app.tsx (Pages Router)
|
|
34
|
+
import '@stackloop/ui/theme.css'
|
|
35
|
+
|
|
36
|
+
export default function App({ Component, pageProps }) {
|
|
37
|
+
return <Component {...pageProps} />
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### 2. Tailwind Configuration
|
|
42
|
+
|
|
43
|
+
If using Tailwind CSS v4 with `@theme`, the library's theme variables are already configured. If you need to customize colors:
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
/* In your global CSS or theme.css */
|
|
47
|
+
@theme {
|
|
48
|
+
/* Override library colors */
|
|
49
|
+
--color-primary: #your-color;
|
|
50
|
+
--color-primary-dark: #your-darker-color;
|
|
51
|
+
--color-border: #your-border-color;
|
|
52
|
+
--color-border-dark: #your-darker-border;
|
|
53
|
+
--color-secondary: #your-secondary-bg;
|
|
54
|
+
--color-background: #your-bg-color;
|
|
55
|
+
--color-foreground: #your-text-color;
|
|
56
|
+
|
|
57
|
+
/* Semantic colors */
|
|
58
|
+
--color-success: #10b981;
|
|
59
|
+
--color-warning: #f59e0b;
|
|
60
|
+
--color-error: #ef4444;
|
|
61
|
+
--color-info: #3b82f6;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Usage with React (Vite/CRA)
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
// In your main entry file (main.tsx or index.tsx)
|
|
69
|
+
import '@stackloop/ui/theme.css'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Importing Components
|
|
73
|
+
|
|
74
|
+
Import components from the package root:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { Button, Modal, Input } from '@stackloop/ui'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
All components are **client-side** components with `'use client'` directive, making them compatible with Next.js App Router.
|
|
81
|
+
|
|
82
|
+
## Theme Customization
|
|
83
|
+
|
|
84
|
+
The library uses a simplified color system with semantic variables:
|
|
85
|
+
|
|
86
|
+
### Color Variables
|
|
87
|
+
|
|
88
|
+
| Variable | Default | Description |
|
|
89
|
+
|----------|---------|-------------|
|
|
90
|
+
| `--color-primary` | `#525252` | Primary brand color |
|
|
91
|
+
| `--color-primary-dark` | `#404040` | Darker primary variant |
|
|
92
|
+
| `--color-border` | `#e5e5e5` | Default border color |
|
|
93
|
+
| `--color-border-dark` | `#d4d4d4` | Darker border variant |
|
|
94
|
+
| `--color-secondary` | `#fafafa` | Secondary background |
|
|
95
|
+
| `--color-background` | `#ffffff` | Main background |
|
|
96
|
+
| `--color-foreground` | `#171717` | Primary text color |
|
|
97
|
+
| `--color-success` | `#10b981` | Success state |
|
|
98
|
+
| `--color-warning` | `#f59e0b` | Warning state |
|
|
99
|
+
| `--color-error` | `#ef4444` | Error state |
|
|
100
|
+
| `--color-info` | `#3b82f6` | Info state |
|
|
101
|
+
|
|
102
|
+
### Customizing Colors
|
|
103
|
+
|
|
104
|
+
Create a custom theme file or extend the existing one:
|
|
105
|
+
|
|
106
|
+
```css
|
|
107
|
+
/* styles/custom-theme.css */
|
|
108
|
+
@import '@stackloop/ui/theme.css';
|
|
109
|
+
|
|
110
|
+
@theme {
|
|
111
|
+
/* Brand colors */
|
|
112
|
+
--color-primary: #3b82f6;
|
|
113
|
+
--color-primary-dark: #2563eb;
|
|
114
|
+
|
|
115
|
+
/* Borders */
|
|
116
|
+
--color-border: #e2e8f0;
|
|
117
|
+
--color-border-dark: #cbd5e1;
|
|
118
|
+
|
|
119
|
+
/* Backgrounds */
|
|
120
|
+
--color-secondary: #f8fafc;
|
|
121
|
+
--color-background: #ffffff;
|
|
122
|
+
--color-foreground: #0f172a;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Dark Mode Support
|
|
127
|
+
|
|
128
|
+
You can add dark mode variants:
|
|
129
|
+
|
|
130
|
+
```css
|
|
131
|
+
@theme {
|
|
132
|
+
/* Light mode (default) */
|
|
133
|
+
--color-background: #ffffff;
|
|
134
|
+
--color-foreground: #171717;
|
|
135
|
+
|
|
136
|
+
/* Dark mode */
|
|
137
|
+
@media (prefers-color-scheme: dark) {
|
|
138
|
+
--color-background: #0a0a0a;
|
|
139
|
+
--color-foreground: #fafafa;
|
|
140
|
+
--color-primary: #60a5fa;
|
|
141
|
+
--color-border: #27272a;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Components Reference
|
|
147
|
+
|
|
148
|
+
- For each component below you'll find a short description, props (type, default, notes) and a minimal usage example.
|
|
149
|
+
|
|
150
|
+
**Checkbox**:
|
|
151
|
+
- **Description:** Accessible checkbox with optional label and description.
|
|
152
|
+
- **Props:**
|
|
153
|
+
- **`label`**: `string` — optional.
|
|
154
|
+
- **`description`**: `string` — optional.
|
|
155
|
+
- **`onChange`**: `(checked: boolean) => void` — optional.
|
|
156
|
+
- **`className`**: `string` — optional.
|
|
157
|
+
- Inherits standard `input` props (e.g. `disabled`, `defaultChecked`, `checked`).
|
|
158
|
+
- **Usage:**
|
|
159
|
+
|
|
160
|
+
```jsx
|
|
161
|
+
import { Checkbox } from '@stackloop/ui'
|
|
162
|
+
|
|
163
|
+
<Checkbox label="Accept terms" onChange={(v) => console.log(v)} />
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Button**:
|
|
167
|
+
- **Description:** Animated button with variants, sizes, icons and loading state.
|
|
168
|
+
- **Props:**
|
|
169
|
+
- **`variant`**: `'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'` — default: `'primary'`.
|
|
170
|
+
- **`size`**: `'sm' | 'md' | 'lg'` — default: `'md'`.
|
|
171
|
+
- **`loading`**: `boolean` — default: `false`.
|
|
172
|
+
- **`icon`**: `ReactNode` — optional.
|
|
173
|
+
- **`className`**: `string` — optional.
|
|
174
|
+
- Inherits standard `button` props.
|
|
175
|
+
- **Usage:**
|
|
176
|
+
|
|
177
|
+
```jsx
|
|
178
|
+
import { Button } from '@stackloop/ui'
|
|
179
|
+
|
|
180
|
+
<Button variant="outline" size="lg" onClick={() => {}}>Save</Button>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Input**:
|
|
184
|
+
- **Description:** Text input with label, error and optional icons.
|
|
185
|
+
- **Props:**
|
|
186
|
+
- **`label`**: `string` — optional.
|
|
187
|
+
- **`error`**: `string` — optional.
|
|
188
|
+
- **`hint`**: `string` — optional.
|
|
189
|
+
- **`leftIcon`** / **`rightIcon`**: `ReactNode` — optional.
|
|
190
|
+
- **`className`**: `string` — optional.
|
|
191
|
+
- Inherits `input` HTML attributes.
|
|
192
|
+
- **Usage:**
|
|
193
|
+
|
|
194
|
+
```jsx
|
|
195
|
+
import { Input } from '@stackloop/ui'
|
|
196
|
+
|
|
197
|
+
<Input label="Email" placeholder="you@example.com" />
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Modal**:
|
|
201
|
+
- **Description:** Centered modal with backdrop, title and Escape-to-close handling.
|
|
202
|
+
- **Props:**
|
|
203
|
+
- **`isOpen`**: `boolean` — required.
|
|
204
|
+
- **`onClose`**: `() => void` — required.
|
|
205
|
+
- **`children`**: `ReactNode` — required.
|
|
206
|
+
- **`title`**: `string` — optional.
|
|
207
|
+
- **`size`**: `'sm'|'md'|'lg'|'xl'|'full'` — default: `'md'`.
|
|
208
|
+
- **`className`**: `string` — optional.
|
|
209
|
+
- **Subcomponents:** `ModalContent`, `ModalFooter`.
|
|
210
|
+
- **Usage:**
|
|
211
|
+
|
|
212
|
+
```jsx
|
|
213
|
+
import { Modal, ModalContent, ModalFooter } from '@stackloop/ui'
|
|
214
|
+
|
|
215
|
+
<Modal isOpen={open} onClose={() => setOpen(false)} title="My Modal">
|
|
216
|
+
<ModalContent>Body</ModalContent>
|
|
217
|
+
<ModalFooter>
|
|
218
|
+
<button onClick={() => setOpen(false)}>Close</button>
|
|
219
|
+
</ModalFooter>
|
|
220
|
+
</Modal>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Table**:
|
|
224
|
+
- **Description:** Generic sortable table component with loading skeleton, row interactions, and responsive design. Supports client-side sorting and custom cell rendering.
|
|
225
|
+
- **Props:**
|
|
226
|
+
- **`data`**: `T[]` — required. Array of data items to display in the table.
|
|
227
|
+
- **`columns`**: `Column<T>[]` — required. Array of column definitions (see Column interface below).
|
|
228
|
+
- **`loading`**: `boolean` — optional. Shows animated skeleton loading state when true.
|
|
229
|
+
- **`onRowClick`**: `(item: T) => void` — optional. Callback fired when a row is clicked. Adds hover effect and pointer cursor to rows.
|
|
230
|
+
- **`keyExtractor`**: `(item: T) => string` — required. Function to extract unique key from each data item for React reconciliation.
|
|
231
|
+
- **`className`**: `string` — optional. Additional CSS classes for the table wrapper.
|
|
232
|
+
|
|
233
|
+
- **Column Interface:**
|
|
234
|
+
```typescript
|
|
235
|
+
interface Column<T> {
|
|
236
|
+
key: string; // Unique column identifier and default accessor key
|
|
237
|
+
header: string; // Column header text displayed in table header
|
|
238
|
+
sortable?: boolean; // Enable sorting for this column (default: false)
|
|
239
|
+
render?: (item: T) => ReactNode; // Custom render function for cell content
|
|
240
|
+
width?: string; // CSS width value (e.g., '100px', '20%', 'auto')
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
- **Sorting Behavior:**
|
|
245
|
+
- Click sortable column headers to toggle between ascending → descending → no sort.
|
|
246
|
+
- Sort icons: `ChevronUp` (ascending), `ChevronDown` (descending), `ChevronsUpDown` (sortable but not active).
|
|
247
|
+
- Sorting is client-side using JavaScript's `localeCompare` for strings and numeric comparison for numbers.
|
|
248
|
+
- Only one column can be sorted at a time.
|
|
249
|
+
|
|
250
|
+
- **Loading State:**
|
|
251
|
+
- When `loading={true}`, displays skeleton rows with animated gradient shimmer.
|
|
252
|
+
- Shows 5 skeleton rows by default, preserving table structure and column widths.
|
|
253
|
+
- Loading state prevents interactions and hides real data.
|
|
254
|
+
|
|
255
|
+
- **Styling & Customization:**
|
|
256
|
+
- Responsive: Horizontal scroll on mobile, full table view on desktop.
|
|
257
|
+
- Hover states: Rows have hover background when `onRowClick` is provided.
|
|
258
|
+
- Colors: Uses semantic color tokens (`border`, `border-dark`, `background`, `foreground-color`, `primary`).
|
|
259
|
+
- Animations: Powered by Framer Motion for smooth row entry and sorting transitions.
|
|
260
|
+
|
|
261
|
+
- **Usage:**
|
|
262
|
+
|
|
263
|
+
```jsx
|
|
264
|
+
import { Table } from '@stackloop/ui'
|
|
265
|
+
|
|
266
|
+
// Basic example
|
|
267
|
+
const columns = [
|
|
268
|
+
{ key: 'id', header: 'ID', width: '80px' },
|
|
269
|
+
{ key: 'name', header: 'Name', sortable: true },
|
|
270
|
+
{
|
|
271
|
+
key: 'status',
|
|
272
|
+
header: 'Status',
|
|
273
|
+
render: (item) => <Badge variant={item.status === 'active' ? 'success' : 'default'}>{item.status}</Badge>
|
|
274
|
+
}
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
<Table
|
|
278
|
+
data={users}
|
|
279
|
+
columns={columns}
|
|
280
|
+
keyExtractor={(user) => String(user.id)}
|
|
281
|
+
onRowClick={(user) => navigate(`/users/${user.id}`)}
|
|
282
|
+
loading={isLoading}
|
|
283
|
+
/>
|
|
284
|
+
|
|
285
|
+
// Advanced example with custom rendering
|
|
286
|
+
const columns = [
|
|
287
|
+
{ key: 'avatar', header: '', width: '50px', render: (u) => <img src={u.avatar} /> },
|
|
288
|
+
{ key: 'name', header: 'Full Name', sortable: true },
|
|
289
|
+
{ key: 'email', header: 'Email Address', sortable: true },
|
|
290
|
+
{
|
|
291
|
+
key: 'createdAt',
|
|
292
|
+
header: 'Joined',
|
|
293
|
+
sortable: true,
|
|
294
|
+
render: (u) => new Date(u.createdAt).toLocaleDateString()
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Dropdown**:
|
|
300
|
+
- **Description:** Select with optional search, clear and icons.
|
|
301
|
+
- **Props:**
|
|
302
|
+
- **`options`**: `{ value: string; label: string; icon?: ReactNode }[]` — required.
|
|
303
|
+
- **`value`**: `string` — optional.
|
|
304
|
+
- **`onChange`**: `(value: string) => void` — required.
|
|
305
|
+
- **`placeholder`**: `string` — default: `'Select an option'`.
|
|
306
|
+
- **`label`**, **`error`**, **`searchable`** (default `false`), **`clearable`** (default `true`), **`disabled`**, **`className`**.
|
|
307
|
+
- **Usage:**
|
|
308
|
+
|
|
309
|
+
```jsx
|
|
310
|
+
import { Dropdown } from '@stackloop/ui'
|
|
311
|
+
|
|
312
|
+
<Dropdown options={[{value:'a',label:'A'}]} value={val} onChange={setVal} searchable />
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**BottomSheet**:
|
|
316
|
+
- **Description:** Mobile bottom sheet with header and optional close button.
|
|
317
|
+
- **Props:**
|
|
318
|
+
- **`isOpen`**: `boolean` — required.
|
|
319
|
+
- **`onClose`**: `() => void` — required.
|
|
320
|
+
- **`title`**: `string` — optional.
|
|
321
|
+
- **`children`**: `ReactNode` — required.
|
|
322
|
+
- **`showCloseButton`**: `boolean` — default: `true`.
|
|
323
|
+
- **`className`**: `string` — optional.
|
|
324
|
+
- **Usage:**
|
|
325
|
+
|
|
326
|
+
```jsx
|
|
327
|
+
import { BottomSheet } from '@stackloop/ui'
|
|
328
|
+
|
|
329
|
+
<BottomSheet isOpen={open} onClose={() => setOpen(false)} title="Actions">...</BottomSheet>
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**DatePicker**:
|
|
333
|
+
- **Description:** Date picker with month navigation and min/max options.
|
|
334
|
+
- **Props:**
|
|
335
|
+
- **`value`**: `Date` — optional.
|
|
336
|
+
- **`onChange`**: `(date: Date) => void` — required.
|
|
337
|
+
- **`label`**, **`placeholder`** (default `'Select date'`), **`error`**, **`disabled`**, **`minDate`**, **`maxDate`**, **`className`**.
|
|
338
|
+
- **Usage:**
|
|
339
|
+
|
|
340
|
+
```jsx
|
|
341
|
+
import { DatePicker } from '@stackloop/ui'
|
|
342
|
+
|
|
343
|
+
<DatePicker value={date} onChange={setDate} />
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**DualSlider**:
|
|
347
|
+
- **Description:** Two linked sliders showing relative portions.
|
|
348
|
+
- **Props:**
|
|
349
|
+
- **`value1`**, **`value2`**: `number` — required.
|
|
350
|
+
- **`onChange`**: `(v1:number, v2:number)=>void` — required.
|
|
351
|
+
- **`label1`**, **`label2`**: `string` — required.
|
|
352
|
+
- **`min`** (default `0`), **`max`** (default `100`), **`step`** (default `1`), **`unit`** (default `'%')`, **`disabled`**, **`className`**.
|
|
353
|
+
- **Usage:**
|
|
354
|
+
|
|
355
|
+
```jsx
|
|
356
|
+
import { DualSlider } from '@stackloop/ui'
|
|
357
|
+
|
|
358
|
+
<DualSlider value1={30} value2={70} onChange={(a,b)=>{}} label1="A" label2="B" />
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Pagination**:
|
|
362
|
+
- **Description:** Pagination with previous/next and numeric buttons.
|
|
363
|
+
- **Props:**
|
|
364
|
+
- **`currentPage`**: `number` — required.
|
|
365
|
+
- **`totalPages`**: `number` — required.
|
|
366
|
+
- **`onPageChange`**: `(page:number)=>void` — required.
|
|
367
|
+
- **`totalItems`**, **`itemsPerPage`**, **`className`** — optional.
|
|
368
|
+
- **Usage:**
|
|
369
|
+
|
|
370
|
+
```jsx
|
|
371
|
+
import { Pagination } from '@stackloop/ui'
|
|
372
|
+
|
|
373
|
+
<Pagination currentPage={1} totalPages={10} onPageChange={setPage} />
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Badge**:
|
|
377
|
+
- **Description:** Inline badge with color variants and optional dot.
|
|
378
|
+
- **Props:**
|
|
379
|
+
- **`children`**: `ReactNode` — required.
|
|
380
|
+
- **`variant`**: `'default'|'primary'|'success'|'warning'|'danger'|'info'` — default: `'default'`.
|
|
381
|
+
- **`size`**: `'sm'|'md'|'lg'` — default: `'md'`.
|
|
382
|
+
- **`dot`**: `boolean` — default: `false`.
|
|
383
|
+
- **`className`**: `string` — optional.
|
|
384
|
+
- **Usage:**
|
|
385
|
+
|
|
386
|
+
```jsx
|
|
387
|
+
import { Badge } from '@stackloop/ui'
|
|
388
|
+
|
|
389
|
+
<Badge variant="primary">New</Badge>
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**FloatingActionButton (FAB)**:
|
|
393
|
+
- **Description:** Floating action button with expanded action list support.
|
|
394
|
+
- **Props:**
|
|
395
|
+
- **`icon`**, **`label`**, **`onClick`**, **`actions`** (array of `{label, icon, onClick, variant}`) — optional.
|
|
396
|
+
- **`variant`**: `'primary'|'secondary'` — default: `'primary'`.
|
|
397
|
+
- **`position`**: `'bottom-right'|'bottom-left'|'bottom-center'` — default: `'bottom-right'`.
|
|
398
|
+
- **`disabled`**, **`className`**.
|
|
399
|
+
- **Usage:**
|
|
400
|
+
|
|
401
|
+
```jsx
|
|
402
|
+
import { FloatingActionButton as FAB } from '@stackloop/ui'
|
|
403
|
+
|
|
404
|
+
<FAB label="New" onClick={()=>{}} />
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**AudioRecorder**:
|
|
408
|
+
- **Description:** Browser audio recorder component using MediaRecorder.
|
|
409
|
+
- **Props:**
|
|
410
|
+
- **`onRecordingComplete`**: `(audioBlob: Blob) => void` — required.
|
|
411
|
+
- **`label`**: `string` — default: `'Record Audio'`.
|
|
412
|
+
- **`maxDuration`**: `number` — default: `300` seconds.
|
|
413
|
+
- **`disabled`**, **`className`**.
|
|
414
|
+
- **Usage:**
|
|
415
|
+
|
|
416
|
+
```jsx
|
|
417
|
+
import { AudioRecorder } from '@stackloop/ui'
|
|
418
|
+
|
|
419
|
+
<AudioRecorder onRecordingComplete={(blob)=>{ /* handle */ }} />
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Drawer**:
|
|
423
|
+
- **Description:** Side drawer that slides in from the left or right.
|
|
424
|
+
- **Props:**
|
|
425
|
+
- **`isOpen`**: `boolean` — required.
|
|
426
|
+
- **`onClose`**: `() => void` — required.
|
|
427
|
+
- **`children`**: `ReactNode` — required.
|
|
428
|
+
- **`title`**: `string` — optional.
|
|
429
|
+
- **`position`**: `'left'|'right'` — default: `'right'`.
|
|
430
|
+
- **`className`**: `string` — optional.
|
|
431
|
+
- **Usage:**
|
|
432
|
+
|
|
433
|
+
```jsx
|
|
434
|
+
import { Drawer } from '@stackloop/ui'
|
|
435
|
+
|
|
436
|
+
<Drawer isOpen={open} onClose={()=>setOpen(false)} title="Menu">...</Drawer>
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Toggle**:
|
|
440
|
+
- **Description:** Switch control with label and description.
|
|
441
|
+
- **Props:**
|
|
442
|
+
- **`label`**: `string` — optional.
|
|
443
|
+
- **`description`**: `string` — optional.
|
|
444
|
+
- **`onChange`**: `(checked:boolean)=>void` — optional.
|
|
445
|
+
- **`className`**: `string` — optional.
|
|
446
|
+
- Inherits input attributes such as `checked`, `disabled`.
|
|
447
|
+
- **Usage:**
|
|
448
|
+
|
|
449
|
+
```jsx
|
|
450
|
+
import { Toggle } from '@stackloop/ui'
|
|
451
|
+
|
|
452
|
+
<Toggle label="Enable" onChange={(v)=>console.log(v)} />
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**StatusBadges** (OfflineBadge, SyncIndicator):
|
|
456
|
+
- **OfflineBadge Props:** `isOffline: boolean` (required), `className?: string`.
|
|
457
|
+
- **SyncIndicator Props:** `status: 'synced'|'syncing'|'unsynced'|'error'` (required), `count?: number`, `className?: string`.
|
|
458
|
+
- **Usage:**
|
|
459
|
+
|
|
460
|
+
```jsx
|
|
461
|
+
import { OfflineBadge, SyncIndicator } from '@stackloop/ui'
|
|
462
|
+
|
|
463
|
+
<OfflineBadge isOffline={true} />
|
|
464
|
+
<SyncIndicator status="syncing" />
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Slider**:
|
|
468
|
+
- **Description:** Single-value slider with optional unit and label.
|
|
469
|
+
- **Props:**
|
|
470
|
+
- **`value`**: `number` — required.
|
|
471
|
+
- **`onChange`**: `(value:number)=>void` — required.
|
|
472
|
+
- **`min`** (default `0`), **`max`** (default `100`), **`step`** (default `1`), **`label`**, **`showValue`** (default `true`), **`unit`** (default `'%')`, **`disabled`**, **`className`**.
|
|
473
|
+
- **Usage:**
|
|
474
|
+
|
|
475
|
+
```jsx
|
|
476
|
+
import { Slider } from '@stackloop/ui'
|
|
477
|
+
|
|
478
|
+
<Slider value={50} onChange={setValue} />
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**RadioPills**:
|
|
482
|
+
- **Description:** Radio group styled as pill buttons.
|
|
483
|
+
- **Props:**
|
|
484
|
+
- **`options`**: `{ value: string; label: string; icon?: ReactNode }[]` — required.
|
|
485
|
+
- **`value`**: `string` — optional.
|
|
486
|
+
- **`onChange`**: `(v:string)=>void` — optional.
|
|
487
|
+
- **`name`**: `string` — required.
|
|
488
|
+
- **`disabled`**: `boolean` — optional.
|
|
489
|
+
- **`className`**: `string` — optional.
|
|
490
|
+
- **Usage:**
|
|
491
|
+
|
|
492
|
+
```jsx
|
|
493
|
+
import { RadioPills } from '@stackloop/ui'
|
|
494
|
+
|
|
495
|
+
<RadioPills name="mode" options={[{value:'a',label:'A'}]} />
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**Textarea**:
|
|
499
|
+
- **Description:** Multiline input with label, error and helper text.
|
|
500
|
+
- **Props:**
|
|
501
|
+
- **`label`**: `string` — optional.
|
|
502
|
+
- **`error`**: `string` — optional.
|
|
503
|
+
- **`helperText`**: `string` — optional.
|
|
504
|
+
- **`className`**: `string` — optional.
|
|
505
|
+
- Inherits native `textarea` attributes.
|
|
506
|
+
- **Usage:**
|
|
507
|
+
|
|
508
|
+
```jsx
|
|
509
|
+
import { Textarea } from '@stackloop/ui'
|
|
510
|
+
|
|
511
|
+
<Textarea label="Message" helperText="Max 500 chars" />
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
**ThumbnailGrid**:
|
|
515
|
+
- **Description:** Grid to display thumbnails for images, documents, audio with optional remove/view actions.
|
|
516
|
+
- **Props:**
|
|
517
|
+
- **`items`**: `{ id, name, url, type:'image'|'document'|'audio', size? }[]` — required.
|
|
518
|
+
- **`onRemove`**: `(id:string)=>void` — optional.
|
|
519
|
+
- **`onView`**: `(item)=>void` — optional.
|
|
520
|
+
- **`columns`**: `2|3|4` — default: `3`.
|
|
521
|
+
- **`className`**.
|
|
522
|
+
- **Usage:**
|
|
523
|
+
|
|
524
|
+
```jsx
|
|
525
|
+
import { ThumbnailGrid } from '@stackloop/ui'
|
|
526
|
+
|
|
527
|
+
<ThumbnailGrid items={items} onRemove={(id)=>{}} />
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**Card**:
|
|
531
|
+
- **Description:** Generic card container with variants and content subcomponents.
|
|
532
|
+
- **Props:**
|
|
533
|
+
- **`children`**: `ReactNode` — required.
|
|
534
|
+
- **`variant`**: `'default'|'outlined'|'elevated'` — default: `'default'`.
|
|
535
|
+
- **`padding`**: `'sm'|'md'|'lg'|'none'` — default: `'md'`.
|
|
536
|
+
- **`onClick`**: `() => void` — optional.
|
|
537
|
+
- **`hover`**: `boolean` — default: `false`.
|
|
538
|
+
- **`className`**: `string` — optional.
|
|
539
|
+
- **Subcomponents:** `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`.
|
|
540
|
+
- **Usage:**
|
|
541
|
+
|
|
542
|
+
```jsx
|
|
543
|
+
import { Card, CardTitle, CardContent } from '@stackloop/ui'
|
|
544
|
+
|
|
545
|
+
<Card variant="elevated"> <CardTitle>Title</CardTitle> <CardContent>Body</CardContent> </Card>
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
**FileUpload (CameraCapture, FileUploader)**:
|
|
549
|
+
- **CameraCapture Props:** `onCapture(file:File) => void` (required), `onRemove?`, `preview?`, `label?`, `disabled?`, `className?`.
|
|
550
|
+
- **FileUploader Props:** `onUpload(files:File[]) => void` (required), `accept?` (default `'*/*'`), `multiple?` (default `false`), `label?`, `disabled?`, `className?`.
|
|
551
|
+
- **Usage:**
|
|
552
|
+
|
|
553
|
+
```jsx
|
|
554
|
+
import { FileUploader, CameraCapture } from '@stackloop/ui'
|
|
555
|
+
|
|
556
|
+
<FileUploader onUpload={(files)=>{}} multiple accept="image/*" />
|
|
557
|
+
<CameraCapture onCapture={(file)=>{}} />
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**StepProgress**:
|
|
561
|
+
- **Description:** Horizontal stepper (desktop) and compact dots view (mobile) showing progress.
|
|
562
|
+
- **Props:**
|
|
563
|
+
- **`steps`**: `{ label: string; description?: string }[]` — required.
|
|
564
|
+
- **`currentStep`**: `number` — required.
|
|
565
|
+
- **`className`**: optional.
|
|
566
|
+
- **Usage:**
|
|
567
|
+
|
|
568
|
+
```jsx
|
|
569
|
+
import { StepProgress } from '@stackloop/ui'
|
|
570
|
+
|
|
571
|
+
<StepProgress steps={[{label:'One'},{label:'Two'}]} currentStep={1} />
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## Next.js Best Practices
|
|
577
|
+
|
|
578
|
+
### App Router
|
|
579
|
+
|
|
580
|
+
All components work seamlessly with Next.js 13+ App Router. They include the `'use client'` directive:
|
|
581
|
+
|
|
582
|
+
```tsx
|
|
583
|
+
// app/page.tsx
|
|
584
|
+
import { Button, Card } from '@stackloop/ui'
|
|
585
|
+
|
|
586
|
+
export default function Page() {
|
|
587
|
+
return (
|
|
588
|
+
<div>
|
|
589
|
+
<Card>
|
|
590
|
+
<Button onClick={() => console.log('clicked')}>Click me</Button>
|
|
591
|
+
</Card>
|
|
592
|
+
</div>
|
|
593
|
+
)
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### Server Components
|
|
598
|
+
|
|
599
|
+
To use these components with Server Components, import them in client components:
|
|
600
|
+
|
|
601
|
+
```tsx
|
|
602
|
+
// components/ClientWrapper.tsx
|
|
603
|
+
'use client'
|
|
604
|
+
import { Button } from '@stackloop/ui'
|
|
605
|
+
|
|
606
|
+
export function ClientButton() {
|
|
607
|
+
return <Button onClick={() => alert('Hello')}>Click</Button>
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
```tsx
|
|
612
|
+
// app/page.tsx (Server Component)
|
|
613
|
+
import { ClientButton } from '@/components/ClientWrapper'
|
|
614
|
+
|
|
615
|
+
export default function Page() {
|
|
616
|
+
return <ClientButton />
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Styling Considerations
|
|
621
|
+
|
|
622
|
+
- The library uses Tailwind CSS v4 with `@theme` directive
|
|
623
|
+
- Ensure your Next.js project is configured for Tailwind CSS v4
|
|
624
|
+
- All animations use Framer Motion and are optimized for performance
|
|
625
|
+
|
|
626
|
+
## Browser Support
|
|
627
|
+
|
|
628
|
+
- Modern browsers (Chrome, Firefox, Safari, Edge)
|
|
629
|
+
- Mobile browsers (iOS Safari, Chrome Mobile)
|
|
630
|
+
- Audio recording requires MediaRecorder API support
|
|
631
|
+
|
|
632
|
+
## License
|
|
633
|
+
|
|
634
|
+
MIT
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
For questions, issues, or contributions, please visit the [GitHub repository](https://github.com/AtutiBonface/@stackloop/ui).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface AudioRecorderProps {
|
|
3
|
+
onRecordingComplete: (audioBlob: Blob) => void;
|
|
4
|
+
label?: string;
|
|
5
|
+
maxDuration?: number;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const AudioRecorder: React.FC<AudioRecorderProps>;
|
package/dist/Badge.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
type BadgeVariant = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
|
|
3
|
+
type BadgeSize = 'sm' | 'md' | 'lg';
|
|
4
|
+
export interface BadgeProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
variant?: BadgeVariant;
|
|
7
|
+
size?: BadgeSize;
|
|
8
|
+
className?: string;
|
|
9
|
+
dot?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const Badge: React.FC<BadgeProps>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface BottomSheetProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
title?: string;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
showCloseButton?: boolean;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const BottomSheet: React.FC<BottomSheetProps>;
|
package/dist/Button.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type HTMLMotionProps } from 'framer-motion';
|
|
3
|
+
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
4
|
+
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
|
5
|
+
size?: 'sm' | 'md' | 'lg';
|
|
6
|
+
loading?: boolean;
|
|
7
|
+
icon?: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
type MotionButtonProps = Omit<ButtonProps, 'children' | keyof HTMLMotionProps<'button'>> & {
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
} & HTMLMotionProps<'button'>;
|
|
12
|
+
export declare const Button: React.ForwardRefExoticComponent<Omit<MotionButtonProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
13
|
+
export {};
|