@makolabs/ripple 1.2.3 → 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.
Files changed (106) hide show
  1. package/README.md +77 -0
  2. package/dist/adapters/ai/OpenAIAdapter.js +16 -11
  3. package/dist/adapters/ai/types.d.ts +3 -3
  4. package/dist/adapters/storage/BaseAdapter.d.ts +1 -1
  5. package/dist/adapters/storage/BaseAdapter.js +1 -1
  6. package/dist/adapters/storage/S3Adapter.js +2 -2
  7. package/dist/ai/AIChatInterface.svelte +32 -34
  8. package/dist/ai/AIChatInterface.svelte.d.ts +0 -1
  9. package/dist/ai/AIChatInterfaceTestWrapper.svelte +26 -0
  10. package/dist/ai/AIChatInterfaceTestWrapper.svelte.d.ts +17 -0
  11. package/dist/ai/ChatInput.svelte +7 -15
  12. package/dist/ai/ChatInput.svelte.d.ts +0 -2
  13. package/dist/ai/CodeRenderer.svelte +25 -12
  14. package/dist/ai/ComposeDropdown.svelte +17 -14
  15. package/dist/ai/MermaidRenderer.svelte +21 -17
  16. package/dist/ai/MermaidRenderer.svelte.d.ts +0 -1
  17. package/dist/ai/MessageBox.svelte +10 -7
  18. package/dist/ai/ThinkingDisplay.svelte +67 -43
  19. package/dist/ai/ai-chat-interface.d.ts +22 -21
  20. package/dist/ai/ai-chat-interface.js +8 -7
  21. package/dist/ai/content-detector.js +2 -2
  22. package/dist/button/ButtonTestWrapper.svelte +10 -0
  23. package/dist/button/ButtonTestWrapper.svelte.d.ts +7 -0
  24. package/dist/charts/Chart.svelte +6 -1
  25. package/dist/config/ai.js +1 -0
  26. package/dist/drawer/DrawerTestWrapper.svelte +19 -0
  27. package/dist/drawer/DrawerTestWrapper.svelte.d.ts +9 -0
  28. package/dist/drawer/drawer.d.ts +19 -18
  29. package/dist/drawer/drawer.js +7 -6
  30. package/dist/elements/accordion/Accordion.svelte +1 -1
  31. package/dist/elements/accordion/Accordion.svelte.d.ts +1 -1
  32. package/dist/elements/accordion/AccordionTestWrapper.svelte +21 -0
  33. package/dist/elements/accordion/AccordionTestWrapper.svelte.d.ts +10 -0
  34. package/dist/elements/badge/Badge.svelte +5 -4
  35. package/dist/elements/badge/BadgeTestWrapper.svelte +14 -0
  36. package/dist/elements/badge/BadgeTestWrapper.svelte.d.ts +9 -0
  37. package/dist/elements/badge/badge.d.ts +40 -39
  38. package/dist/elements/badge/badge.js +14 -13
  39. package/dist/elements/dropdown/Dropdown.svelte +0 -1
  40. package/dist/elements/pagination/Pagination.svelte +20 -26
  41. package/dist/elements/progress/Progress.svelte +3 -3
  42. package/dist/elements/timeline/Timeline.svelte +1 -1
  43. package/dist/file-browser/FileBrowser.svelte +7 -10
  44. package/dist/filters/CompactFilters.svelte +3 -3
  45. package/dist/forms/Checkbox.svelte +0 -1
  46. package/dist/forms/CheckboxTestWrapper.svelte +8 -0
  47. package/dist/forms/CheckboxTestWrapper.svelte.d.ts +4 -0
  48. package/dist/forms/DateRange.svelte +186 -198
  49. package/dist/forms/Form.svelte +1 -0
  50. package/dist/forms/Input.svelte +14 -5
  51. package/dist/forms/InputTestWrapper.svelte +8 -0
  52. package/dist/forms/InputTestWrapper.svelte.d.ts +4 -0
  53. package/dist/forms/NumberInput.svelte +2 -2
  54. package/dist/forms/RadioInputs.svelte +1 -1
  55. package/dist/forms/RadioPill.svelte +1 -1
  56. package/dist/forms/Slider.svelte +2 -2
  57. package/dist/forms/Tags.svelte +3 -3
  58. package/dist/forms/ToggleTestWrapper.svelte +8 -0
  59. package/dist/forms/ToggleTestWrapper.svelte.d.ts +7 -0
  60. package/dist/forms/slider.js +1 -1
  61. package/dist/header/PageHeader.svelte +2 -1
  62. package/dist/header/breadcrumbs.d.ts +47 -33
  63. package/dist/header/breadcrumbs.js +12 -11
  64. package/dist/index.d.ts +3 -2
  65. package/dist/index.js +2 -0
  66. package/dist/layout/activity-list/ActivityList.svelte +9 -11
  67. package/dist/layout/card/CardTestWrapper.svelte +15 -0
  68. package/dist/layout/card/CardTestWrapper.svelte.d.ts +7 -0
  69. package/dist/layout/card/RankedCard.svelte +2 -3
  70. package/dist/layout/navbar/navbar.d.ts +19 -18
  71. package/dist/layout/navbar/navbar.js +7 -6
  72. package/dist/layout/sidebar/NavGroup.svelte +1 -0
  73. package/dist/layout/table/Cells.svelte +5 -5
  74. package/dist/layout/table/Table.svelte +8 -8
  75. package/dist/layout/table/table.d.ts +28 -24
  76. package/dist/layout/table/table.js +14 -13
  77. package/dist/modal/Modal.svelte +1 -1
  78. package/dist/modal/ModalTestWrapper.svelte +20 -0
  79. package/dist/modal/ModalTestWrapper.svelte.d.ts +8 -0
  80. package/dist/modal/modal.d.ts +1 -20
  81. package/dist/pipeline/Pipeline.svelte +29 -17
  82. package/dist/user-management/README.md +417 -0
  83. package/dist/user-management/UserManagement.svelte +184 -0
  84. package/dist/user-management/UserManagement.svelte.d.ts +4 -0
  85. package/dist/user-management/UserManagementTestWrapper.svelte +47 -0
  86. package/dist/user-management/UserManagementTestWrapper.svelte.d.ts +7 -0
  87. package/dist/user-management/UserModal.svelte +303 -0
  88. package/dist/user-management/UserModal.svelte.d.ts +4 -0
  89. package/dist/user-management/UserModalTestWrapper.svelte +22 -0
  90. package/dist/user-management/UserModalTestWrapper.svelte.d.ts +7 -0
  91. package/dist/user-management/UserTable.svelte +219 -0
  92. package/dist/user-management/UserTable.svelte.d.ts +4 -0
  93. package/dist/user-management/UserTableTestWrapper.svelte +41 -0
  94. package/dist/user-management/UserTableTestWrapper.svelte.d.ts +7 -0
  95. package/dist/user-management/UserViewModal.svelte +282 -0
  96. package/dist/user-management/UserViewModal.svelte.d.ts +4 -0
  97. package/dist/user-management/UserViewModalTestWrapper.svelte +22 -0
  98. package/dist/user-management/UserViewModalTestWrapper.svelte.d.ts +7 -0
  99. package/dist/user-management/index.d.ts +10 -0
  100. package/dist/user-management/index.js +11 -0
  101. package/dist/user-management/user-management.d.ts +99 -0
  102. package/dist/user-management/user-management.js +42 -0
  103. package/package.json +3 -1
  104. package/dist/types/markdown.d.ts +0 -14
  105. package/dist/types/variants.d.ts +0 -1
  106. package/dist/types/variants.js +0 -1
@@ -0,0 +1,303 @@
1
+ <script lang="ts">
2
+ import { Modal, Button, cn } from '../index.js';
3
+ import type { User, UserModalProps, FormErrors } from './user-management.js';
4
+ import { getUserDisplayName } from './user-management.js';
5
+
6
+ // Icons as simple SVGs
7
+ let {
8
+ open = $bindable(),
9
+ user = $bindable(),
10
+ roles = [],
11
+ onSave,
12
+ onClose,
13
+ class: className
14
+ }: UserModalProps = $props();
15
+
16
+ // Mode determination
17
+ const mode = $derived(user ? 'edit' : 'create');
18
+
19
+ // Local state
20
+ let formErrors = $state<FormErrors>({});
21
+ let saving = $state(false);
22
+ let formElement = $state<HTMLFormElement | null>(null);
23
+
24
+ // Form data
25
+ let formData = $state<Partial<User>>({
26
+ first_name: '',
27
+ last_name: '',
28
+ username: '',
29
+ email_addresses: [{ email_address: '' }],
30
+ phone_numbers: [],
31
+ role: '',
32
+ permissions: []
33
+ });
34
+
35
+ // Initialize form data when user changes
36
+ $effect(() => {
37
+ if (open && user) {
38
+ formData = {
39
+ first_name: user.first_name || '',
40
+ last_name: user.last_name || '',
41
+ username: user.username || '',
42
+ email_addresses: user.email_addresses || [{ email_address: '' }],
43
+ phone_numbers: user.phone_numbers || [],
44
+ role: user.role || '',
45
+ permissions: user.permissions || []
46
+ };
47
+ } else if (open && !user) {
48
+ // Reset for create mode
49
+ formData = {
50
+ first_name: '',
51
+ last_name: '',
52
+ username: '',
53
+ email_addresses: [{ email_address: '' }],
54
+ phone_numbers: [],
55
+ role: '',
56
+ permissions: []
57
+ };
58
+ }
59
+ formErrors = {};
60
+ });
61
+
62
+ function handleClose() {
63
+ open = false;
64
+ formErrors = {};
65
+ saving = false;
66
+ if (onClose) onClose();
67
+ }
68
+
69
+ async function handleSubmit(event: SubmitEvent) {
70
+ event.preventDefault();
71
+ formErrors = {};
72
+
73
+ // Basic validation
74
+ if (!formData.first_name?.trim()) {
75
+ formErrors.first_name = 'First name is required';
76
+ }
77
+ if (!formData.last_name?.trim()) {
78
+ formErrors.last_name = 'Last name is required';
79
+ }
80
+ if (!formData.email_addresses?.[0]?.email_address?.trim()) {
81
+ formErrors.email = 'Email address is required';
82
+ }
83
+
84
+ if (Object.keys(formErrors).length > 0) {
85
+ return;
86
+ }
87
+
88
+ try {
89
+ saving = true;
90
+ const userData: User = {
91
+ id: user?.id || '',
92
+ first_name: formData.first_name,
93
+ last_name: formData.last_name,
94
+ username: formData.username,
95
+ email_addresses: formData.email_addresses,
96
+ phone_numbers: formData.phone_numbers,
97
+ role: formData.role,
98
+ permissions: formData.permissions
99
+ };
100
+ await onSave(userData, mode);
101
+ handleClose();
102
+ } catch (error) {
103
+ console.error('Error saving user:', error);
104
+ formErrors.submit = error instanceof Error ? error.message : 'Failed to save user';
105
+ } finally {
106
+ saving = false;
107
+ }
108
+ }
109
+
110
+ function handleRoleChange(roleValue: string) {
111
+ formData.role = roleValue;
112
+ const role = roles?.find((r) => r.value === roleValue);
113
+ if (role) {
114
+ formData.permissions = [...role.permissions];
115
+ }
116
+ }
117
+
118
+ function getModalTitle() {
119
+ if (mode === 'create') return 'Create New User';
120
+ return `Edit ${getUserDisplayName(user)}`;
121
+ }
122
+ </script>
123
+
124
+ <Modal
125
+ {open}
126
+ onclose={handleClose}
127
+ title={getModalTitle()}
128
+ contentclass="max-w-4xl"
129
+ class={cn(className)}
130
+ >
131
+ <form bind:this={formElement} onsubmit={handleSubmit} class="flex gap-6">
132
+ <!-- Left Column: Profile Information -->
133
+ <div class="min-w-0 flex-1 space-y-4">
134
+ <div class="border-b border-gray-200 pb-3">
135
+ <h3 class="text-lg font-semibold text-gray-900">Profile Information</h3>
136
+ </div>
137
+
138
+ <!-- First Name -->
139
+ <div>
140
+ <label for="first-name" class="mb-1 block text-sm font-medium text-gray-700">
141
+ First Name <span class="text-red-500">*</span>
142
+ </label>
143
+ <input
144
+ id="first-name"
145
+ type="text"
146
+ bind:value={formData.first_name}
147
+ class="w-full rounded-lg border px-3 py-2 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 {formErrors.first_name
148
+ ? 'border-red-300'
149
+ : 'border-gray-300'}"
150
+ placeholder="First name"
151
+ required
152
+ />
153
+ {#if formErrors.first_name}
154
+ <p class="mt-1 text-xs text-red-500">{formErrors.first_name}</p>
155
+ {/if}
156
+ </div>
157
+
158
+ <!-- Last Name -->
159
+ <div>
160
+ <label for="last-name" class="mb-1 block text-sm font-medium text-gray-700">
161
+ Last Name <span class="text-red-500">*</span>
162
+ </label>
163
+ <input
164
+ id="last-name"
165
+ type="text"
166
+ bind:value={formData.last_name}
167
+ class="w-full rounded-lg border px-3 py-2 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 {formErrors.last_name
168
+ ? 'border-red-300'
169
+ : 'border-gray-300'}"
170
+ placeholder="Last name"
171
+ required
172
+ />
173
+ {#if formErrors.last_name}
174
+ <p class="mt-1 text-xs text-red-500">{formErrors.last_name}</p>
175
+ {/if}
176
+ </div>
177
+
178
+ <!-- Email Address -->
179
+ <div>
180
+ <label for="email" class="mb-1 block text-sm font-medium text-gray-700">
181
+ Email Address <span class="text-red-500">*</span>
182
+ </label>
183
+ <input
184
+ id="email"
185
+ type="email"
186
+ bind:value={formData.email_addresses![0].email_address}
187
+ class="w-full rounded-lg border px-3 py-2 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 {formErrors.email
188
+ ? 'border-red-300'
189
+ : 'border-gray-300'}"
190
+ placeholder="user@example.com"
191
+ required
192
+ />
193
+ {#if formErrors.email}
194
+ <p class="mt-1 text-xs text-red-500">{formErrors.email}</p>
195
+ {/if}
196
+ </div>
197
+
198
+ <!-- Username (Optional) -->
199
+ <div>
200
+ <label for="username" class="mb-1 block text-sm font-medium text-gray-700">
201
+ Username
202
+ </label>
203
+ <input
204
+ id="username"
205
+ type="text"
206
+ bind:value={formData.username}
207
+ class="w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
208
+ placeholder="username"
209
+ />
210
+ </div>
211
+ </div>
212
+
213
+ <!-- Right Column: Permissions & Role -->
214
+ <div class="min-w-0 flex-1 space-y-4">
215
+ <div class="border-b border-gray-200 pb-3">
216
+ <h3 class="text-lg font-semibold text-gray-900">Role & Permissions</h3>
217
+ </div>
218
+
219
+ <!-- Role Selection -->
220
+ {#if roles && roles.length > 0}
221
+ <div>
222
+ <span class="mb-3 block text-sm font-medium text-gray-700">
223
+ Select User Role {#if mode === 'create'}<span class="text-red-500">*</span>{/if}
224
+ </span>
225
+ <div class="grid grid-cols-1 gap-2">
226
+ {#each roles as role (role.value)}
227
+ {@const isSelected = formData.role === role.value}
228
+ <button
229
+ type="button"
230
+ onclick={() => handleRoleChange(role.value)}
231
+ class="cursor-pointer rounded-lg border-2 p-3 text-left transition-all {isSelected
232
+ ? 'border-blue-500 bg-blue-50'
233
+ : 'border-gray-200 bg-white hover:border-gray-300'}"
234
+ >
235
+ <div class="flex items-center justify-between gap-2">
236
+ <div class="min-w-0 flex-1">
237
+ <h4 class="text-sm font-semibold text-gray-900">{role.label}</h4>
238
+ {#if role.description}
239
+ <p class="mt-1 line-clamp-2 text-xs text-gray-600">{role.description}</p>
240
+ {/if}
241
+ </div>
242
+ <div
243
+ class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full border-2 {isSelected
244
+ ? 'border-blue-500 bg-blue-500'
245
+ : 'border-gray-300 bg-white'}"
246
+ >
247
+ {#if isSelected}
248
+ <div class="h-2 w-2 rounded-full bg-white"></div>
249
+ {/if}
250
+ </div>
251
+ </div>
252
+ </button>
253
+ {/each}
254
+ </div>
255
+ </div>
256
+ {/if}
257
+
258
+ <!-- Permission Preview -->
259
+ {#if formData.permissions && formData.permissions.length > 0}
260
+ <div>
261
+ <h4 class="mb-2 text-sm font-medium text-gray-700">
262
+ Permissions ({formData.permissions.length})
263
+ </h4>
264
+ <div class="max-h-60 overflow-y-auto rounded-lg bg-gray-50 p-3">
265
+ <div class="space-y-2">
266
+ {#each formData.permissions as permission (permission)}
267
+ <div class="flex items-start gap-2 text-xs">
268
+ <div class="mt-1 h-1 w-1 shrink-0 rounded-full bg-blue-500"></div>
269
+ <div class="font-mono text-gray-700">{permission}</div>
270
+ </div>
271
+ {/each}
272
+ </div>
273
+ </div>
274
+ </div>
275
+ {/if}
276
+ </div>
277
+ </form>
278
+
279
+ <!-- Form Actions -->
280
+ {#snippet footer()}
281
+ <div class="flex items-center justify-between gap-4">
282
+ {#if formErrors.submit}
283
+ <p class="text-sm text-red-500">{formErrors.submit}</p>
284
+ {:else}
285
+ <div></div>
286
+ {/if}
287
+ <div class="flex gap-3">
288
+ <Button variant="outline" onclick={handleClose} disabled={saving} type="button">
289
+ Cancel
290
+ </Button>
291
+ <Button
292
+ type="button"
293
+ color="primary"
294
+ onclick={() => formElement?.requestSubmit()}
295
+ disabled={saving}
296
+ isLoading={saving}
297
+ >
298
+ {mode === 'create' ? 'Create User' : 'Save Changes'}
299
+ </Button>
300
+ </div>
301
+ </div>
302
+ {/snippet}
303
+ </Modal>
@@ -0,0 +1,4 @@
1
+ import type { UserModalProps } from './user-management.js';
2
+ declare const UserModal: import("svelte").Component<UserModalProps, {}, "open" | "user">;
3
+ type UserModal = ReturnType<typeof UserModal>;
4
+ export default UserModal;
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import UserModal from './UserModal.svelte';
3
+ import type { UserModalProps } from './user-management.js';
4
+
5
+ interface Props extends UserModalProps {
6
+ testId?: string;
7
+ }
8
+
9
+ let {
10
+ open = $bindable(),
11
+ user = $bindable(),
12
+ roles = [],
13
+ onSave = async () => {},
14
+ onClose = () => {},
15
+ testId,
16
+ ...rest
17
+ }: Props = $props();
18
+ </script>
19
+
20
+ <div data-testid={testId}>
21
+ <UserModal bind:open bind:user {roles} {onSave} {onClose} {...rest} />
22
+ </div>
@@ -0,0 +1,7 @@
1
+ import type { UserModalProps } from './user-management.js';
2
+ interface Props extends UserModalProps {
3
+ testId?: string;
4
+ }
5
+ declare const UserModalTestWrapper: import("svelte").Component<Props, {}, "open" | "user">;
6
+ type UserModalTestWrapper = ReturnType<typeof UserModalTestWrapper>;
7
+ export default UserModalTestWrapper;
@@ -0,0 +1,219 @@
1
+ <script lang="ts">
2
+ import { Table, type TableColumn } from '../index.js';
3
+ import type { User, UserTableProps } from './user-management.js';
4
+ import { getUserDisplayName, getUserInitials } from './user-management.js';
5
+ import { SvelteDate } from 'svelte/reactivity';
6
+
7
+ let {
8
+ users = [],
9
+ loading = false,
10
+ currentPage = 1,
11
+ pageSize = 10,
12
+ totalUsers = 0,
13
+ onPageChange,
14
+ onPageSizeChange,
15
+ onSort,
16
+ onView,
17
+ onEdit,
18
+ onDelete,
19
+ class: className = ''
20
+ }: UserTableProps = $props();
21
+
22
+ // Format date helper
23
+ function formatDate(timestamp?: number) {
24
+ if (!timestamp) return 'Never';
25
+ return new SvelteDate(timestamp).toLocaleDateString('en-US', {
26
+ year: 'numeric',
27
+ month: 'short',
28
+ day: 'numeric',
29
+ hour: '2-digit',
30
+ minute: '2-digit'
31
+ });
32
+ }
33
+
34
+ // Handle table sort
35
+ function handleTableSort(state: { column: string | null; direction: 'asc' | 'desc' | null }) {
36
+ if (onSort && state.column && state.direction) {
37
+ onSort(state);
38
+ }
39
+ }
40
+
41
+ // Table column definitions with cell snippets
42
+ const columns: TableColumn<User>[] = [
43
+ {
44
+ key: 'user',
45
+ header: 'User',
46
+ sortable: true,
47
+ sortKey: 'first_name',
48
+ cell: UserCell
49
+ },
50
+ {
51
+ key: 'email_addresses',
52
+ header: 'Contact',
53
+ sortable: false,
54
+ cell: ContactCell
55
+ },
56
+ {
57
+ key: 'last_sign_in_at',
58
+ header: 'Last Sign In',
59
+ sortable: true,
60
+ class: 'hidden lg:table-cell',
61
+ cell: LastSignInCell
62
+ },
63
+ {
64
+ key: 'created_at',
65
+ header: 'Created',
66
+ sortable: true,
67
+ class: 'hidden xl:table-cell',
68
+ cell: CreatedCell
69
+ },
70
+ {
71
+ key: 'actions',
72
+ header: 'Actions',
73
+ sortable: false,
74
+ align: 'right',
75
+ cell: ActionsCell
76
+ }
77
+ ];
78
+ </script>
79
+
80
+ {#snippet EyeIcon()}
81
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
82
+ <path
83
+ stroke-linecap="round"
84
+ stroke-linejoin="round"
85
+ stroke-width="2"
86
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
87
+ ></path>
88
+ <path
89
+ stroke-linecap="round"
90
+ stroke-linejoin="round"
91
+ stroke-width="2"
92
+ d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
93
+ ></path>
94
+ </svg>
95
+ {/snippet}
96
+
97
+ {#snippet EditIcon()}
98
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
99
+ <path
100
+ stroke-linecap="round"
101
+ stroke-linejoin="round"
102
+ stroke-width="2"
103
+ d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
104
+ ></path>
105
+ </svg>
106
+ {/snippet}
107
+
108
+ {#snippet TrashIcon()}
109
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
110
+ <path
111
+ stroke-linecap="round"
112
+ stroke-linejoin="round"
113
+ stroke-width="2"
114
+ d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
115
+ ></path>
116
+ </svg>
117
+ {/snippet}
118
+
119
+ {#snippet UserCell(user: User)}
120
+ <div class="flex items-center">
121
+ <div class="h-8 w-8 shrink-0">
122
+ {#if user.image_url}
123
+ <img class="h-8 w-8 rounded-full" src={user.image_url} alt="" />
124
+ {:else}
125
+ <div class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-300">
126
+ <span class="text-xs font-medium text-gray-700">
127
+ {getUserInitials(user)}
128
+ </span>
129
+ </div>
130
+ {/if}
131
+ </div>
132
+ <div class="ml-3">
133
+ <div class="text-sm font-medium text-gray-900">
134
+ {getUserDisplayName(user)}
135
+ </div>
136
+ <div class="text-xs text-gray-500">
137
+ {user.username || user.id.slice(0, 8) + '...'}
138
+ </div>
139
+ </div>
140
+ </div>
141
+ {/snippet}
142
+
143
+ {#snippet ContactCell(user: User)}
144
+ <div class="text-sm text-gray-900">
145
+ {#if user.email_addresses?.[0]}
146
+ <div class="max-w-[180px] truncate text-xs" title={user.email_addresses[0].email_address}>
147
+ {user.email_addresses[0].email_address}
148
+ </div>
149
+ {/if}
150
+ {#if user.phone_numbers?.[0]}
151
+ <div class="mt-1 text-xs text-gray-500">
152
+ {user.phone_numbers[0].phone_number}
153
+ </div>
154
+ {/if}
155
+ </div>
156
+ {/snippet}
157
+
158
+ {#snippet LastSignInCell(user: User)}
159
+ <span class="text-xs whitespace-nowrap text-gray-500">
160
+ {formatDate(user.last_sign_in_at)}
161
+ </span>
162
+ {/snippet}
163
+
164
+ {#snippet CreatedCell(user: User)}
165
+ <span class="text-xs whitespace-nowrap text-gray-500">
166
+ {formatDate(user.created_at)}
167
+ </span>
168
+ {/snippet}
169
+
170
+ {#snippet ActionsCell(user: User)}
171
+ <div class="flex items-center justify-end space-x-1">
172
+ <button
173
+ onclick={() => onView(user)}
174
+ class="inline-flex rounded p-1 text-blue-600 transition-colors hover:bg-blue-50 hover:text-blue-900"
175
+ title="View User"
176
+ aria-label="View User"
177
+ >
178
+ {@render EyeIcon()}
179
+ </button>
180
+ <button
181
+ onclick={() => onEdit(user)}
182
+ class="inline-flex rounded p-1 text-green-600 transition-colors hover:bg-green-50 hover:text-green-900"
183
+ title="Edit User"
184
+ aria-label="Edit User"
185
+ >
186
+ {@render EditIcon()}
187
+ </button>
188
+ <button
189
+ onclick={() => onDelete(user.id)}
190
+ class="inline-flex rounded p-1 text-red-600 transition-colors hover:bg-red-50 hover:text-red-900"
191
+ title="Delete User"
192
+ aria-label="Delete User"
193
+ >
194
+ {@render TrashIcon()}
195
+ </button>
196
+ </div>
197
+ {/snippet}
198
+
199
+ <Table
200
+ data={users}
201
+ {columns}
202
+ {currentPage}
203
+ {pageSize}
204
+ totalItems={totalUsers}
205
+ {loading}
206
+ pagination={true}
207
+ showPagination={true}
208
+ showPageSize={true}
209
+ pageSizeOptions={[10, 25, 50, 100]}
210
+ paginationTemplate="full"
211
+ onpagechange={onPageChange}
212
+ onpagesizechange={onPageSizeChange}
213
+ onsort={handleTableSort}
214
+ paginationPosition="bottom"
215
+ paginationclass="whitespace-nowrap flex-wrap gap-3 justify-between items-center"
216
+ wrapperclass="overflow-x-auto"
217
+ tableclass="min-w-full w-full"
218
+ class={className}
219
+ />
@@ -0,0 +1,4 @@
1
+ import type { UserTableProps } from './user-management.js';
2
+ declare const UserTable: import("svelte").Component<UserTableProps, {}, "">;
3
+ type UserTable = ReturnType<typeof UserTable>;
4
+ export default UserTable;
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ import UserTable from './UserTable.svelte';
3
+ import type { UserTableProps } from './user-management.js';
4
+
5
+ interface Props extends UserTableProps {
6
+ testId?: string;
7
+ }
8
+
9
+ let {
10
+ users = [],
11
+ loading = false,
12
+ currentPage = 1,
13
+ pageSize = 10,
14
+ totalUsers = 0,
15
+ onPageChange = () => {},
16
+ onPageSizeChange = () => {},
17
+ onSort,
18
+ onView = () => {},
19
+ onEdit = () => {},
20
+ onDelete = () => {},
21
+ testId,
22
+ ...rest
23
+ }: Props = $props();
24
+ </script>
25
+
26
+ <div data-testid={testId}>
27
+ <UserTable
28
+ {users}
29
+ {loading}
30
+ {currentPage}
31
+ {pageSize}
32
+ {totalUsers}
33
+ {onPageChange}
34
+ {onPageSizeChange}
35
+ {onSort}
36
+ {onView}
37
+ {onEdit}
38
+ {onDelete}
39
+ {...rest}
40
+ />
41
+ </div>
@@ -0,0 +1,7 @@
1
+ import type { UserTableProps } from './user-management.js';
2
+ interface Props extends UserTableProps {
3
+ testId?: string;
4
+ }
5
+ declare const UserTableTestWrapper: import("svelte").Component<Props, {}, "">;
6
+ type UserTableTestWrapper = ReturnType<typeof UserTableTestWrapper>;
7
+ export default UserTableTestWrapper;