@dsbtek/component-library 1.0.4 → 1.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 +840 -235
- package/dist/index.d.mts +167 -2
- package/dist/index.d.ts +167 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @dsbtek/component-library
|
|
2
2
|
|
|
3
|
-
A collection of advanced React components built with shadcn/ui.
|
|
3
|
+
A collection of advanced React components built with TypeScript, Tailwind CSS, and shadcn/ui.
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
@@ -12,98 +12,100 @@ A collection of advanced React components built with shadcn/ui.
|
|
|
12
12
|
- [DateTimePicker](#datetimepicker)
|
|
13
13
|
- [FileInput](#fileinput)
|
|
14
14
|
- [MultiSelect](#multiselect)
|
|
15
|
+
- [MultiStepper](#multistepper)
|
|
15
16
|
- [PasswordInput](#passwordinput)
|
|
16
17
|
- [PhoneInput](#phoneinput)
|
|
17
18
|
- [ResponsiveAlertDialog](#responsivealertdialog)
|
|
18
19
|
- [ResponsiveDialog](#responsivedialog)
|
|
20
|
+
- [TagInput](#taginput)
|
|
19
21
|
- [Styling](#styling)
|
|
20
22
|
- [Contributing](#contributing)
|
|
21
23
|
- [License](#license)
|
|
22
24
|
|
|
23
25
|
## Installation
|
|
24
26
|
|
|
25
|
-
1. Install the package
|
|
27
|
+
1. Install the package:
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
|
-
# Install the component library
|
|
29
30
|
npm install @dsbtek/component-library
|
|
31
|
+
````
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
npm install @radix-ui/react-popover cmdk class-variance-authority clsx lucide-react tailwind-merge
|
|
33
|
-
```
|
|
33
|
+
2. Install peer dependencies:
|
|
34
34
|
|
|
35
|
-
2. Set up Tailwind CSS in your project:
|
|
36
35
|
|
|
37
36
|
```bash
|
|
38
|
-
|
|
39
|
-
npm install -D tailwindcss postcss autoprefixer tailwindcss-animate
|
|
37
|
+
npm install react@^18 react-dom@^18
|
|
40
38
|
```
|
|
41
39
|
|
|
42
|
-
3.
|
|
40
|
+
3. Set up Tailwind CSS:
|
|
41
|
+
|
|
43
42
|
|
|
44
43
|
```bash
|
|
44
|
+
npm install -D tailwindcss postcss autoprefixer
|
|
45
45
|
npx tailwindcss init -p
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
4. Update your `tailwind.config.js`:
|
|
49
49
|
|
|
50
|
+
|
|
50
51
|
```javascript
|
|
51
52
|
/** @type {import('tailwindcss').Config} */
|
|
52
53
|
module.exports = {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
borderRadius: {
|
|
96
|
-
lg: `var(--radius)`,
|
|
97
|
-
md: `calc(var(--radius) - 2px)`,
|
|
98
|
-
sm: 'calc(var(--radius) - 4px)',
|
|
99
|
-
},
|
|
54
|
+
darkMode: ['class'],
|
|
55
|
+
content: [
|
|
56
|
+
'./src/**/*.{js,ts,jsx,tsx}',
|
|
57
|
+
'./node_modules/@dsbtek/component-library/**/*.{js,ts,jsx,tsx}',
|
|
58
|
+
],
|
|
59
|
+
theme: {
|
|
60
|
+
extend: {
|
|
61
|
+
colors: {
|
|
62
|
+
border: 'hsl(var(--border))',
|
|
63
|
+
input: 'hsl(var(--input))',
|
|
64
|
+
ring: 'hsl(var(--ring))',
|
|
65
|
+
background: 'hsl(var(--background))',
|
|
66
|
+
foreground: 'hsl(var(--foreground))',
|
|
67
|
+
primary: {
|
|
68
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
69
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
70
|
+
},
|
|
71
|
+
secondary: {
|
|
72
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
73
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
74
|
+
},
|
|
75
|
+
destructive: {
|
|
76
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
77
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
78
|
+
},
|
|
79
|
+
muted: {
|
|
80
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
81
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
82
|
+
},
|
|
83
|
+
accent: {
|
|
84
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
85
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
86
|
+
},
|
|
87
|
+
popover: {
|
|
88
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
89
|
+
foreground: 'hsl(var(--popover-foreground))',
|
|
90
|
+
},
|
|
91
|
+
card: {
|
|
92
|
+
DEFAULT: 'hsl(var(--card))',
|
|
93
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
100
94
|
},
|
|
95
|
+
},
|
|
96
|
+
borderRadius: {
|
|
97
|
+
lg: 'var(--radius)',
|
|
98
|
+
md: 'calc(var(--radius) - 2px)',
|
|
99
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
100
|
+
},
|
|
101
101
|
},
|
|
102
|
-
|
|
102
|
+
},
|
|
103
|
+
plugins: [require('tailwindcss-animate')],
|
|
103
104
|
};
|
|
104
105
|
```
|
|
105
106
|
|
|
106
|
-
5. Add
|
|
107
|
+
5. Add these CSS variables to your global CSS:
|
|
108
|
+
|
|
107
109
|
|
|
108
110
|
```css
|
|
109
111
|
@tailwind base;
|
|
@@ -111,210 +113,807 @@ module.exports = {
|
|
|
111
113
|
@tailwind utilities;
|
|
112
114
|
|
|
113
115
|
@layer base {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
116
|
+
:root {
|
|
117
|
+
--background: 0 0% 100%;
|
|
118
|
+
--foreground: 222.2 84% 4.9%;
|
|
119
|
+
--card: 0 0% 100%;
|
|
120
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
121
|
+
--popover: 0 0% 100%;
|
|
122
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
123
|
+
--primary: 222.2 47.4% 11.2%;
|
|
124
|
+
--primary-foreground: 210 40% 98%;
|
|
125
|
+
--secondary: 210 40% 96.1%;
|
|
126
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
127
|
+
--muted: 210 40% 96.1%;
|
|
128
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
129
|
+
--accent: 210 40% 96.1%;
|
|
130
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
131
|
+
--destructive: 0 84.2% 60.2%;
|
|
132
|
+
--destructive-foreground: 210 40% 98%;
|
|
133
|
+
--border: 214.3 31.8% 91.4%;
|
|
134
|
+
--input: 214.3 31.8% 91.4%;
|
|
135
|
+
--ring: 222.2 84% 4.9%;
|
|
136
|
+
--radius: 0.5rem;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.dark {
|
|
140
|
+
--background: 222.2 84% 4.9%;
|
|
141
|
+
--foreground: 210 40% 98%;
|
|
142
|
+
--card: 222.2 84% 4.9%;
|
|
143
|
+
--card-foreground: 210 40% 98%;
|
|
144
|
+
--popover: 222.2 84% 4.9%;
|
|
145
|
+
--popover-foreground: 210 40% 98%;
|
|
146
|
+
--primary: 210 40% 98%;
|
|
147
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
148
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
149
|
+
--secondary-foreground: 210 40% 98%;
|
|
150
|
+
--muted: 217.2 32.6% 17.5%;
|
|
151
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
152
|
+
--accent: 217.2 32.6% 17.5%;
|
|
153
|
+
--accent-foreground: 210 40% 98%;
|
|
154
|
+
--destructive: 0 62.8% 30.6%;
|
|
155
|
+
--destructive-foreground: 210 40% 98%;
|
|
156
|
+
--border: 217.2 32.6% 17.5%;
|
|
157
|
+
--input: 217.2 32.6% 17.5%;
|
|
158
|
+
--ring: 212.7 26.8% 83.9%;
|
|
159
|
+
}
|
|
136
160
|
}
|
|
137
161
|
```
|
|
138
162
|
|
|
139
163
|
## Components
|
|
140
164
|
|
|
141
|
-
###
|
|
165
|
+
### Breadcrumbs
|
|
142
166
|
|
|
143
|
-
A
|
|
167
|
+
A navigation component that helps users understand their current location within a website's hierarchy.
|
|
144
168
|
|
|
145
|
-
####
|
|
169
|
+
#### Props
|
|
146
170
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
|
|
171
|
+
| Prop | Type | Default | Description
|
|
172
|
+
|-----|-----|-----|-----
|
|
173
|
+
| segments | `Array<{ label: string; href?: string }>` | Required | Array of breadcrumb segments
|
|
174
|
+
| separator | `React.ReactNode` | `<ChevronRight className="h-4 w-4" />` | Custom separator between breadcrumb items
|
|
175
|
+
| className | `string` | - | Additional CSS classes
|
|
176
|
+
| onNavigate | `(href: string) => void` | - | Callback function when a breadcrumb is clicked
|
|
153
177
|
|
|
154
|
-
#### Props
|
|
155
178
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
179
|
+
#### Example Usage
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
import { Breadcrumbs } from '@dsbtek/component-library';
|
|
183
|
+
|
|
184
|
+
function BreadcrumbsExample() {
|
|
185
|
+
return (
|
|
186
|
+
<Breadcrumbs
|
|
187
|
+
segments={[
|
|
188
|
+
{ label: 'Home', href: '/' },
|
|
189
|
+
{ label: 'Products', href: '/products' },
|
|
190
|
+
{ label: 'Electronics', href: '/products/electronics' },
|
|
191
|
+
{ label: 'Smartphones' },
|
|
192
|
+
]}
|
|
193
|
+
onNavigate={(href) => console.log(`Navigating to: ${href}`)}
|
|
194
|
+
/>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
164
198
|
|
|
165
|
-
|
|
199
|
+
### ColorPicker
|
|
200
|
+
|
|
201
|
+
A comprehensive color selection component with RGB, HSL support, color schemes, and history.
|
|
202
|
+
|
|
203
|
+
#### Props
|
|
204
|
+
|
|
205
|
+
| Prop | Type | Default | Description
|
|
206
|
+
|-----|-----|-----|-----
|
|
207
|
+
| color | `string` | `#000000` | Current color value in hex format
|
|
208
|
+
| onChange | `(value: string) => void` | - | Callback function when color changes
|
|
209
|
+
| className | `string` | - | Additional CSS classes
|
|
166
210
|
|
|
167
|
-
- `↑/↓`: Navigate through items
|
|
168
|
-
- `Enter`: Select highlighted item
|
|
169
|
-
- `Esc`: Close dropdown
|
|
170
|
-
- `Backspace`: Clear selection (when input is empty)
|
|
171
|
-
- `Type`: Search items (when searchable is true)
|
|
172
211
|
|
|
173
212
|
#### Example Usage
|
|
174
213
|
|
|
175
214
|
```tsx
|
|
176
|
-
import {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
onSelect={(value) => console.log('Selected:', value)}
|
|
189
|
-
/>
|
|
190
|
-
);
|
|
215
|
+
import { ColorPicker } from '@dsbtek/component-library';
|
|
216
|
+
import { useState } from 'react';
|
|
217
|
+
|
|
218
|
+
function ColorPickerExample() {
|
|
219
|
+
const [color, setColor] = useState('#3B82F6');
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<ColorPicker
|
|
223
|
+
color={color}
|
|
224
|
+
onChange={setColor}
|
|
225
|
+
/>
|
|
226
|
+
);
|
|
191
227
|
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### DataTable
|
|
231
|
+
|
|
232
|
+
A feature-rich table component with sorting, filtering, pagination, and more.
|
|
233
|
+
|
|
234
|
+
#### Props
|
|
192
235
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
236
|
+
| Prop | Type | Default | Description
|
|
237
|
+
|-----|-----|-----|-----
|
|
238
|
+
| data | `T[]` | Required | Array of data items
|
|
239
|
+
| columns | `ColumnDef<T>[]` | Required | Array of column definitions
|
|
240
|
+
| meta | `{ current_page: number; last_page: number; per_page: number; total: number }` | - | Pagination metadata
|
|
241
|
+
| loading | `boolean` | `false` | Loading state of the table
|
|
242
|
+
| onPageChange | `(page: number) => void` | - | Callback when page changes
|
|
243
|
+
| onPerPageChange | `(perPage: number) => void` | - | Callback when items per page changes
|
|
244
|
+
| onSort | `(column: string, direction: 'asc' | 'desc' | null) => void` | - | Callback when sorting changes
|
|
245
|
+
| onSearch | `(value: string) => void` | - | Callback when search value changes
|
|
246
|
+
| onFilter | `(filters: AdvancedFilter[]) => void` | - | Callback when filters change
|
|
247
|
+
| pageSizeOptions | `number[]` | `[10,20,50]` | Available options for items per page
|
|
248
|
+
| renderItemActions | `(row: T) => React.ReactNode` | - | Function to render action buttons for each row
|
|
249
|
+
| features | `Partial<DataTableFeatures>` | - | Object to enable/disable various table features
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
#### Example Usage
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
import { DataTable } from '@dsbtek/component-library';
|
|
256
|
+
|
|
257
|
+
function DataTableExample() {
|
|
258
|
+
const columns = [
|
|
259
|
+
{ accessorKey: 'name', header: 'Name' },
|
|
260
|
+
{ accessorKey: 'email', header: 'Email' },
|
|
261
|
+
{ accessorKey: 'role', header: 'Role' },
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
const data = [
|
|
265
|
+
{ id: '1', name: 'John Doe', email: 'john@example.com', role: 'Admin' },
|
|
266
|
+
{ id: '2', name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
|
|
267
|
+
// ... more data
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<DataTable
|
|
272
|
+
data={data}
|
|
273
|
+
columns={columns}
|
|
274
|
+
features={{
|
|
275
|
+
sorting: true,
|
|
276
|
+
pagination: true,
|
|
277
|
+
search: true,
|
|
278
|
+
}}
|
|
279
|
+
onPageChange={(page) => console.log(`Page changed to ${page}`)}
|
|
280
|
+
onSort={(column, direction) => console.log(`Sorting ${column} ${direction}`)}
|
|
281
|
+
/>
|
|
282
|
+
);
|
|
207
283
|
}
|
|
208
284
|
```
|
|
209
285
|
|
|
210
|
-
###
|
|
286
|
+
### DateTimePicker
|
|
287
|
+
|
|
288
|
+
A versatile date and time selection component with support for ranges, time-only, and date-only modes.
|
|
211
289
|
|
|
212
|
-
|
|
290
|
+
#### Props
|
|
213
291
|
|
|
214
|
-
|
|
292
|
+
| Prop | Type | Default | Description
|
|
293
|
+
|-----|-----|-----|-----
|
|
294
|
+
| date | `DateValue | DateRange | undefined | null` | - | Selected date(s)
|
|
295
|
+
| setDate | `(date: DateValue | DateRange | undefined) => void` | Required | Callback function when date changes
|
|
296
|
+
| isRange | `boolean` | `false` | Enable date range selection
|
|
297
|
+
| includeTime | `boolean` | `true` | Include time selection
|
|
298
|
+
| dateOnly | `boolean` | `false` | Show date picker only
|
|
299
|
+
| timeOnly | `boolean` | `false` | Show time picker only
|
|
300
|
+
| minDate | `Date` | - | Minimum selectable date
|
|
301
|
+
| maxDate | `Date` | - | Maximum selectable date
|
|
302
|
+
| disabledDates | `Date[]` | - | Array of disabled dates
|
|
303
|
+
| clearable | `boolean` | `true` | Allow clearing the selection
|
|
304
|
+
| disabled | `boolean` | `false` | Disable the input
|
|
215
305
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
306
|
+
|
|
307
|
+
#### Example Usage
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
import { DateTimePicker } from '@dsbtek/component-library';
|
|
311
|
+
import { useState } from 'react';
|
|
312
|
+
|
|
313
|
+
function DateTimePickerExample() {
|
|
314
|
+
const [date, setDate] = useState<Date | undefined>(new Date());
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<DateTimePicker
|
|
318
|
+
date={date}
|
|
319
|
+
setDate={setDate}
|
|
320
|
+
includeTime={true}
|
|
321
|
+
/>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### FileInput
|
|
327
|
+
|
|
328
|
+
A file upload component with drag and drop support, previews, and progress indication.
|
|
224
329
|
|
|
225
330
|
#### Props
|
|
226
331
|
|
|
227
|
-
| Prop
|
|
228
|
-
|
|
229
|
-
|
|
|
230
|
-
|
|
|
231
|
-
|
|
|
232
|
-
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
|
237
|
-
|
|
|
238
|
-
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
332
|
+
| Prop | Type | Default | Description
|
|
333
|
+
|-----|-----|-----|-----
|
|
334
|
+
| value | `FileWithPreview[]` | - | Array of selected files
|
|
335
|
+
| onChange | `(files: FileWithPreview[]) => void` | Required | Callback when files change
|
|
336
|
+
| multiple | `boolean` | `false` | Allow multiple file selection
|
|
337
|
+
| accept | `Record<string, string[]>` | `{ "image/*": [".png", ".jpg", ".jpeg", ".gif"], "application/pdf": [".pdf"] }` | Accepted file types
|
|
338
|
+
| maxSize | `number` | 2MB | Maximum file size in bytes
|
|
339
|
+
| maxFiles | `number` | 5 | Maximum number of files
|
|
340
|
+
| disabled | `boolean` | `false` | Disable the input
|
|
341
|
+
| loading | `boolean` | `false` | Show loading state
|
|
342
|
+
| progress | `number | number[]` | - | Upload progress percentage
|
|
343
|
+
| onRemove | `(file: FileWithPreview) => void` | - | Callback when a file is removed
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
#### Example Usage
|
|
347
|
+
|
|
348
|
+
```tsx
|
|
349
|
+
import { FileInput } from '@dsbtek/component-library';
|
|
350
|
+
import { useState } from 'react';
|
|
351
|
+
|
|
352
|
+
function FileInputExample() {
|
|
353
|
+
const [files, setFiles] = useState([]);
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
<FileInput
|
|
357
|
+
value={files}
|
|
358
|
+
onChange={setFiles}
|
|
359
|
+
multiple={true}
|
|
360
|
+
maxSize={5 * 1024 * 1024} // 5MB
|
|
361
|
+
/>
|
|
362
|
+
);
|
|
253
363
|
}
|
|
254
364
|
```
|
|
255
365
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
366
|
+
### MultiSelect
|
|
367
|
+
|
|
368
|
+
A flexible select component that supports single or multiple selection with grouping.
|
|
369
|
+
|
|
370
|
+
#### Props
|
|
371
|
+
|
|
372
|
+
| Prop | Type | Default | Description
|
|
373
|
+
|-----|-----|-----|-----
|
|
374
|
+
| options | `OptionType[]` | Required | Array of selectable options
|
|
375
|
+
| selected | `string[]` | Required | Array of selected option values
|
|
376
|
+
| onChange | `(value: string[]) => void` | Required | Callback when selection changes
|
|
377
|
+
| placeholder | `string` | `"Select..."` | Placeholder text
|
|
378
|
+
| className | `string` | - | Additional CSS classes
|
|
379
|
+
| multiple | `boolean` | `false` | Allow multiple selection
|
|
380
|
+
| onLoadMore | `() => void` | - | Callback for infinite loading
|
|
381
|
+
| hasMore | `boolean` | `false` | Indicates if more options can be loaded
|
|
382
|
+
| isDialog | `boolean` | `false` | Use dialog instead of drawer on mobile
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
#### Example Usage
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
import { MultiSelect } from '@dsbtek/component-library';
|
|
389
|
+
import { useState } from 'react';
|
|
390
|
+
|
|
391
|
+
function MultiSelectExample() {
|
|
392
|
+
const [selected, setSelected] = useState(['react']);
|
|
393
|
+
|
|
394
|
+
const options = [
|
|
395
|
+
{ label: 'React', value: 'react', group: 'Frontend' },
|
|
396
|
+
{ label: 'Vue', value: 'vue', group: 'Frontend' },
|
|
397
|
+
{ label: 'Angular', value: 'angular', group: 'Frontend' },
|
|
398
|
+
{ label: 'Node.js', value: 'nodejs', group: 'Backend' },
|
|
399
|
+
{ label: 'Express', value: 'express', group: 'Backend' },
|
|
400
|
+
];
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
<MultiSelect
|
|
404
|
+
options={options}
|
|
405
|
+
selected={selected}
|
|
406
|
+
onChange={setSelected}
|
|
407
|
+
placeholder="Select technologies..."
|
|
408
|
+
multiple={true}
|
|
409
|
+
/>
|
|
410
|
+
);
|
|
267
411
|
}
|
|
268
412
|
```
|
|
269
413
|
|
|
414
|
+
### MultiStepper
|
|
415
|
+
|
|
416
|
+
A comprehensive multi-step form component with navigation, validation, and customizable steps.
|
|
417
|
+
|
|
418
|
+
#### Props
|
|
419
|
+
|
|
420
|
+
| Prop | Type | Default | Description
|
|
421
|
+
|-----|-----|-----|-----
|
|
422
|
+
| context | `React.Context<UseMultiStepFormTypeOptions<T>>` | Required | Context created with buildMultiStepForm
|
|
423
|
+
| previousLabel | `string` | `"Previous"` | Label for the previous button
|
|
424
|
+
| nextLabel | `string` | `"Next"` | Label for the next button
|
|
425
|
+
| endStepLabel | `string` | `"Submit"` | Label for the submit button
|
|
426
|
+
| showNavbar | `boolean` | `true` | Show the step navigation bar
|
|
427
|
+
| showButtons | `boolean` | `true` | Show the navigation buttons
|
|
428
|
+
| isLoading | `boolean` | `false` | Loading state for the submit button
|
|
429
|
+
| className | `string` | `""` | Additional CSS classes
|
|
430
|
+
| debug | `boolean` | `false` | Show debug information
|
|
431
|
+
|
|
432
|
+
#### Utility Functions
|
|
433
|
+
|
|
434
|
+
| Function | Description
|
|
435
|
+
|-----|-----
|
|
436
|
+
| buildMultiStepForm | Creates a context and provider for a multi-step form
|
|
437
|
+
| useMultiStepForm | Hook for accessing multi-step form functionality
|
|
438
|
+
|
|
270
439
|
#### Example Usage
|
|
271
440
|
|
|
272
441
|
```tsx
|
|
273
|
-
import {
|
|
442
|
+
import {
|
|
443
|
+
MultiStepper,
|
|
444
|
+
buildMultiStepForm,
|
|
445
|
+
useMultiStepForm,
|
|
446
|
+
Form
|
|
447
|
+
} from '@dsbtek/component-library';
|
|
448
|
+
import { z } from 'zod';
|
|
449
|
+
import { useForm, FormProvider, useFormContext } from 'react-hook-form';
|
|
450
|
+
import {
|
|
451
|
+
FormField,
|
|
452
|
+
FormItem,
|
|
453
|
+
FormLabel,
|
|
454
|
+
FormControl,
|
|
455
|
+
FormMessage
|
|
456
|
+
} from '@/components/ui/form';
|
|
457
|
+
import { Input } from '@/components/ui/input';
|
|
458
|
+
import { PasswordInput } from '@dsbtek/component-library';
|
|
459
|
+
import { MultiSelect } from '@dsbtek/component-library';
|
|
460
|
+
import { FileInput } from '@dsbtek/component-library';
|
|
461
|
+
|
|
462
|
+
// Define your form schema
|
|
463
|
+
const signupSchema = z.object({
|
|
464
|
+
name: z.string().min(2, "Name must be at least 2 characters"),
|
|
465
|
+
email: z.string().email("Please enter a valid email"),
|
|
466
|
+
phone: z.string().min(10, "Please enter a valid phone number"),
|
|
467
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
468
|
+
password_confirmation: z.string(),
|
|
469
|
+
roles: z.array(z.string()),
|
|
470
|
+
permissions: z.array(z.string()),
|
|
471
|
+
image: z.any().optional(),
|
|
472
|
+
}).refine((data) => data.password === data.password_confirmation, {
|
|
473
|
+
message: "Passwords do not match",
|
|
474
|
+
path: ["password_confirmation"],
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// Step 1: Basic Information
|
|
478
|
+
function Step1() {
|
|
479
|
+
const { control } = useFormContext();
|
|
480
|
+
|
|
481
|
+
return (
|
|
482
|
+
<>
|
|
483
|
+
<FormField
|
|
484
|
+
control={control}
|
|
485
|
+
name="name"
|
|
486
|
+
render={({ field }) => (
|
|
487
|
+
<FormItem>
|
|
488
|
+
<FormLabel>Name</FormLabel>
|
|
489
|
+
<FormControl>
|
|
490
|
+
<Input placeholder="Enter your name" {...field} />
|
|
491
|
+
</FormControl>
|
|
492
|
+
<FormMessage />
|
|
493
|
+
</FormItem>
|
|
494
|
+
)}
|
|
495
|
+
/>
|
|
496
|
+
<FormField
|
|
497
|
+
control={control}
|
|
498
|
+
name="email"
|
|
499
|
+
render={({ field }) => (
|
|
500
|
+
<FormItem>
|
|
501
|
+
<FormLabel>Email</FormLabel>
|
|
502
|
+
<FormControl>
|
|
503
|
+
<Input placeholder="Enter your email" {...field} />
|
|
504
|
+
</FormControl>
|
|
505
|
+
<FormMessage />
|
|
506
|
+
</FormItem>
|
|
507
|
+
)}
|
|
508
|
+
/>
|
|
509
|
+
<FormField
|
|
510
|
+
control={control}
|
|
511
|
+
name="phone"
|
|
512
|
+
render={({ field }) => (
|
|
513
|
+
<FormItem>
|
|
514
|
+
<FormLabel>Phone</FormLabel>
|
|
515
|
+
<FormControl>
|
|
516
|
+
<Input placeholder="Enter your phone number" {...field} />
|
|
517
|
+
</FormControl>
|
|
518
|
+
<FormMessage />
|
|
519
|
+
</FormItem>
|
|
520
|
+
)}
|
|
521
|
+
/>
|
|
522
|
+
</>
|
|
523
|
+
);
|
|
524
|
+
}
|
|
274
525
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
526
|
+
// Step 2: Security
|
|
527
|
+
function Step2() {
|
|
528
|
+
const { control } = useFormContext();
|
|
529
|
+
|
|
530
|
+
return (
|
|
531
|
+
<>
|
|
532
|
+
<FormField
|
|
533
|
+
control={control}
|
|
534
|
+
name="password"
|
|
535
|
+
render={({ field }) => (
|
|
536
|
+
<FormItem>
|
|
537
|
+
<FormLabel>Password</FormLabel>
|
|
538
|
+
<FormControl>
|
|
539
|
+
<PasswordInput placeholder="Enter your password" {...field} />
|
|
540
|
+
</FormControl>
|
|
541
|
+
<FormMessage />
|
|
542
|
+
</FormItem>
|
|
543
|
+
)}
|
|
544
|
+
/>
|
|
545
|
+
<FormField
|
|
546
|
+
control={control}
|
|
547
|
+
name="password_confirmation"
|
|
548
|
+
render={({ field }) => (
|
|
549
|
+
<FormItem>
|
|
550
|
+
<FormLabel>Confirm Password</FormLabel>
|
|
551
|
+
<FormControl>
|
|
552
|
+
<PasswordInput placeholder="Confirm your password" {...field} />
|
|
553
|
+
</FormControl>
|
|
554
|
+
<FormMessage />
|
|
555
|
+
</FormItem>
|
|
556
|
+
)}
|
|
557
|
+
/>
|
|
558
|
+
</>
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Step 3: Roles & Permissions
|
|
563
|
+
function Step3() {
|
|
564
|
+
const { control } = useFormContext();
|
|
565
|
+
|
|
566
|
+
const roleOptions = [
|
|
567
|
+
{ label: 'Admin', value: 'admin' },
|
|
568
|
+
{ label: 'User', value: 'user' },
|
|
569
|
+
{ label: 'Editor', value: 'editor' },
|
|
570
|
+
];
|
|
571
|
+
|
|
572
|
+
const permissionOptions = [
|
|
573
|
+
{ label: 'Create', value: 'create', group: 'Content' },
|
|
574
|
+
{ label: 'Edit', value: 'edit', group: 'Content' },
|
|
575
|
+
{ label: 'Delete', value: 'delete', group: 'Content' },
|
|
576
|
+
{ label: 'View Users', value: 'view_users', group: 'Users' },
|
|
577
|
+
{ label: 'Manage Users', value: 'manage_users', group: 'Users' },
|
|
578
|
+
];
|
|
579
|
+
|
|
580
|
+
return (
|
|
581
|
+
<>
|
|
582
|
+
<FormField
|
|
583
|
+
control={control}
|
|
584
|
+
name="roles"
|
|
585
|
+
render={({ field }) => (
|
|
586
|
+
<FormItem>
|
|
587
|
+
<FormLabel>Roles</FormLabel>
|
|
588
|
+
<FormControl>
|
|
589
|
+
<MultiSelect
|
|
590
|
+
options={roleOptions}
|
|
591
|
+
selected={field.value || []}
|
|
592
|
+
onChange={field.onChange}
|
|
593
|
+
placeholder="Select roles"
|
|
594
|
+
multiple={true}
|
|
595
|
+
/>
|
|
596
|
+
</FormControl>
|
|
597
|
+
<FormMessage />
|
|
598
|
+
</FormItem>
|
|
599
|
+
)}
|
|
600
|
+
/>
|
|
601
|
+
<FormField
|
|
602
|
+
control={control}
|
|
603
|
+
name="permissions"
|
|
604
|
+
render={({ field }) => (
|
|
605
|
+
<FormItem>
|
|
606
|
+
<FormLabel>Permissions</FormLabel>
|
|
607
|
+
<FormControl>
|
|
608
|
+
<MultiSelect
|
|
609
|
+
options={permissionOptions}
|
|
610
|
+
selected={field.value || []}
|
|
611
|
+
onChange={field.onChange}
|
|
612
|
+
placeholder="Select permissions"
|
|
613
|
+
multiple={true}
|
|
614
|
+
/>
|
|
615
|
+
</FormControl>
|
|
616
|
+
<FormMessage />
|
|
617
|
+
</FormItem>
|
|
618
|
+
)}
|
|
619
|
+
/>
|
|
620
|
+
</>
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Step 4: Profile Picture
|
|
625
|
+
function Step4() {
|
|
626
|
+
const { control } = useFormContext();
|
|
627
|
+
|
|
628
|
+
return (
|
|
629
|
+
<FormField
|
|
630
|
+
control={control}
|
|
631
|
+
name="image"
|
|
632
|
+
render={({ field }) => (
|
|
633
|
+
<FormItem>
|
|
634
|
+
<FormLabel>Profile Picture</FormLabel>
|
|
635
|
+
<FormControl>
|
|
636
|
+
<FileInput
|
|
637
|
+
value={field.value || []}
|
|
638
|
+
onChange={field.onChange}
|
|
639
|
+
accept={{ "image/*": [".png", ".jpg", ".jpeg"] }}
|
|
640
|
+
maxSize={2 * 1024 * 1024} // 2MB
|
|
641
|
+
/>
|
|
642
|
+
</FormControl>
|
|
643
|
+
<FormMessage />
|
|
644
|
+
</FormItem>
|
|
645
|
+
)}
|
|
646
|
+
/>
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Define your form steps
|
|
651
|
+
const forms: Form<z.infer<typeof signupSchema>>[] = [
|
|
652
|
+
{ id: 1, label: "Basic Information", form: Step1, fields: ["name", "email", "phone"] },
|
|
653
|
+
{ id: 2, label: "Security", form: Step2, fields: ["password", "password_confirmation"] },
|
|
654
|
+
{ id: 3, label: "Roles & Permissions", form: Step3, fields: ["roles", "permissions"] },
|
|
655
|
+
{ id: 4, label: "Profile Picture", form: Step4, fields: ["image"] },
|
|
656
|
+
];
|
|
657
|
+
|
|
658
|
+
// Initial form data
|
|
659
|
+
const initialFormData = {
|
|
660
|
+
name: "",
|
|
661
|
+
email: "",
|
|
662
|
+
phone: "",
|
|
663
|
+
password: "",
|
|
664
|
+
password_confirmation: "",
|
|
665
|
+
roles: [],
|
|
666
|
+
permissions: [],
|
|
667
|
+
image: undefined,
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// Create form context and provider
|
|
671
|
+
const { FormContext, FormProvider } = buildMultiStepForm(
|
|
288
672
|
{
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
673
|
+
schema: signupSchema,
|
|
674
|
+
currentStep: 0,
|
|
675
|
+
setCurrentStep: () => {},
|
|
676
|
+
forms,
|
|
677
|
+
saveFormData: async (data) => {
|
|
678
|
+
// Submit your form data
|
|
679
|
+
console.log("Form submitted:", data);
|
|
680
|
+
return { success: true };
|
|
681
|
+
},
|
|
294
682
|
},
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
683
|
+
signupSchema,
|
|
684
|
+
initialFormData
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
// Use the multi-step form
|
|
688
|
+
function SignupForm() {
|
|
689
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
690
|
+
|
|
691
|
+
const handleSubmit = async (data) => {
|
|
692
|
+
setIsLoading(true);
|
|
693
|
+
try {
|
|
694
|
+
// Submit your form data
|
|
695
|
+
await saveUserData(data);
|
|
696
|
+
toast.success("Account created successfully!");
|
|
697
|
+
} catch (error) {
|
|
698
|
+
toast.error("Failed to create account");
|
|
699
|
+
} finally {
|
|
700
|
+
setIsLoading(false);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
return (
|
|
705
|
+
<FormProvider>
|
|
706
|
+
<div className="max-w-md mx-auto p-6 bg-card rounded-lg shadow-md">
|
|
707
|
+
<h1 className="text-2xl font-bold mb-6 text-center">Create Account</h1>
|
|
708
|
+
<MultiStepper
|
|
709
|
+
context={FormContext}
|
|
710
|
+
previousLabel="Back"
|
|
711
|
+
nextLabel="Continue"
|
|
712
|
+
endStepLabel="Sign Up"
|
|
713
|
+
isLoading={isLoading}
|
|
714
|
+
className="space-y-6"
|
|
715
|
+
/>
|
|
716
|
+
</div>
|
|
717
|
+
</FormProvider>
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### PasswordInput
|
|
723
|
+
|
|
724
|
+
A password input component with show/hide functionality.
|
|
725
|
+
|
|
726
|
+
#### Props
|
|
727
|
+
|
|
728
|
+
| Prop | Type | Default | Description
|
|
729
|
+
|-----|-----|-----|-----
|
|
730
|
+
| className | `string` | - | Additional CSS classes
|
|
731
|
+
| showPasswordLabel | `string` | `"Show password"` | Screen reader label for show password button
|
|
732
|
+
| hidePasswordLabel | `string` | `"Hide password"` | Screen reader label for hide password button
|
|
733
|
+
| ...props | `React.InputHTMLAttributes<HTMLInputElement>` | - | All standard input props
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
#### Example Usage
|
|
737
|
+
|
|
738
|
+
```tsx
|
|
739
|
+
import { PasswordInput } from '@dsbtek/component-library';
|
|
740
|
+
import { useState } from 'react';
|
|
741
|
+
|
|
742
|
+
function PasswordInputExample() {
|
|
743
|
+
const [password, setPassword] = useState('');
|
|
744
|
+
|
|
745
|
+
return (
|
|
746
|
+
<PasswordInput
|
|
747
|
+
value={password}
|
|
748
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
749
|
+
placeholder="Enter your password"
|
|
750
|
+
/>
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### PhoneInput
|
|
756
|
+
|
|
757
|
+
An international phone number input with country selection and validation.
|
|
758
|
+
|
|
759
|
+
#### Props
|
|
760
|
+
|
|
761
|
+
| Prop | Type | Default | Description
|
|
762
|
+
|-----|-----|-----|-----
|
|
763
|
+
| value | `string` | - | Phone number value
|
|
764
|
+
| defaultCountry | `CountryCode` | `"US"` | Default country code
|
|
765
|
+
| ...props | `React.ComponentPropsWithoutRef<'input'>` | - | All standard input props
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
#### Example Usage
|
|
769
|
+
|
|
770
|
+
```tsx
|
|
771
|
+
import { PhoneInput, getPhoneData } from '@dsbtek/component-library';
|
|
772
|
+
import { useState, useEffect } from 'react';
|
|
773
|
+
|
|
774
|
+
function PhoneInputExample() {
|
|
775
|
+
const [phone, setPhone] = useState('+1');
|
|
776
|
+
const [phoneData, setPhoneData] = useState(getPhoneData(phone));
|
|
777
|
+
|
|
778
|
+
useEffect(() => {
|
|
779
|
+
setPhoneData(getPhoneData(phone));
|
|
780
|
+
}, [phone]);
|
|
781
|
+
|
|
782
|
+
return (
|
|
783
|
+
<PhoneInput
|
|
784
|
+
value={phone}
|
|
785
|
+
onChange={(e) => setPhone(e.target.value)}
|
|
786
|
+
defaultCountry="US"
|
|
787
|
+
/>
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
### ResponsiveAlertDialog
|
|
793
|
+
|
|
794
|
+
An alert dialog component that adapts to desktop (modal) and mobile (drawer) views.
|
|
795
|
+
|
|
796
|
+
#### Props
|
|
797
|
+
|
|
798
|
+
| Prop | Type | Default | Description
|
|
799
|
+
|-----|-----|-----|-----
|
|
800
|
+
| trigger | `React.ReactNode` | Required | Element that triggers the dialog
|
|
801
|
+
| title | `string` | Required | Dialog title
|
|
802
|
+
| description | `string` | Required | Dialog description
|
|
803
|
+
| cancelText | `string` | `"Cancel"` | Text for the cancel button
|
|
804
|
+
| confirmText | `string` | `"Continue"` | Text for the confirm button
|
|
805
|
+
| onConfirm | `() => void` | Required | Callback when confirm button is clicked
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
#### Example Usage
|
|
809
|
+
|
|
810
|
+
```tsx
|
|
811
|
+
import { ResponsiveAlertDialog } from '@dsbtek/component-library';
|
|
812
|
+
import { Button } from '@dsbtek/component-library';
|
|
813
|
+
|
|
814
|
+
function AlertDialogExample() {
|
|
815
|
+
const handleDelete = () => {
|
|
816
|
+
console.log('Item deleted');
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
return (
|
|
820
|
+
<ResponsiveAlertDialog
|
|
821
|
+
trigger={<Button variant="destructive">Delete Item</Button>}
|
|
822
|
+
title="Are you sure?"
|
|
823
|
+
description="This action cannot be undone. This will permanently delete the item."
|
|
824
|
+
cancelText="Cancel"
|
|
825
|
+
confirmText="Delete"
|
|
826
|
+
onConfirm={handleDelete}
|
|
827
|
+
/>
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
### ResponsiveDialog
|
|
833
|
+
|
|
834
|
+
A dialog component that adapts to desktop (modal) and mobile (drawer) views.
|
|
835
|
+
|
|
836
|
+
#### Props
|
|
837
|
+
|
|
838
|
+
| Prop | Type | Default | Description
|
|
839
|
+
|-----|-----|-----|-----
|
|
840
|
+
| trigger | `React.ReactNode` | Required | Element that triggers the dialog
|
|
841
|
+
| title | `string` | Required | Dialog title
|
|
842
|
+
| description | `string` | Required | Dialog description
|
|
843
|
+
| children | `React.ReactNode` | Required | Dialog content
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
#### Example Usage
|
|
847
|
+
|
|
848
|
+
```tsx
|
|
849
|
+
import { ResponsiveDialog } from '@dsbtek/component-library';
|
|
850
|
+
import { Button } from '@dsbtek/component-library';
|
|
851
|
+
|
|
852
|
+
function DialogExample() {
|
|
853
|
+
return (
|
|
854
|
+
<ResponsiveDialog
|
|
855
|
+
trigger={<Button>Open Dialog</Button>}
|
|
856
|
+
title="Edit Profile"
|
|
857
|
+
description="Make changes to your profile here."
|
|
858
|
+
>
|
|
859
|
+
<form className="space-y-4 pt-4">
|
|
860
|
+
<div className="space-y-2">
|
|
861
|
+
<label htmlFor="name">Name</label>
|
|
862
|
+
<input id="name" className="w-full p-2 border rounded" />
|
|
863
|
+
</div>
|
|
864
|
+
<div className="space-y-2">
|
|
865
|
+
<label htmlFor="email">Email</label>
|
|
866
|
+
<input id="email" type="email" className="w-full p-2 border rounded" />
|
|
867
|
+
</div>
|
|
868
|
+
<div className="flex justify-end space-x-2">
|
|
869
|
+
<Button type="button" variant="outline">Cancel</Button>
|
|
870
|
+
<Button type="submit">Save</Button>
|
|
871
|
+
</div>
|
|
872
|
+
</form>
|
|
873
|
+
</ResponsiveDialog>
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### TagInput
|
|
879
|
+
|
|
880
|
+
A component for adding and managing tags with suggestions and validation.
|
|
881
|
+
|
|
882
|
+
#### Props
|
|
883
|
+
|
|
884
|
+
| Prop | Type | Default | Description
|
|
885
|
+
|-----|-----|-----|-----
|
|
886
|
+
| placeholder | `string` | `"Add tag..."` | Placeholder text
|
|
887
|
+
| tags | `string[]` | Required | Array of current tags
|
|
888
|
+
| setTags | `(tags: string[]) => void` | Required | Callback when tags change
|
|
889
|
+
| suggestions | `string[]` | `[]` | Array of tag suggestions
|
|
890
|
+
| maxTags | `number` | - | Maximum number of tags allowed
|
|
891
|
+
| disabled | `boolean` | `false` | Disable the input
|
|
892
|
+
| error | `string` | - | Error message to display
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
#### Example Usage
|
|
896
|
+
|
|
897
|
+
```tsx
|
|
898
|
+
import { TagInput } from '@dsbtek/component-library';
|
|
899
|
+
import { useState } from 'react';
|
|
900
|
+
|
|
901
|
+
function TagInputExample() {
|
|
902
|
+
const [tags, setTags] = useState(['react', 'typescript']);
|
|
903
|
+
|
|
904
|
+
const suggestions = [
|
|
905
|
+
'react', 'vue', 'angular', 'svelte', 'javascript',
|
|
906
|
+
'typescript', 'node', 'express', 'mongodb'
|
|
907
|
+
];
|
|
908
|
+
|
|
909
|
+
return (
|
|
910
|
+
<TagInput
|
|
911
|
+
tags={tags}
|
|
912
|
+
setTags={setTags}
|
|
913
|
+
suggestions={suggestions}
|
|
914
|
+
placeholder="Add technologies..."
|
|
915
|
+
/>
|
|
916
|
+
);
|
|
318
917
|
}
|
|
319
918
|
```
|
|
320
919
|
|
|
@@ -325,24 +924,30 @@ All components use Tailwind CSS for styling and can be customized using Tailwind
|
|
|
325
924
|
### Custom Styling Example
|
|
326
925
|
|
|
327
926
|
```tsx
|
|
328
|
-
<
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
927
|
+
<Breadcrumbs
|
|
928
|
+
className="text-blue-600 dark:text-blue-400"
|
|
929
|
+
segments={[
|
|
930
|
+
{ label: 'Home', href: '/' },
|
|
931
|
+
{ label: 'Products', href: '/products' },
|
|
932
|
+
{ label: 'Current Page' },
|
|
933
|
+
]}
|
|
333
934
|
/>
|
|
334
935
|
|
|
335
|
-
<
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
936
|
+
<ColorPicker
|
|
937
|
+
className="w-[300px] rounded-lg"
|
|
938
|
+
color="#3B82F6"
|
|
939
|
+
onChange={setColor}
|
|
339
940
|
/>
|
|
340
941
|
```
|
|
341
942
|
|
|
943
|
+
## TypeScript Support
|
|
944
|
+
|
|
945
|
+
All components include full TypeScript support out of the box. Type definitions are automatically available when you import components.
|
|
946
|
+
|
|
342
947
|
## Contributing
|
|
343
948
|
|
|
344
|
-
[
|
|
949
|
+
Please read our [Contributing Guide](CONTRIBUTING.md) before submitting a Pull Request.
|
|
345
950
|
|
|
346
951
|
## License
|
|
347
952
|
|
|
348
|
-
MIT
|
|
953
|
+
MIT © [Smartflowtech Team](https://smartflowtech.com)
|