@makolabs/ripple 3.0.9 → 3.0.11
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/dist/drawer/Drawer.svelte +33 -12
- package/dist/drawer/drawer-types.d.ts +13 -5
- package/dist/drawer/drawer.d.ts +45 -0
- package/dist/drawer/drawer.js +14 -5
- package/dist/funcs/mock-user-management.d.ts +40 -0
- package/dist/funcs/mock-user-management.js +85 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/modal/ConfirmDialog.svelte +6 -9
- package/dist/modal/Modal.svelte +18 -14
- package/dist/modal/modal-types.d.ts +9 -5
- package/dist/modal/modal.d.ts +42 -0
- package/dist/modal/modal.js +18 -8
- package/dist/user-management/ApiKeyField.svelte +165 -0
- package/dist/user-management/ApiKeyField.svelte.d.ts +32 -0
- package/dist/user-management/RoleCard.svelte +73 -0
- package/dist/user-management/RoleCard.svelte.d.ts +16 -0
- package/dist/user-management/UserApproveModal.svelte +115 -0
- package/dist/user-management/UserApproveModal.svelte.d.ts +4 -0
- package/dist/user-management/UserIdentityCard.svelte +53 -0
- package/dist/user-management/UserIdentityCard.svelte.d.ts +11 -0
- package/dist/user-management/UserManagement.svelte +191 -20
- package/dist/user-management/UserModal.svelte +203 -439
- package/dist/user-management/UserTable.svelte +48 -55
- package/dist/user-management/UserViewModal.svelte +87 -221
- package/dist/user-management/UserViewModal.svelte.d.ts +1 -1
- package/dist/user-management/user-management-types.d.ts +52 -3
- package/package.json +1 -1
- package/dist/modal/ModalFooter.svelte +0 -35
- package/dist/modal/ModalFooter.svelte.d.ts +0 -11
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
open = $bindable(false),
|
|
13
13
|
onclose: onClose = () => {},
|
|
14
14
|
title,
|
|
15
|
+
description,
|
|
15
16
|
position = 'right',
|
|
16
17
|
size = 'sm',
|
|
18
|
+
footerAlign,
|
|
17
19
|
children,
|
|
18
20
|
header,
|
|
19
21
|
footer,
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
headerClass = '',
|
|
24
26
|
bodyClass = '',
|
|
25
27
|
titleClass = '',
|
|
28
|
+
descriptionClass = '',
|
|
26
29
|
footerClass = '',
|
|
27
30
|
testId
|
|
28
31
|
}: DrawerProps = $props();
|
|
@@ -37,12 +40,14 @@
|
|
|
37
40
|
header: headerVClass,
|
|
38
41
|
body,
|
|
39
42
|
footer: footerVClass,
|
|
40
|
-
title: titleVClass
|
|
43
|
+
title: titleVClass,
|
|
44
|
+
description: descriptionVClass
|
|
41
45
|
} = $derived(
|
|
42
46
|
drawer({
|
|
43
47
|
open,
|
|
44
48
|
position,
|
|
45
|
-
size
|
|
49
|
+
size,
|
|
50
|
+
footerAlign
|
|
46
51
|
})
|
|
47
52
|
);
|
|
48
53
|
|
|
@@ -53,6 +58,7 @@
|
|
|
53
58
|
const headerClasses = $derived(cn(headerVClass(), headerClass));
|
|
54
59
|
const bodyClasses = $derived(cn(body(), bodyClass));
|
|
55
60
|
const titleClasses = $derived(cn(titleVClass(), titleClass));
|
|
61
|
+
const descriptionClasses = $derived(cn(descriptionVClass(), descriptionClass));
|
|
56
62
|
const footerClasses = $derived(cn(footerVClass(), 'mt-auto', footerClass));
|
|
57
63
|
|
|
58
64
|
function handleBackdropClick(e: MouseEvent) {
|
|
@@ -161,6 +167,8 @@
|
|
|
161
167
|
role="dialog"
|
|
162
168
|
aria-modal="true"
|
|
163
169
|
aria-labelledby={title ? 'drawer-title' : undefined}
|
|
170
|
+
aria-describedby={description ? 'drawer-description' : undefined}
|
|
171
|
+
aria-label={!title ? 'Drawer' : undefined}
|
|
164
172
|
>
|
|
165
173
|
{#key open}
|
|
166
174
|
<!-- Backdrop -->
|
|
@@ -196,19 +204,32 @@
|
|
|
196
204
|
</button>
|
|
197
205
|
|
|
198
206
|
<!-- Header -->
|
|
199
|
-
{#if
|
|
207
|
+
{#if header}
|
|
200
208
|
<div class={headerClasses}>
|
|
201
|
-
|
|
202
|
-
id="drawer-title"
|
|
203
|
-
class={titleClasses}
|
|
204
|
-
data-testid={buildTestId('drawer', 'title', testId)}
|
|
205
|
-
>
|
|
206
|
-
{title}
|
|
207
|
-
</h3>
|
|
209
|
+
{@render header()}
|
|
208
210
|
</div>
|
|
209
|
-
{:else if
|
|
211
|
+
{:else if title || description}
|
|
210
212
|
<div class={headerClasses}>
|
|
211
|
-
|
|
213
|
+
<div class="flex min-w-0 flex-1 flex-col">
|
|
214
|
+
{#if title}
|
|
215
|
+
<h3
|
|
216
|
+
id="drawer-title"
|
|
217
|
+
class={titleClasses}
|
|
218
|
+
data-testid={buildTestId('drawer', 'title', testId)}
|
|
219
|
+
>
|
|
220
|
+
{title}
|
|
221
|
+
</h3>
|
|
222
|
+
{/if}
|
|
223
|
+
{#if description}
|
|
224
|
+
<p
|
|
225
|
+
id="drawer-description"
|
|
226
|
+
class={descriptionClasses}
|
|
227
|
+
data-testid={buildTestId('drawer', 'description', testId)}
|
|
228
|
+
>
|
|
229
|
+
{description}
|
|
230
|
+
</p>
|
|
231
|
+
{/if}
|
|
232
|
+
</div>
|
|
212
233
|
</div>
|
|
213
234
|
{/if}
|
|
214
235
|
|
|
@@ -12,10 +12,8 @@ import type { VariantSizes } from '../index.js';
|
|
|
12
12
|
* <Drawer bind:open title="Filters" position="right">
|
|
13
13
|
* <FilterPanel bind:filters />
|
|
14
14
|
* {#snippet footer()}
|
|
15
|
-
* <
|
|
16
|
-
*
|
|
17
|
-
* <Button onclick={apply}>Apply</Button>
|
|
18
|
-
* </div>
|
|
15
|
+
* <Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
|
|
16
|
+
* <Button onclick={apply}>Apply</Button>
|
|
19
17
|
* {/snippet}
|
|
20
18
|
* </Drawer>
|
|
21
19
|
* ```
|
|
@@ -27,6 +25,8 @@ export type DrawerProps = {
|
|
|
27
25
|
onclose?: () => void;
|
|
28
26
|
/** Header title text. Ignored if a `header` snippet is provided. */
|
|
29
27
|
title?: string;
|
|
28
|
+
/** Smaller muted line directly below the title. */
|
|
29
|
+
description?: string;
|
|
30
30
|
/** Side the drawer slides in from. @default 'right' */
|
|
31
31
|
position?: 'left' | 'right';
|
|
32
32
|
/** @default 'sm' */
|
|
@@ -48,7 +48,15 @@ export type DrawerProps = {
|
|
|
48
48
|
children?: Snippet;
|
|
49
49
|
/** Replace the default title bar. */
|
|
50
50
|
header?: Snippet;
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Footer content — typically action buttons. The footer wrapper renders
|
|
53
|
+
* with a tinted background, hairline separator, padding, and flex layout
|
|
54
|
+
* built-in, so the snippet can just contain `<Button>`s directly.
|
|
55
|
+
*/
|
|
52
56
|
footer?: Snippet;
|
|
57
|
+
/** How to align footer items horizontally. @default 'end' */
|
|
58
|
+
footerAlign?: 'start' | 'center' | 'end' | 'between';
|
|
59
|
+
/** Classes on the description element. */
|
|
60
|
+
descriptionClass?: ClassValue;
|
|
53
61
|
testId?: string;
|
|
54
62
|
};
|
package/dist/drawer/drawer.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
export declare const drawer: import("tailwind-variants").TVReturnType<{
|
|
2
|
+
footerAlign: {
|
|
3
|
+
start: {
|
|
4
|
+
footer: string;
|
|
5
|
+
};
|
|
6
|
+
center: {
|
|
7
|
+
footer: string;
|
|
8
|
+
};
|
|
9
|
+
end: {
|
|
10
|
+
footer: string;
|
|
11
|
+
};
|
|
12
|
+
between: {
|
|
13
|
+
footer: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
2
16
|
open: {
|
|
3
17
|
true: {
|
|
4
18
|
base: string;
|
|
@@ -50,8 +64,23 @@ export declare const drawer: import("tailwind-variants").TVReturnType<{
|
|
|
50
64
|
body: string;
|
|
51
65
|
footer: string;
|
|
52
66
|
title: string;
|
|
67
|
+
description: string;
|
|
53
68
|
closeButton: string;
|
|
54
69
|
}, undefined, {
|
|
70
|
+
footerAlign: {
|
|
71
|
+
start: {
|
|
72
|
+
footer: string;
|
|
73
|
+
};
|
|
74
|
+
center: {
|
|
75
|
+
footer: string;
|
|
76
|
+
};
|
|
77
|
+
end: {
|
|
78
|
+
footer: string;
|
|
79
|
+
};
|
|
80
|
+
between: {
|
|
81
|
+
footer: string;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
55
84
|
open: {
|
|
56
85
|
true: {
|
|
57
86
|
base: string;
|
|
@@ -103,8 +132,23 @@ export declare const drawer: import("tailwind-variants").TVReturnType<{
|
|
|
103
132
|
body: string;
|
|
104
133
|
footer: string;
|
|
105
134
|
title: string;
|
|
135
|
+
description: string;
|
|
106
136
|
closeButton: string;
|
|
107
137
|
}, import("tailwind-variants").TVReturnType<{
|
|
138
|
+
footerAlign: {
|
|
139
|
+
start: {
|
|
140
|
+
footer: string;
|
|
141
|
+
};
|
|
142
|
+
center: {
|
|
143
|
+
footer: string;
|
|
144
|
+
};
|
|
145
|
+
end: {
|
|
146
|
+
footer: string;
|
|
147
|
+
};
|
|
148
|
+
between: {
|
|
149
|
+
footer: string;
|
|
150
|
+
};
|
|
151
|
+
};
|
|
108
152
|
open: {
|
|
109
153
|
true: {
|
|
110
154
|
base: string;
|
|
@@ -156,5 +200,6 @@ export declare const drawer: import("tailwind-variants").TVReturnType<{
|
|
|
156
200
|
body: string;
|
|
157
201
|
footer: string;
|
|
158
202
|
title: string;
|
|
203
|
+
description: string;
|
|
159
204
|
closeButton: string;
|
|
160
205
|
}, undefined, unknown, unknown, undefined>>;
|
package/dist/drawer/drawer.js
CHANGED
|
@@ -6,13 +6,21 @@ export const drawer = tv({
|
|
|
6
6
|
backdrop: 'fixed inset-0 transition-opacity bg-black/50',
|
|
7
7
|
contentWrapper: 'absolute flex flex-col transform transition-transform max-w-[100vw]',
|
|
8
8
|
content: 'relative flex flex-col h-full w-full overflow-y-auto bg-white',
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
// Header/footer tinted to match Modal/Card visual language.
|
|
10
|
+
header: 'flex items-center justify-between gap-3 px-5 py-3 bg-default-100/50 border-b border-default-200',
|
|
11
|
+
body: 'flex-1 overflow-y-auto p-5 text-sm text-default-700',
|
|
12
|
+
footer: 'flex flex-wrap items-center gap-3 px-5 py-3 bg-default-100/50 border-t border-default-200',
|
|
13
|
+
title: 'text-default-900 text-sm font-semibold leading-tight',
|
|
14
|
+
description: 'text-default-500 text-xs font-normal',
|
|
13
15
|
closeButton: 'text-default-400 hover:text-default-500 rounded-md cursor-pointer'
|
|
14
16
|
},
|
|
15
17
|
variants: {
|
|
18
|
+
footerAlign: {
|
|
19
|
+
start: { footer: 'justify-start' },
|
|
20
|
+
center: { footer: 'justify-center' },
|
|
21
|
+
end: { footer: 'justify-end' },
|
|
22
|
+
between: { footer: 'justify-between' }
|
|
23
|
+
},
|
|
16
24
|
open: {
|
|
17
25
|
true: {
|
|
18
26
|
base: 'visible',
|
|
@@ -76,6 +84,7 @@ export const drawer = tv({
|
|
|
76
84
|
defaultVariants: {
|
|
77
85
|
open: false,
|
|
78
86
|
position: 'right',
|
|
79
|
-
size: 'sm'
|
|
87
|
+
size: 'sm',
|
|
88
|
+
footerAlign: 'end'
|
|
80
89
|
}
|
|
81
90
|
});
|
|
@@ -20,6 +20,7 @@ import type { User, GetUsersOptions, GetUsersResult } from '../index.js';
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function resetState(options?: {
|
|
22
22
|
initialUsers?: User[];
|
|
23
|
+
initialPendingUsers?: User[];
|
|
23
24
|
simulateDelay?: boolean;
|
|
24
25
|
delayMs?: number;
|
|
25
26
|
}): Promise<void>;
|
|
@@ -77,3 +78,42 @@ export declare function generateApiKey(options: {
|
|
|
77
78
|
apiKey: string;
|
|
78
79
|
message: string;
|
|
79
80
|
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Verify an API key (mock implementation).
|
|
83
|
+
* Matches UserManagementAdapter.verifyToken signature.
|
|
84
|
+
*
|
|
85
|
+
* The mock only accepts keys that exactly match an existing active user's
|
|
86
|
+
* `private_metadata.mako_api_key`; unknown/revoked keys are rejected so the
|
|
87
|
+
* failure UI is reachable.
|
|
88
|
+
*/
|
|
89
|
+
export declare function verifyToken(options: {
|
|
90
|
+
apiKey: string;
|
|
91
|
+
}): Promise<{
|
|
92
|
+
valid: boolean;
|
|
93
|
+
scopes?: string[];
|
|
94
|
+
error?: string;
|
|
95
|
+
sub?: string;
|
|
96
|
+
client_id?: string;
|
|
97
|
+
}>;
|
|
98
|
+
/**
|
|
99
|
+
* List users awaiting approval (registered in identity provider but not
|
|
100
|
+
* yet onboarded — no org / no API key).
|
|
101
|
+
* Matches UserManagementAdapter.getPendingUsers signature.
|
|
102
|
+
*/
|
|
103
|
+
export declare function getPendingUsers(options: GetUsersOptions): Promise<GetUsersResult>;
|
|
104
|
+
/**
|
|
105
|
+
* Approve a pending user — assigns the chosen role and generates an API key.
|
|
106
|
+
* Moves the user from the pending list to the active list.
|
|
107
|
+
* Matches UserManagementAdapter.approveUser signature.
|
|
108
|
+
*/
|
|
109
|
+
export declare function approveUser(input: {
|
|
110
|
+
userId: string;
|
|
111
|
+
role: string;
|
|
112
|
+
}): Promise<{
|
|
113
|
+
apiKey: string;
|
|
114
|
+
}>;
|
|
115
|
+
/**
|
|
116
|
+
* Reject a pending user — permanently removes them from the identity provider.
|
|
117
|
+
* Matches UserManagementAdapter.rejectUser signature.
|
|
118
|
+
*/
|
|
119
|
+
export declare function rejectUser(userId: string): Promise<void>;
|
|
@@ -16,13 +16,17 @@
|
|
|
16
16
|
*/
|
|
17
17
|
// Internal module-level state
|
|
18
18
|
let mockUsers = [];
|
|
19
|
+
let mockPendingUsers = [];
|
|
19
20
|
let simulateDelay = false;
|
|
20
21
|
let delayMs = 300;
|
|
21
22
|
/**
|
|
22
23
|
* Reset mock adapter state
|
|
23
24
|
*/
|
|
24
25
|
export async function resetState(options = {}) {
|
|
25
|
-
|
|
26
|
+
// Deep-clone fixtures so internal mutations (splice in approveUser/rejectUser)
|
|
27
|
+
// don't poison caller-owned arrays — keeps tests order-independent.
|
|
28
|
+
mockUsers = structuredClone(options.initialUsers ?? []);
|
|
29
|
+
mockPendingUsers = structuredClone(options.initialPendingUsers ?? []);
|
|
26
30
|
simulateDelay = options.simulateDelay ?? false;
|
|
27
31
|
delayMs = options.delayMs ?? 300;
|
|
28
32
|
}
|
|
@@ -214,3 +218,83 @@ export async function generateApiKey(options) {
|
|
|
214
218
|
: 'API key generated successfully'
|
|
215
219
|
};
|
|
216
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Verify an API key (mock implementation).
|
|
223
|
+
* Matches UserManagementAdapter.verifyToken signature.
|
|
224
|
+
*
|
|
225
|
+
* The mock only accepts keys that exactly match an existing active user's
|
|
226
|
+
* `private_metadata.mako_api_key`; unknown/revoked keys are rejected so the
|
|
227
|
+
* failure UI is reachable.
|
|
228
|
+
*/
|
|
229
|
+
export async function verifyToken(options) {
|
|
230
|
+
await delay();
|
|
231
|
+
const owner = mockUsers.find((u) => u.private_metadata &&
|
|
232
|
+
typeof u.private_metadata === 'object' &&
|
|
233
|
+
'mako_api_key' in u.private_metadata &&
|
|
234
|
+
u.private_metadata.mako_api_key === options.apiKey);
|
|
235
|
+
if (!owner) {
|
|
236
|
+
return {
|
|
237
|
+
valid: false,
|
|
238
|
+
error: 'API key has been revoked or is malformed'
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// Real Mako Auth returns `sub` as the user's email — mirror that here so
|
|
242
|
+
// downstream code that key-lookups by email behaves identically.
|
|
243
|
+
return {
|
|
244
|
+
valid: true,
|
|
245
|
+
sub: owner.email_addresses?.[0]?.email_address ?? owner.id,
|
|
246
|
+
client_id: 'mock-client',
|
|
247
|
+
scopes: owner.permissions ?? []
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* List users awaiting approval (registered in identity provider but not
|
|
252
|
+
* yet onboarded — no org / no API key).
|
|
253
|
+
* Matches UserManagementAdapter.getPendingUsers signature.
|
|
254
|
+
*/
|
|
255
|
+
export async function getPendingUsers(options) {
|
|
256
|
+
await delay();
|
|
257
|
+
const start = (options.page - 1) * options.pageSize;
|
|
258
|
+
const end = start + options.pageSize;
|
|
259
|
+
return {
|
|
260
|
+
users: mockPendingUsers.slice(start, end),
|
|
261
|
+
totalUsers: mockPendingUsers.length
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Approve a pending user — assigns the chosen role and generates an API key.
|
|
266
|
+
* Moves the user from the pending list to the active list.
|
|
267
|
+
* Matches UserManagementAdapter.approveUser signature.
|
|
268
|
+
*/
|
|
269
|
+
export async function approveUser(input) {
|
|
270
|
+
await delay();
|
|
271
|
+
// Fail fast on empty role — no key minted, no state mutation.
|
|
272
|
+
if (!input.role?.trim()) {
|
|
273
|
+
throw new Error('Role is required to approve a user');
|
|
274
|
+
}
|
|
275
|
+
const idx = mockPendingUsers.findIndex((u) => u.id === input.userId);
|
|
276
|
+
if (idx === -1) {
|
|
277
|
+
throw new Error(`Pending user ${input.userId} not found`);
|
|
278
|
+
}
|
|
279
|
+
const user = mockPendingUsers[idx];
|
|
280
|
+
const apiKey = `mock_api_key_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
281
|
+
mockPendingUsers.splice(idx, 1);
|
|
282
|
+
mockUsers.push({
|
|
283
|
+
...user,
|
|
284
|
+
role: input.role,
|
|
285
|
+
private_metadata: { ...(user.private_metadata || {}), mako_api_key: apiKey }
|
|
286
|
+
});
|
|
287
|
+
return { apiKey };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Reject a pending user — permanently removes them from the identity provider.
|
|
291
|
+
* Matches UserManagementAdapter.rejectUser signature.
|
|
292
|
+
*/
|
|
293
|
+
export async function rejectUser(userId) {
|
|
294
|
+
await delay();
|
|
295
|
+
const idx = mockPendingUsers.findIndex((u) => u.id === userId);
|
|
296
|
+
if (idx === -1) {
|
|
297
|
+
throw new Error(`Pending user ${userId} not found`);
|
|
298
|
+
}
|
|
299
|
+
mockPendingUsers.splice(idx, 1);
|
|
300
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -60,12 +60,11 @@ export type { StepperProps, StepperStep, StepState, StepperOrientation } from '.
|
|
|
60
60
|
export type { ActivityItemBadge, ActivityItemAction, ActivityItem, ActivityListProps, ActivityListSize } from './layout/activity-list/activity-list-types.js';
|
|
61
61
|
export type { FileUploadProps, FileUploadSize, FilePreviewProps, UploadedFile, StagedFile } from './elements/file-upload/file-upload-types.js';
|
|
62
62
|
export type { ChatMessageType, StreamingCallback, ChatAction, ChatMessage, ChatResponse, QuickAction, FileBrowserProps } from './ai/ai-types.js';
|
|
63
|
-
export type { GetUsersOptions, GetUsersResult, UserEmail, UserPhone, User, Permission, Role, UserTableProps, UserModalProps, UserModalSavePayload, UserViewModalProps, UserManagementAdapter, UserManagementProps, FormErrors } from './user-management/user-management-types.js';
|
|
63
|
+
export type { GetUsersOptions, GetUsersResult, UserEmail, UserPhone, User, Permission, Role, UserTableProps, UserModalProps, UserModalSavePayload, UserViewModalProps, UserApproveModalProps, UserManagementAdapter, UserManagementProps, FormErrors } from './user-management/user-management-types.js';
|
|
64
64
|
export { tv, cn } from './helper/cls.js';
|
|
65
65
|
export { isRouteActive } from './helper/nav.svelte.js';
|
|
66
66
|
export { default as Button } from './button/Button.svelte';
|
|
67
67
|
export { default as Modal } from './modal/Modal.svelte';
|
|
68
|
-
export { default as ModalFooter } from './modal/ModalFooter.svelte';
|
|
69
68
|
export { default as ConfirmDialog } from './modal/ConfirmDialog.svelte';
|
|
70
69
|
export { default as Pipeline } from './pipeline/Pipeline.svelte';
|
|
71
70
|
export { pipelineVariants } from './pipeline/pipeline.js';
|
|
@@ -149,3 +148,7 @@ export { default as UserManagement } from './user-management/UserManagement.svel
|
|
|
149
148
|
export { default as UserTable } from './user-management/UserTable.svelte';
|
|
150
149
|
export { default as UserModal } from './user-management/UserModal.svelte';
|
|
151
150
|
export { default as UserViewModal } from './user-management/UserViewModal.svelte';
|
|
151
|
+
export { default as UserApproveModal } from './user-management/UserApproveModal.svelte';
|
|
152
|
+
export { default as RoleCard } from './user-management/RoleCard.svelte';
|
|
153
|
+
export { default as ApiKeyField } from './user-management/ApiKeyField.svelte';
|
|
154
|
+
export { default as UserIdentityCard } from './user-management/UserIdentityCard.svelte';
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,6 @@ export { isRouteActive } from './helper/nav.svelte.js';
|
|
|
25
25
|
export { default as Button } from './button/Button.svelte';
|
|
26
26
|
// Modal
|
|
27
27
|
export { default as Modal } from './modal/Modal.svelte';
|
|
28
|
-
export { default as ModalFooter } from './modal/ModalFooter.svelte';
|
|
29
28
|
export { default as ConfirmDialog } from './modal/ConfirmDialog.svelte';
|
|
30
29
|
// Pipeline
|
|
31
30
|
export { default as Pipeline } from './pipeline/Pipeline.svelte';
|
|
@@ -154,3 +153,7 @@ export { default as UserManagement } from './user-management/UserManagement.svel
|
|
|
154
153
|
export { default as UserTable } from './user-management/UserTable.svelte';
|
|
155
154
|
export { default as UserModal } from './user-management/UserModal.svelte';
|
|
156
155
|
export { default as UserViewModal } from './user-management/UserViewModal.svelte';
|
|
156
|
+
export { default as UserApproveModal } from './user-management/UserApproveModal.svelte';
|
|
157
|
+
export { default as RoleCard } from './user-management/RoleCard.svelte';
|
|
158
|
+
export { default as ApiKeyField } from './user-management/ApiKeyField.svelte';
|
|
159
|
+
export { default as UserIdentityCard } from './user-management/UserIdentityCard.svelte';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Modal from './Modal.svelte';
|
|
3
|
-
import ModalFooter from './ModalFooter.svelte';
|
|
4
3
|
import Button from '../button/Button.svelte';
|
|
5
4
|
import type { ConfirmDialogProps } from './confirm-dialog-types.js';
|
|
6
5
|
import { Color } from '../variants.js';
|
|
@@ -53,13 +52,11 @@
|
|
|
53
52
|
</div>
|
|
54
53
|
|
|
55
54
|
{#snippet footer()}
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
</Button>
|
|
63
|
-
</ModalFooter>
|
|
55
|
+
<Button variant="outline" onclick={handleCancel} disabled={busy}>
|
|
56
|
+
{cancelLabel}
|
|
57
|
+
</Button>
|
|
58
|
+
<Button color={confirmColor} onclick={handleConfirm} loading={busy}>
|
|
59
|
+
{confirmLabel}
|
|
60
|
+
</Button>
|
|
64
61
|
{/snippet}
|
|
65
62
|
</Modal>
|
package/dist/modal/Modal.svelte
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
title,
|
|
15
15
|
description,
|
|
16
16
|
size,
|
|
17
|
+
footerAlign,
|
|
17
18
|
hideCloseButton = false,
|
|
18
19
|
class: className = '',
|
|
19
20
|
contentClass = '',
|
|
@@ -30,25 +31,17 @@
|
|
|
30
31
|
|
|
31
32
|
let modalElement: HTMLDivElement | undefined = $state();
|
|
32
33
|
|
|
33
|
-
const styles = $derived(modal({ size }));
|
|
34
|
+
const styles = $derived(modal({ size, footerAlign }));
|
|
34
35
|
|
|
35
36
|
const baseClass = $derived(cn(styles.base(), className));
|
|
36
37
|
const backdropClasses = $derived(cn(styles.backdrop(), backdropClass));
|
|
37
38
|
const containerClass = $derived(cn(styles.container(), contentClass));
|
|
38
39
|
const headerClasses = $derived(cn(styles.header(), headerClass));
|
|
39
40
|
const titleClasses = $derived(cn(styles.title(), titleClass));
|
|
40
|
-
const bodyClasses = $derived(
|
|
41
|
-
cn(
|
|
42
|
-
'flex-1 px-6 overflow-y-auto',
|
|
43
|
-
// Adjust top padding based on header presence
|
|
44
|
-
title || description ? 'py-4' : 'pt-6 pb-4',
|
|
45
|
-
bodyClass
|
|
46
|
-
)
|
|
47
|
-
);
|
|
41
|
+
const bodyClasses = $derived(cn(styles.body(), bodyClass));
|
|
48
42
|
const footerClasses = $derived(cn(styles.footer(), footerClass));
|
|
49
43
|
const closeClass = $derived(cn(styles.close(), ''));
|
|
50
|
-
|
|
51
|
-
const descriptionClass = $derived(cn(styles.description(), ''));
|
|
44
|
+
const descriptionClasses = $derived(cn(styles.description(), ''));
|
|
52
45
|
|
|
53
46
|
function handleBackdropClick() {
|
|
54
47
|
onclose();
|
|
@@ -131,15 +124,24 @@
|
|
|
131
124
|
{#snippet predefinedHeader()}
|
|
132
125
|
{#if title || description}
|
|
133
126
|
<header class={headerClasses}>
|
|
134
|
-
<div class="flex-1">
|
|
127
|
+
<div class="flex min-w-0 flex-1 flex-col">
|
|
135
128
|
{#if title}
|
|
136
|
-
<
|
|
129
|
+
<h3
|
|
137
130
|
id="modal-title"
|
|
138
131
|
class={titleClasses}
|
|
139
132
|
data-testid={buildTestId('modal', 'title', testId)}
|
|
140
133
|
>
|
|
141
134
|
{title}
|
|
142
|
-
</
|
|
135
|
+
</h3>
|
|
136
|
+
{/if}
|
|
137
|
+
{#if description}
|
|
138
|
+
<p
|
|
139
|
+
id="modal-description"
|
|
140
|
+
class={descriptionClasses}
|
|
141
|
+
data-testid={buildTestId('modal', 'description', testId)}
|
|
142
|
+
>
|
|
143
|
+
{description}
|
|
144
|
+
</p>
|
|
143
145
|
{/if}
|
|
144
146
|
</div>
|
|
145
147
|
{#if !hideCloseButton}
|
|
@@ -190,6 +192,8 @@
|
|
|
190
192
|
role="dialog"
|
|
191
193
|
aria-modal="true"
|
|
192
194
|
aria-labelledby={title ? 'modal-title' : undefined}
|
|
195
|
+
aria-describedby={description ? 'modal-description' : undefined}
|
|
196
|
+
aria-label={!title ? 'Modal dialog' : undefined}
|
|
193
197
|
bind:this={modalElement}
|
|
194
198
|
>
|
|
195
199
|
<!-- Backdrop -->
|
|
@@ -12,10 +12,8 @@ import type { VariantSizes } from '../index.js';
|
|
|
12
12
|
* <Modal bind:open title="Edit profile">
|
|
13
13
|
* <ProfileForm />
|
|
14
14
|
* {#snippet footer()}
|
|
15
|
-
* <
|
|
16
|
-
*
|
|
17
|
-
* <Button onclick={save}>Save</Button>
|
|
18
|
-
* </ModalFooter>
|
|
15
|
+
* <Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
|
|
16
|
+
* <Button onclick={save}>Save</Button>
|
|
19
17
|
* {/snippet}
|
|
20
18
|
* </Modal>
|
|
21
19
|
* ```
|
|
@@ -65,8 +63,14 @@ export type ModalProps = {
|
|
|
65
63
|
footerClass?: ClassValue;
|
|
66
64
|
/** Body content. */
|
|
67
65
|
children?: Snippet;
|
|
68
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Footer content — typically action buttons. The footer wrapper renders
|
|
68
|
+
* with a tinted background, hairline separator, padding, and flex layout
|
|
69
|
+
* built-in, so the snippet can just contain `<Button>`s directly.
|
|
70
|
+
*/
|
|
69
71
|
footer?: Snippet;
|
|
72
|
+
/** How to align footer items horizontally. @default 'end' */
|
|
73
|
+
footerAlign?: 'start' | 'center' | 'end' | 'between';
|
|
70
74
|
/** Replace the default title bar entirely. */
|
|
71
75
|
header?: Snippet;
|
|
72
76
|
testId?: string;
|
package/dist/modal/modal.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
export declare const modal: import("tailwind-variants").TVReturnType<{
|
|
2
|
+
footerAlign: {
|
|
3
|
+
start: {
|
|
4
|
+
footer: string;
|
|
5
|
+
};
|
|
6
|
+
center: {
|
|
7
|
+
footer: string;
|
|
8
|
+
};
|
|
9
|
+
end: {
|
|
10
|
+
footer: string;
|
|
11
|
+
};
|
|
12
|
+
between: {
|
|
13
|
+
footer: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
2
16
|
size: {
|
|
3
17
|
xs: {
|
|
4
18
|
container: string;
|
|
@@ -30,6 +44,20 @@ export declare const modal: import("tailwind-variants").TVReturnType<{
|
|
|
30
44
|
description: string;
|
|
31
45
|
close: string;
|
|
32
46
|
}, undefined, {
|
|
47
|
+
footerAlign: {
|
|
48
|
+
start: {
|
|
49
|
+
footer: string;
|
|
50
|
+
};
|
|
51
|
+
center: {
|
|
52
|
+
footer: string;
|
|
53
|
+
};
|
|
54
|
+
end: {
|
|
55
|
+
footer: string;
|
|
56
|
+
};
|
|
57
|
+
between: {
|
|
58
|
+
footer: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
33
61
|
size: {
|
|
34
62
|
xs: {
|
|
35
63
|
container: string;
|
|
@@ -61,6 +89,20 @@ export declare const modal: import("tailwind-variants").TVReturnType<{
|
|
|
61
89
|
description: string;
|
|
62
90
|
close: string;
|
|
63
91
|
}, import("tailwind-variants").TVReturnType<{
|
|
92
|
+
footerAlign: {
|
|
93
|
+
start: {
|
|
94
|
+
footer: string;
|
|
95
|
+
};
|
|
96
|
+
center: {
|
|
97
|
+
footer: string;
|
|
98
|
+
};
|
|
99
|
+
end: {
|
|
100
|
+
footer: string;
|
|
101
|
+
};
|
|
102
|
+
between: {
|
|
103
|
+
footer: string;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
64
106
|
size: {
|
|
65
107
|
xs: {
|
|
66
108
|
container: string;
|
package/dist/modal/modal.js
CHANGED
|
@@ -4,15 +4,24 @@ export const modal = tv({
|
|
|
4
4
|
slots: {
|
|
5
5
|
base: 'fixed inset-0 z-50 flex items-center justify-center p-2 sm:p-4',
|
|
6
6
|
backdrop: 'fixed inset-0 bg-black/50 backdrop-blur-sm',
|
|
7
|
-
container: 'relative w-full flex flex-col bg-white rounded-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
container: 'relative w-full flex flex-col bg-white rounded-lg shadow-2xl max-sm:max-w-none max-sm:max-h-[95vh]',
|
|
8
|
+
// Header: matches Card chrome — translucent bg-default-100/50 tint + hairline
|
|
9
|
+
// separator. Padding sits between Card md and lg: dialogs deserve a touch
|
|
10
|
+
// more presence than inline panels but stay compact relative to legacy modal.
|
|
11
|
+
header: 'px-5 py-3 bg-default-100/50 border-b border-default-200 flex items-center justify-between shrink-0',
|
|
12
|
+
body: 'flex-1 p-5 overflow-y-auto text-sm text-default-700',
|
|
13
|
+
footer: 'bg-default-100/50 border-t border-default-200 rounded-b-lg shrink-0 flex flex-wrap items-center gap-3 px-5 py-3',
|
|
14
|
+
title: 'text-sm font-semibold text-default-900 leading-tight',
|
|
15
|
+
description: 'text-xs text-default-500 font-normal',
|
|
16
|
+
close: 'p-1.5 -mr-1.5 ml-4 rounded-md text-default-400 cursor-pointer hover:text-default-600 hover:bg-default-200/70 transition-colors'
|
|
14
17
|
},
|
|
15
18
|
variants: {
|
|
19
|
+
footerAlign: {
|
|
20
|
+
start: { footer: 'justify-start' },
|
|
21
|
+
center: { footer: 'justify-center' },
|
|
22
|
+
end: { footer: 'justify-end' },
|
|
23
|
+
between: { footer: 'justify-between' }
|
|
24
|
+
},
|
|
16
25
|
size: {
|
|
17
26
|
[Size.XS]: {
|
|
18
27
|
container: 'max-w-xs max-h-[80vh]'
|
|
@@ -35,6 +44,7 @@ export const modal = tv({
|
|
|
35
44
|
}
|
|
36
45
|
},
|
|
37
46
|
defaultVariants: {
|
|
38
|
-
size: Size.MD
|
|
47
|
+
size: Size.MD,
|
|
48
|
+
footerAlign: 'end'
|
|
39
49
|
}
|
|
40
50
|
});
|