@makolabs/ripple 1.2.2 → 1.2.4
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 +77 -0
- package/dist/adapters/ai/OpenAIAdapter.js +16 -11
- package/dist/adapters/ai/types.d.ts +3 -3
- package/dist/adapters/storage/BaseAdapter.d.ts +1 -1
- package/dist/adapters/storage/BaseAdapter.js +1 -1
- package/dist/adapters/storage/S3Adapter.js +2 -2
- package/dist/ai/AIChatInterface.svelte +32 -34
- package/dist/ai/AIChatInterface.svelte.d.ts +0 -1
- package/dist/ai/AIChatInterfaceTestWrapper.svelte +26 -0
- package/dist/ai/AIChatInterfaceTestWrapper.svelte.d.ts +17 -0
- package/dist/ai/ChatInput.svelte +7 -15
- package/dist/ai/ChatInput.svelte.d.ts +0 -2
- package/dist/ai/CodeRenderer.svelte +25 -12
- package/dist/ai/ComposeDropdown.svelte +17 -14
- package/dist/ai/MermaidRenderer.svelte +21 -17
- package/dist/ai/MermaidRenderer.svelte.d.ts +0 -1
- package/dist/ai/MessageBox.svelte +10 -7
- package/dist/ai/ThinkingDisplay.svelte +67 -43
- package/dist/ai/ai-chat-interface.d.ts +22 -21
- package/dist/ai/ai-chat-interface.js +8 -7
- package/dist/ai/content-detector.js +2 -2
- package/dist/button/ButtonTestWrapper.svelte +10 -0
- package/dist/button/ButtonTestWrapper.svelte.d.ts +7 -0
- package/dist/charts/Chart.svelte +6 -1
- package/dist/config/ai.js +1 -0
- package/dist/drawer/DrawerTestWrapper.svelte +19 -0
- package/dist/drawer/DrawerTestWrapper.svelte.d.ts +9 -0
- package/dist/drawer/drawer.d.ts +19 -18
- package/dist/drawer/drawer.js +7 -6
- package/dist/elements/accordion/Accordion.svelte +1 -1
- package/dist/elements/accordion/Accordion.svelte.d.ts +1 -1
- package/dist/elements/accordion/AccordionTestWrapper.svelte +21 -0
- package/dist/elements/accordion/AccordionTestWrapper.svelte.d.ts +10 -0
- package/dist/elements/badge/Badge.svelte +5 -4
- package/dist/elements/badge/BadgeTestWrapper.svelte +14 -0
- package/dist/elements/badge/BadgeTestWrapper.svelte.d.ts +9 -0
- package/dist/elements/badge/badge.d.ts +40 -39
- package/dist/elements/badge/badge.js +14 -13
- package/dist/elements/dropdown/Dropdown.svelte +0 -1
- package/dist/elements/pagination/Pagination.svelte +458 -0
- package/dist/elements/pagination/Pagination.svelte.d.ts +57 -0
- package/dist/elements/progress/Progress.svelte +3 -3
- package/dist/elements/timeline/Timeline.svelte +1 -1
- package/dist/file-browser/FileBrowser.svelte +7 -10
- package/dist/filters/CompactFilters.svelte +3 -3
- package/dist/forms/Checkbox.svelte +0 -1
- package/dist/forms/CheckboxTestWrapper.svelte +8 -0
- package/dist/forms/CheckboxTestWrapper.svelte.d.ts +4 -0
- package/dist/forms/DateRange.svelte +186 -198
- package/dist/forms/Form.svelte +1 -0
- package/dist/forms/Input.svelte +14 -5
- package/dist/forms/InputTestWrapper.svelte +8 -0
- package/dist/forms/InputTestWrapper.svelte.d.ts +4 -0
- package/dist/forms/NumberInput.svelte +2 -2
- package/dist/forms/RadioInputs.svelte +1 -1
- package/dist/forms/RadioPill.svelte +1 -1
- package/dist/forms/Slider.svelte +2 -2
- package/dist/forms/Tags.svelte +3 -3
- package/dist/forms/ToggleTestWrapper.svelte +8 -0
- package/dist/forms/ToggleTestWrapper.svelte.d.ts +7 -0
- package/dist/forms/slider.js +1 -1
- package/dist/header/PageHeader.svelte +2 -1
- package/dist/header/breadcrumbs.d.ts +47 -33
- package/dist/header/breadcrumbs.js +12 -11
- package/dist/index.d.ts +8 -2
- package/dist/index.js +3 -0
- package/dist/layout/activity-list/ActivityList.svelte +9 -11
- package/dist/layout/card/CardTestWrapper.svelte +15 -0
- package/dist/layout/card/CardTestWrapper.svelte.d.ts +7 -0
- package/dist/layout/card/RankedCard.svelte +2 -3
- package/dist/layout/navbar/navbar.d.ts +19 -18
- package/dist/layout/navbar/navbar.js +7 -6
- package/dist/layout/sidebar/NavGroup.svelte +1 -0
- package/dist/layout/table/Cells.svelte +5 -5
- package/dist/layout/table/Table.svelte +477 -594
- package/dist/layout/table/table.d.ts +28 -24
- package/dist/layout/table/table.js +9 -8
- package/dist/modal/Modal.svelte +1 -1
- package/dist/modal/ModalTestWrapper.svelte +20 -0
- package/dist/modal/ModalTestWrapper.svelte.d.ts +8 -0
- package/dist/modal/modal.d.ts +1 -20
- package/dist/pipeline/Pipeline.svelte +29 -17
- package/dist/user-management/README.md +417 -0
- package/dist/user-management/UserManagement.svelte +184 -0
- package/dist/user-management/UserManagement.svelte.d.ts +4 -0
- package/dist/user-management/UserManagementTestWrapper.svelte +47 -0
- package/dist/user-management/UserManagementTestWrapper.svelte.d.ts +7 -0
- package/dist/user-management/UserModal.svelte +303 -0
- package/dist/user-management/UserModal.svelte.d.ts +4 -0
- package/dist/user-management/UserModalTestWrapper.svelte +22 -0
- package/dist/user-management/UserModalTestWrapper.svelte.d.ts +7 -0
- package/dist/user-management/UserTable.svelte +219 -0
- package/dist/user-management/UserTable.svelte.d.ts +4 -0
- package/dist/user-management/UserTableTestWrapper.svelte +41 -0
- package/dist/user-management/UserTableTestWrapper.svelte.d.ts +7 -0
- package/dist/user-management/UserViewModal.svelte +282 -0
- package/dist/user-management/UserViewModal.svelte.d.ts +4 -0
- package/dist/user-management/UserViewModalTestWrapper.svelte +22 -0
- package/dist/user-management/UserViewModalTestWrapper.svelte.d.ts +7 -0
- package/dist/user-management/index.d.ts +10 -0
- package/dist/user-management/index.js +11 -0
- package/dist/user-management/user-management.d.ts +99 -0
- package/dist/user-management/user-management.js +42 -0
- package/package.json +3 -1
- package/dist/types/markdown.d.ts +0 -14
- package/dist/types/variants.d.ts +0 -1
- package/dist/types/variants.js +0 -1
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# User Management Module
|
|
2
|
+
|
|
3
|
+
A comprehensive, self-contained user management system built with Svelte 5 and TypeScript. This module provides all the components needed to build a complete user management interface with CRUD operations, role management, and permissions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 📊 **User Table**: Fully-featured data table with sorting, pagination, and search
|
|
8
|
+
- ✏️ **User Modal**: Create and edit users with role and permission management
|
|
9
|
+
- 👁️ **User View Modal**: View detailed user information in a read-only modal
|
|
10
|
+
- 🎯 **User Management**: Complete orchestrator component that combines all features
|
|
11
|
+
- 🔐 **Role-Based Access**: Built-in support for roles and permissions
|
|
12
|
+
- 🎨 **Customizable**: All components accept custom classes and styling
|
|
13
|
+
- ♿ **Accessible**: Built with accessibility in mind
|
|
14
|
+
- 📱 **Responsive**: Works seamlessly on all screen sizes
|
|
15
|
+
- 🧪 **Well-Tested**: Comprehensive test suite included
|
|
16
|
+
- 📖 **Storybook Ready**: Full Storybook stories for all components
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
The user management module is included in `@makolabs/ripple`. Simply import the components you need:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { UserManagement, UserTable, UserModal, UserViewModal } from '@makolabs/ripple';
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Components
|
|
27
|
+
|
|
28
|
+
### UserManagement
|
|
29
|
+
|
|
30
|
+
The main orchestrator component that combines all user management functionality.
|
|
31
|
+
|
|
32
|
+
```svelte
|
|
33
|
+
<script lang="ts">
|
|
34
|
+
import { UserManagement } from '@makolabs/ripple';
|
|
35
|
+
import type { User } from '@makolabs/ripple';
|
|
36
|
+
|
|
37
|
+
let users = $state<User[]>([
|
|
38
|
+
/* your users */
|
|
39
|
+
]);
|
|
40
|
+
let totalUsers = $state(100);
|
|
41
|
+
let currentPage = $state(1);
|
|
42
|
+
let pageSize = $state(10);
|
|
43
|
+
|
|
44
|
+
const roles = [
|
|
45
|
+
{
|
|
46
|
+
value: 'admin',
|
|
47
|
+
label: 'Administrator',
|
|
48
|
+
description: 'Full access',
|
|
49
|
+
permissions: ['read', 'write', 'delete']
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
value: 'user',
|
|
53
|
+
label: 'User',
|
|
54
|
+
description: 'Basic access',
|
|
55
|
+
permissions: ['read']
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
async function handleCreateUser(userData: Partial<User>) {
|
|
60
|
+
// Your create logic
|
|
61
|
+
console.log('Creating user:', userData);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function handleUpdateUser(userId: string, userData: Partial<User>) {
|
|
65
|
+
// Your update logic
|
|
66
|
+
console.log('Updating user:', userId, userData);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function handleDeleteUser(userId: string) {
|
|
70
|
+
// Your delete logic
|
|
71
|
+
console.log('Deleting user:', userId);
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<UserManagement
|
|
76
|
+
{users}
|
|
77
|
+
{totalUsers}
|
|
78
|
+
{currentPage}
|
|
79
|
+
{pageSize}
|
|
80
|
+
{roles}
|
|
81
|
+
onPageChange={(page) => (currentPage = page)}
|
|
82
|
+
onPageSizeChange={(size) => (pageSize = size)}
|
|
83
|
+
onCreateUser={handleCreateUser}
|
|
84
|
+
onUpdateUser={handleUpdateUser}
|
|
85
|
+
onDeleteUser={handleDeleteUser}
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### UserTable
|
|
90
|
+
|
|
91
|
+
Display users in a sortable, paginated table.
|
|
92
|
+
|
|
93
|
+
```svelte
|
|
94
|
+
<script lang="ts">
|
|
95
|
+
import { UserTable } from '@makolabs/ripple';
|
|
96
|
+
import type { User } from '@makolabs/ripple';
|
|
97
|
+
|
|
98
|
+
let users = $state<User[]>([
|
|
99
|
+
/* your users */
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
function handleView(user: User) {
|
|
103
|
+
console.log('View user:', user);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function handleEdit(user: User) {
|
|
107
|
+
console.log('Edit user:', user);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function handleDelete(userId: string) {
|
|
111
|
+
console.log('Delete user:', userId);
|
|
112
|
+
}
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<UserTable
|
|
116
|
+
{users}
|
|
117
|
+
totalUsers={100}
|
|
118
|
+
currentPage={1}
|
|
119
|
+
pageSize={10}
|
|
120
|
+
onPageChange={(page) => console.log('Page:', page)}
|
|
121
|
+
onPageSizeChange={(size) => console.log('Size:', size)}
|
|
122
|
+
onView={handleView}
|
|
123
|
+
onEdit={handleEdit}
|
|
124
|
+
onDelete={handleDelete}
|
|
125
|
+
/>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### UserModal
|
|
129
|
+
|
|
130
|
+
Create or edit users with role and permission selection.
|
|
131
|
+
|
|
132
|
+
```svelte
|
|
133
|
+
<script lang="ts">
|
|
134
|
+
import { UserModal } from '@makolabs/ripple';
|
|
135
|
+
import type { User, Role } from '@makolabs/ripple';
|
|
136
|
+
|
|
137
|
+
let open = $state(false);
|
|
138
|
+
let selectedUser = $state<User | null>(null);
|
|
139
|
+
|
|
140
|
+
const roles: Role[] = [
|
|
141
|
+
{
|
|
142
|
+
value: 'admin',
|
|
143
|
+
label: 'Administrator',
|
|
144
|
+
description: 'Full access',
|
|
145
|
+
permissions: ['read', 'write', 'delete']
|
|
146
|
+
}
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
async function handleSave(user: User, mode: 'create' | 'edit') {
|
|
150
|
+
console.log('Save user:', user, 'Mode:', mode);
|
|
151
|
+
// Your save logic here
|
|
152
|
+
open = false;
|
|
153
|
+
}
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
<button onclick={() => (open = true)}>Open Modal</button>
|
|
157
|
+
|
|
158
|
+
<UserModal
|
|
159
|
+
bind:open
|
|
160
|
+
bind:user={selectedUser}
|
|
161
|
+
{roles}
|
|
162
|
+
onSave={handleSave}
|
|
163
|
+
onClose={() => (open = false)}
|
|
164
|
+
/>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### UserViewModal
|
|
168
|
+
|
|
169
|
+
View user details in a read-only modal with tabs for profile and permissions.
|
|
170
|
+
|
|
171
|
+
```svelte
|
|
172
|
+
<script lang="ts">
|
|
173
|
+
import { UserViewModal } from '@makolabs/ripple';
|
|
174
|
+
import type { User } from '@makolabs/ripple';
|
|
175
|
+
|
|
176
|
+
let open = $state(false);
|
|
177
|
+
let selectedUser = $state<User | null>(null);
|
|
178
|
+
|
|
179
|
+
function handleEdit(user: User) {
|
|
180
|
+
console.log('Edit user:', user);
|
|
181
|
+
open = false;
|
|
182
|
+
}
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<UserViewModal
|
|
186
|
+
bind:open
|
|
187
|
+
bind:user={selectedUser}
|
|
188
|
+
onEdit={handleEdit}
|
|
189
|
+
onClose={() => (open = false)}
|
|
190
|
+
/>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## TypeScript Types
|
|
194
|
+
|
|
195
|
+
### User
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
interface User {
|
|
199
|
+
id: string;
|
|
200
|
+
first_name?: string;
|
|
201
|
+
last_name?: string;
|
|
202
|
+
username?: string;
|
|
203
|
+
email_addresses?: UserEmail[];
|
|
204
|
+
phone_numbers?: UserPhone[];
|
|
205
|
+
image_url?: string;
|
|
206
|
+
created_at?: number;
|
|
207
|
+
last_sign_in_at?: number;
|
|
208
|
+
private_metadata?: Record<string, any>;
|
|
209
|
+
public_metadata?: Record<string, any>;
|
|
210
|
+
permissions?: string[];
|
|
211
|
+
role?: string;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Role
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
interface Role {
|
|
219
|
+
value: string;
|
|
220
|
+
label: string;
|
|
221
|
+
description?: string;
|
|
222
|
+
permissions: string[];
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Permission
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
interface Permission {
|
|
230
|
+
id: string;
|
|
231
|
+
name: string;
|
|
232
|
+
description?: string;
|
|
233
|
+
category?: string;
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Utility Functions
|
|
238
|
+
|
|
239
|
+
### createUser
|
|
240
|
+
|
|
241
|
+
Create a user object with default values:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { createUser } from '@makolabs/ripple';
|
|
245
|
+
|
|
246
|
+
const newUser = createUser({
|
|
247
|
+
first_name: 'John',
|
|
248
|
+
last_name: 'Doe',
|
|
249
|
+
email_addresses: [{ email_address: 'john@example.com' }]
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### getUserDisplayName
|
|
254
|
+
|
|
255
|
+
Get a user's display name:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { getUserDisplayName } from '@makolabs/ripple';
|
|
259
|
+
|
|
260
|
+
const displayName = getUserDisplayName(user);
|
|
261
|
+
// Returns: "John Doe" or username or email or ID
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### getUserInitials
|
|
265
|
+
|
|
266
|
+
Get a user's initials for avatars:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { getUserInitials } from '@makolabs/ripple';
|
|
270
|
+
|
|
271
|
+
const initials = getUserInitials(user);
|
|
272
|
+
// Returns: "J" or "?" if no name
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Styling
|
|
276
|
+
|
|
277
|
+
All components accept a `class` prop for custom styling:
|
|
278
|
+
|
|
279
|
+
```svelte
|
|
280
|
+
<UserManagement
|
|
281
|
+
class="my-custom-class"
|
|
282
|
+
{users}
|
|
283
|
+
{totalUsers}
|
|
284
|
+
{currentPage}
|
|
285
|
+
{pageSize}
|
|
286
|
+
onPageChange={handlePageChange}
|
|
287
|
+
onPageSizeChange={handlePageSizeChange}
|
|
288
|
+
/>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Components use Tailwind CSS classes by default, but you can override them with your own styles.
|
|
292
|
+
|
|
293
|
+
## Data Flow
|
|
294
|
+
|
|
295
|
+
The user management module is designed to be **data-driven** and **framework-agnostic**. It doesn't make any API calls directly. Instead, you provide the data and handle the API calls:
|
|
296
|
+
|
|
297
|
+
```svelte
|
|
298
|
+
<script lang="ts">
|
|
299
|
+
import { UserManagement } from '@makolabs/ripple';
|
|
300
|
+
import type { User } from '@makolabs/ripple';
|
|
301
|
+
|
|
302
|
+
// Your data source
|
|
303
|
+
let users = $state<User[]>([]);
|
|
304
|
+
let totalUsers = $state(0);
|
|
305
|
+
let currentPage = $state(1);
|
|
306
|
+
let pageSize = $state(10);
|
|
307
|
+
let loading = $state(false);
|
|
308
|
+
|
|
309
|
+
// Fetch users when page or size changes
|
|
310
|
+
$effect(() => {
|
|
311
|
+
loadUsers();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
async function loadUsers() {
|
|
315
|
+
loading = true;
|
|
316
|
+
try {
|
|
317
|
+
const response = await fetch(`/api/users?page=${currentPage}&size=${pageSize}`);
|
|
318
|
+
const data = await response.json();
|
|
319
|
+
users = data.users;
|
|
320
|
+
totalUsers = data.total;
|
|
321
|
+
} catch (error) {
|
|
322
|
+
console.error('Failed to load users:', error);
|
|
323
|
+
} finally {
|
|
324
|
+
loading = false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function handleCreateUser(userData: Partial<User>) {
|
|
329
|
+
const response = await fetch('/api/users', {
|
|
330
|
+
method: 'POST',
|
|
331
|
+
headers: { 'Content-Type': 'application/json' },
|
|
332
|
+
body: JSON.stringify(userData)
|
|
333
|
+
});
|
|
334
|
+
if (response.ok) {
|
|
335
|
+
await loadUsers(); // Reload the list
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Similar handlers for update, delete, etc.
|
|
340
|
+
</script>
|
|
341
|
+
|
|
342
|
+
<UserManagement
|
|
343
|
+
{users}
|
|
344
|
+
{totalUsers}
|
|
345
|
+
{loading}
|
|
346
|
+
{currentPage}
|
|
347
|
+
{pageSize}
|
|
348
|
+
onPageChange={(page) => (currentPage = page)}
|
|
349
|
+
onPageSizeChange={(size) => {
|
|
350
|
+
pageSize = size;
|
|
351
|
+
currentPage = 1;
|
|
352
|
+
}}
|
|
353
|
+
onCreateUser={handleCreateUser}
|
|
354
|
+
onUpdateUser={handleUpdateUser}
|
|
355
|
+
onDeleteUser={handleDeleteUser}
|
|
356
|
+
/>
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Examples
|
|
360
|
+
|
|
361
|
+
Check out the Storybook stories for interactive examples:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
npm run storybook
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Then navigate to:
|
|
368
|
+
|
|
369
|
+
- **User Management > User Management** - Full user management interface
|
|
370
|
+
- **User Management > User Table** - Table component examples
|
|
371
|
+
- **User Management > User Modal** - Modal component examples (if available)
|
|
372
|
+
- **User Management > User View Modal** - View modal examples (if available)
|
|
373
|
+
|
|
374
|
+
## Testing
|
|
375
|
+
|
|
376
|
+
Run the test suite:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
npm run test:unit
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Tests are located in:
|
|
383
|
+
|
|
384
|
+
- `UserTable.svelte.test.ts`
|
|
385
|
+
- `UserModal.svelte.test.ts`
|
|
386
|
+
- (Additional test files as needed)
|
|
387
|
+
|
|
388
|
+
## Browser Support
|
|
389
|
+
|
|
390
|
+
The user management module supports all modern browsers:
|
|
391
|
+
|
|
392
|
+
- Chrome/Edge (latest)
|
|
393
|
+
- Firefox (latest)
|
|
394
|
+
- Safari (latest)
|
|
395
|
+
- Mobile browsers (iOS Safari, Chrome Mobile)
|
|
396
|
+
|
|
397
|
+
## Accessibility
|
|
398
|
+
|
|
399
|
+
All components are built with accessibility in mind:
|
|
400
|
+
|
|
401
|
+
- ✅ Keyboard navigation
|
|
402
|
+
- ✅ ARIA labels and roles
|
|
403
|
+
- ✅ Screen reader support
|
|
404
|
+
- ✅ Focus management
|
|
405
|
+
- ✅ Semantic HTML
|
|
406
|
+
|
|
407
|
+
## License
|
|
408
|
+
|
|
409
|
+
Part of `@makolabs/ripple` - see main package for license information.
|
|
410
|
+
|
|
411
|
+
## Contributing
|
|
412
|
+
|
|
413
|
+
Contributions are welcome! Please see the main repository for contribution guidelines.
|
|
414
|
+
|
|
415
|
+
## Support
|
|
416
|
+
|
|
417
|
+
For issues, questions, or feature requests, please open an issue on the main repository.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { PageHeader, MetricCard, Button, cn } from '../index.js';
|
|
3
|
+
import UserTable from './UserTable.svelte';
|
|
4
|
+
import UserModal from './UserModal.svelte';
|
|
5
|
+
import UserViewModal from './UserViewModal.svelte';
|
|
6
|
+
import type { User, UserManagementProps } from './user-management.js';
|
|
7
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
users = [],
|
|
11
|
+
totalUsers = 0,
|
|
12
|
+
loading = false,
|
|
13
|
+
currentPage = 1,
|
|
14
|
+
pageSize = 10,
|
|
15
|
+
roles = [],
|
|
16
|
+
permissions = [],
|
|
17
|
+
onPageChange,
|
|
18
|
+
onPageSizeChange,
|
|
19
|
+
onSort,
|
|
20
|
+
onCreateUser,
|
|
21
|
+
onUpdateUser,
|
|
22
|
+
onDeleteUser,
|
|
23
|
+
onDeleteUsers,
|
|
24
|
+
class: className
|
|
25
|
+
}: UserManagementProps = $props();
|
|
26
|
+
|
|
27
|
+
// Modal states
|
|
28
|
+
let showEditCreateModal = $state(false);
|
|
29
|
+
let showViewModal = $state(false);
|
|
30
|
+
let selectedUser = $state<User | null>(null);
|
|
31
|
+
|
|
32
|
+
// Bulk action states
|
|
33
|
+
let selectedUsers = new SvelteSet<string>();
|
|
34
|
+
let bulkAction = $state<'delete' | ''>('');
|
|
35
|
+
|
|
36
|
+
// Derived states
|
|
37
|
+
const hasSelectedUsers = $derived(selectedUsers.size > 0);
|
|
38
|
+
const activeUsers = $derived(users.filter((u) => !!u.id));
|
|
39
|
+
|
|
40
|
+
// Modal handlers
|
|
41
|
+
function openViewModal(user: User) {
|
|
42
|
+
selectedUser = user;
|
|
43
|
+
showViewModal = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function openEditModal(user: User) {
|
|
47
|
+
selectedUser = user;
|
|
48
|
+
showEditCreateModal = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function openCreateModal() {
|
|
52
|
+
selectedUser = null;
|
|
53
|
+
showEditCreateModal = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function handleEditFromView() {
|
|
57
|
+
showViewModal = false;
|
|
58
|
+
if (selectedUser) {
|
|
59
|
+
openEditModal(selectedUser);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Save handlers
|
|
64
|
+
async function handleUserSave(user: User, mode: 'create' | 'edit') {
|
|
65
|
+
if (mode === 'create' && onCreateUser) {
|
|
66
|
+
await onCreateUser(user);
|
|
67
|
+
} else if (mode === 'edit' && onUpdateUser) {
|
|
68
|
+
await onUpdateUser(user.id, user);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Delete handlers
|
|
73
|
+
async function handleDeleteUser(userId: string) {
|
|
74
|
+
if (!confirm('Are you sure you want to delete this user? This action cannot be undone.')) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (onDeleteUser) {
|
|
78
|
+
await onDeleteUser(userId);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Bulk actions
|
|
83
|
+
async function executeBulkAction() {
|
|
84
|
+
if (!bulkAction || selectedUsers.size === 0) return;
|
|
85
|
+
|
|
86
|
+
const userIds = Array.from(selectedUsers);
|
|
87
|
+
|
|
88
|
+
if (!confirm(`Are you sure you want to ${bulkAction} ${userIds.length} user(s)?`)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (bulkAction === 'delete' && onDeleteUsers) {
|
|
93
|
+
await onDeleteUsers(userIds);
|
|
94
|
+
selectedUsers.clear();
|
|
95
|
+
selectedUsers = new SvelteSet(selectedUsers);
|
|
96
|
+
bulkAction = '';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
{#snippet PlusIcon()}
|
|
102
|
+
<svg class="mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
103
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
|
104
|
+
</svg>
|
|
105
|
+
{/snippet}
|
|
106
|
+
|
|
107
|
+
<div class={cn(className)}>
|
|
108
|
+
<!-- Page Header -->
|
|
109
|
+
<PageHeader
|
|
110
|
+
title="User Management"
|
|
111
|
+
subtitle="Manage and monitor user accounts"
|
|
112
|
+
layout="horizontal"
|
|
113
|
+
class="mb-6"
|
|
114
|
+
>
|
|
115
|
+
<Button onclick={openCreateModal} color="primary" disabled={!onCreateUser}>
|
|
116
|
+
{@render PlusIcon()}
|
|
117
|
+
Add User
|
|
118
|
+
</Button>
|
|
119
|
+
</PageHeader>
|
|
120
|
+
|
|
121
|
+
<!-- Stats Cards -->
|
|
122
|
+
<div class="mb-8 grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
123
|
+
<MetricCard title="Total Users" value={totalUsers.toLocaleString()} />
|
|
124
|
+
<MetricCard title="Active Users" value={activeUsers.length.toLocaleString()} />
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<!-- Bulk Actions Bar -->
|
|
128
|
+
{#if hasSelectedUsers}
|
|
129
|
+
<div
|
|
130
|
+
class="mb-4 flex flex-wrap items-center justify-between gap-3 rounded-lg border border-amber-200 bg-amber-50 p-4"
|
|
131
|
+
>
|
|
132
|
+
<div class="flex items-center">
|
|
133
|
+
<span class="text-sm font-medium text-amber-900">
|
|
134
|
+
{selectedUsers.size} user{selectedUsers.size === 1 ? '' : 's'} selected
|
|
135
|
+
</span>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="flex items-center gap-2">
|
|
138
|
+
<select
|
|
139
|
+
bind:value={bulkAction}
|
|
140
|
+
class="rounded-md border-amber-300 text-sm focus:border-amber-500 focus:ring-amber-500"
|
|
141
|
+
>
|
|
142
|
+
<option value="">Select Action</option>
|
|
143
|
+
<option value="delete">Delete Selected</option>
|
|
144
|
+
</select>
|
|
145
|
+
<Button onclick={executeBulkAction} disabled={!bulkAction} color="warning" size="sm">
|
|
146
|
+
Execute
|
|
147
|
+
</Button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
{/if}
|
|
151
|
+
|
|
152
|
+
<!-- Users Table Component -->
|
|
153
|
+
<UserTable
|
|
154
|
+
{users}
|
|
155
|
+
{loading}
|
|
156
|
+
{currentPage}
|
|
157
|
+
{pageSize}
|
|
158
|
+
{totalUsers}
|
|
159
|
+
{onPageChange}
|
|
160
|
+
{onPageSizeChange}
|
|
161
|
+
{onSort}
|
|
162
|
+
onView={openViewModal}
|
|
163
|
+
onEdit={openEditModal}
|
|
164
|
+
onDelete={handleDeleteUser}
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
<!-- User View Modal -->
|
|
168
|
+
<UserViewModal
|
|
169
|
+
bind:open={showViewModal}
|
|
170
|
+
bind:user={selectedUser}
|
|
171
|
+
{permissions}
|
|
172
|
+
onEdit={handleEditFromView}
|
|
173
|
+
onClose={() => (selectedUser = null)}
|
|
174
|
+
/>
|
|
175
|
+
|
|
176
|
+
<!-- User Edit/Create Modal -->
|
|
177
|
+
<UserModal
|
|
178
|
+
bind:open={showEditCreateModal}
|
|
179
|
+
bind:user={selectedUser}
|
|
180
|
+
{roles}
|
|
181
|
+
onSave={handleUserSave}
|
|
182
|
+
onClose={() => (selectedUser = null)}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import UserManagement from './UserManagement.svelte';
|
|
3
|
+
import type { UserManagementProps } from './user-management.js';
|
|
4
|
+
|
|
5
|
+
interface Props extends UserManagementProps {
|
|
6
|
+
testId?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
users = [],
|
|
11
|
+
totalUsers = 0,
|
|
12
|
+
loading = false,
|
|
13
|
+
currentPage = 1,
|
|
14
|
+
pageSize = 10,
|
|
15
|
+
roles = [],
|
|
16
|
+
permissions = [],
|
|
17
|
+
onPageChange = () => {},
|
|
18
|
+
onPageSizeChange = () => {},
|
|
19
|
+
onSort,
|
|
20
|
+
onCreateUser,
|
|
21
|
+
onUpdateUser,
|
|
22
|
+
onDeleteUser,
|
|
23
|
+
onDeleteUsers,
|
|
24
|
+
testId,
|
|
25
|
+
...rest
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<div data-testid={testId}>
|
|
30
|
+
<UserManagement
|
|
31
|
+
{users}
|
|
32
|
+
{totalUsers}
|
|
33
|
+
{loading}
|
|
34
|
+
{currentPage}
|
|
35
|
+
{pageSize}
|
|
36
|
+
{roles}
|
|
37
|
+
{permissions}
|
|
38
|
+
{onPageChange}
|
|
39
|
+
{onPageSizeChange}
|
|
40
|
+
{onSort}
|
|
41
|
+
{onCreateUser}
|
|
42
|
+
{onUpdateUser}
|
|
43
|
+
{onDeleteUser}
|
|
44
|
+
{onDeleteUsers}
|
|
45
|
+
{...rest}
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { UserManagementProps } from './user-management.js';
|
|
2
|
+
interface Props extends UserManagementProps {
|
|
3
|
+
testId?: string;
|
|
4
|
+
}
|
|
5
|
+
declare const UserManagementTestWrapper: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type UserManagementTestWrapper = ReturnType<typeof UserManagementTestWrapper>;
|
|
7
|
+
export default UserManagementTestWrapper;
|