@workspace-platform/ui 0.5.0 → 0.6.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/dist/admin.d.mts +23 -1
- package/dist/admin.d.ts +23 -1
- package/dist/admin.js +2385 -0
- package/dist/admin.js.map +1 -1
- package/dist/admin.mjs +1857 -0
- package/dist/admin.mjs.map +1 -1
- package/dist/chunk-4UDSYAAD.mjs +735 -0
- package/dist/chunk-4UDSYAAD.mjs.map +1 -0
- package/dist/index.mjs +1147 -1849
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
// src/api/client.ts
|
|
2
|
+
function createWorkspaceClient(config) {
|
|
3
|
+
function buildHeaders(extra) {
|
|
4
|
+
const base = {
|
|
5
|
+
"Content-Type": "application/json",
|
|
6
|
+
...extra
|
|
7
|
+
};
|
|
8
|
+
const token = config.getAuthToken();
|
|
9
|
+
if (token) base["Authorization"] = `Bearer ${token}`;
|
|
10
|
+
if (config.extraHeaders) Object.assign(base, config.extraHeaders);
|
|
11
|
+
return base;
|
|
12
|
+
}
|
|
13
|
+
async function req(path, options) {
|
|
14
|
+
const res = await fetch(`${config.baseUrl}${path}`, {
|
|
15
|
+
...options,
|
|
16
|
+
headers: buildHeaders(options?.headers)
|
|
17
|
+
});
|
|
18
|
+
if (res.status === 204) return void 0;
|
|
19
|
+
const json = await res.json();
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
const rawMessage = json?.message ?? `Error ${res.status}`;
|
|
22
|
+
const errorMessage = Array.isArray(rawMessage) ? rawMessage.join(", ") : typeof rawMessage === "string" ? rawMessage : JSON.stringify(rawMessage);
|
|
23
|
+
throw new Error(errorMessage);
|
|
24
|
+
}
|
|
25
|
+
return json;
|
|
26
|
+
}
|
|
27
|
+
async function reqBlob(path) {
|
|
28
|
+
const res = await fetch(`${config.baseUrl}${path}`, {
|
|
29
|
+
headers: buildHeaders()
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const text = await res.text().catch(() => "");
|
|
33
|
+
throw new Error(`Error ${res.status}: ${text}`);
|
|
34
|
+
}
|
|
35
|
+
return res.blob();
|
|
36
|
+
}
|
|
37
|
+
async function upload(path, formData) {
|
|
38
|
+
const headers = {};
|
|
39
|
+
const token = config.getAuthToken();
|
|
40
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
41
|
+
if (config.extraHeaders) Object.assign(headers, config.extraHeaders);
|
|
42
|
+
const res = await fetch(`${config.baseUrl}${path}`, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers,
|
|
45
|
+
body: formData
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const json = await res.json().catch(() => ({}));
|
|
49
|
+
throw new Error(
|
|
50
|
+
json?.message ?? `Upload failed (${res.status})`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return res.json();
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
get: (path) => req(path),
|
|
57
|
+
post: (path, body) => req(path, { method: "POST", body: JSON.stringify(body) }),
|
|
58
|
+
patch: (path, body) => req(path, { method: "PATCH", body: JSON.stringify(body) }),
|
|
59
|
+
delete: (path) => req(path, { method: "DELETE" }),
|
|
60
|
+
upload: (path, formData) => upload(path, formData),
|
|
61
|
+
getBlob: (path) => reqBlob(path)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/socket/client.ts
|
|
66
|
+
import { io } from "socket.io-client";
|
|
67
|
+
function createWorkspaceSocket(config) {
|
|
68
|
+
let socket = null;
|
|
69
|
+
function getSocket() {
|
|
70
|
+
if (!socket) {
|
|
71
|
+
const token = config.getAuthToken();
|
|
72
|
+
socket = io(`${config.baseUrl}/workspaces`, {
|
|
73
|
+
autoConnect: false,
|
|
74
|
+
auth: { token: token ?? "" }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return socket;
|
|
78
|
+
}
|
|
79
|
+
function connect(userId) {
|
|
80
|
+
const s = getSocket();
|
|
81
|
+
if (!s.connected) s.connect();
|
|
82
|
+
const uid = userId ?? config.currentUserId;
|
|
83
|
+
if (uid) {
|
|
84
|
+
if (s.connected) {
|
|
85
|
+
s.emit("authenticate", { userId: uid });
|
|
86
|
+
} else {
|
|
87
|
+
s.once("connect", () => {
|
|
88
|
+
s.emit("authenticate", { userId: uid });
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return s;
|
|
93
|
+
}
|
|
94
|
+
function disconnect() {
|
|
95
|
+
socket?.disconnect();
|
|
96
|
+
socket = null;
|
|
97
|
+
}
|
|
98
|
+
return { getSocket, connect, disconnect };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/theme.ts
|
|
102
|
+
var mix = (color, amount, other) => `color-mix(in srgb, ${color} ${amount}%, ${other})`;
|
|
103
|
+
function buildThemeVars(theme, scheme = "light") {
|
|
104
|
+
const vars = {};
|
|
105
|
+
if (!theme) return vars;
|
|
106
|
+
const dark = scheme === "dark";
|
|
107
|
+
const tintBase = dark ? "#0b1220" : "#fff";
|
|
108
|
+
const set = (name, value) => {
|
|
109
|
+
if (value != null && value !== "") vars[`--ws-${name}`] = value;
|
|
110
|
+
};
|
|
111
|
+
if (theme.primary) {
|
|
112
|
+
set("primary", theme.primary);
|
|
113
|
+
set("primary-hover", dark ? mix(theme.primary, 82, "#fff") : mix(theme.primary, 86, "#000"));
|
|
114
|
+
set("primary-light", mix(theme.primary, dark ? 26 : 12, tintBase));
|
|
115
|
+
set("primary-50", mix(theme.primary, dark ? 14 : 6, tintBase));
|
|
116
|
+
set("primary-100", mix(theme.primary, dark ? 20 : 14, tintBase));
|
|
117
|
+
set("primary-200", mix(theme.primary, dark ? 34 : 28, tintBase));
|
|
118
|
+
}
|
|
119
|
+
set("primary-contrast", theme.primaryContrast);
|
|
120
|
+
if (theme.secondary) {
|
|
121
|
+
set("secondary", theme.secondary);
|
|
122
|
+
set("secondary-hover", dark ? mix(theme.secondary, 82, "#fff") : mix(theme.secondary, 86, "#000"));
|
|
123
|
+
set("secondary-light", mix(theme.secondary, dark ? 26 : 12, tintBase));
|
|
124
|
+
}
|
|
125
|
+
set("secondary-contrast", theme.secondaryContrast);
|
|
126
|
+
set("bg", theme.bg);
|
|
127
|
+
set("bg-subtle", theme.bgSubtle);
|
|
128
|
+
set("surface", theme.surface);
|
|
129
|
+
set("surface-hover", theme.surfaceHover);
|
|
130
|
+
set("border", theme.border);
|
|
131
|
+
set("border-light", theme.borderLight);
|
|
132
|
+
set("text", theme.text);
|
|
133
|
+
set("text-secondary", theme.textSecondary);
|
|
134
|
+
set("text-muted", theme.textMuted);
|
|
135
|
+
set("text-placeholder", theme.textPlaceholder);
|
|
136
|
+
set("danger", theme.danger);
|
|
137
|
+
set("success", theme.success);
|
|
138
|
+
set("warning", theme.warning);
|
|
139
|
+
set("info", theme.info);
|
|
140
|
+
return vars;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/theme-context.tsx
|
|
144
|
+
import { createContext, useContext } from "react";
|
|
145
|
+
var EMPTY = { className: "", style: {} };
|
|
146
|
+
var WorkspaceThemeContext = createContext(EMPTY);
|
|
147
|
+
function useWorkspaceThemeAttrs() {
|
|
148
|
+
return useContext(WorkspaceThemeContext);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/styling.ts
|
|
152
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
153
|
+
import { clsx } from "clsx";
|
|
154
|
+
import { twMerge } from "tailwind-merge";
|
|
155
|
+
var EMPTY2 = {};
|
|
156
|
+
var WorkspaceStylesContext = createContext2(EMPTY2);
|
|
157
|
+
function useWorkspaceClassNames() {
|
|
158
|
+
return useContext2(WorkspaceStylesContext);
|
|
159
|
+
}
|
|
160
|
+
function pickSlot(override, slot) {
|
|
161
|
+
if (!override) return void 0;
|
|
162
|
+
if (typeof override === "string") return slot === "root" ? override : void 0;
|
|
163
|
+
return override[slot];
|
|
164
|
+
}
|
|
165
|
+
function useSlots(keys, local, className) {
|
|
166
|
+
const registry = useContext2(WorkspaceStylesContext);
|
|
167
|
+
const keyList = Array.isArray(keys) ? keys : [keys];
|
|
168
|
+
return (slot, ...base) => twMerge(
|
|
169
|
+
clsx(
|
|
170
|
+
...base,
|
|
171
|
+
...keyList.map((key) => pickSlot(registry[key], slot)),
|
|
172
|
+
pickSlot(local, slot),
|
|
173
|
+
slot === "root" ? className : void 0
|
|
174
|
+
)
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/components/ui/cn.ts
|
|
179
|
+
import { clsx as clsx2 } from "clsx";
|
|
180
|
+
import { twMerge as twMerge2 } from "tailwind-merge";
|
|
181
|
+
function cn(...inputs) {
|
|
182
|
+
return twMerge2(clsx2(inputs));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/provider.tsx
|
|
186
|
+
import { createContext as createContext3, useContext as useContext3, useMemo } from "react";
|
|
187
|
+
import { jsx } from "react/jsx-runtime";
|
|
188
|
+
var WorkspaceContext = createContext3(null);
|
|
189
|
+
var EMPTY_CLASS_NAMES = {};
|
|
190
|
+
function WorkspaceProvider({
|
|
191
|
+
config,
|
|
192
|
+
children
|
|
193
|
+
}) {
|
|
194
|
+
const value = useMemo(() => {
|
|
195
|
+
const apiClient = createWorkspaceClient(config);
|
|
196
|
+
const socket = createWorkspaceSocket(config);
|
|
197
|
+
return { config, apiClient, socket };
|
|
198
|
+
}, [config.baseUrl, config.currentUserId]);
|
|
199
|
+
const isDark = config.colorScheme === "dark";
|
|
200
|
+
const themeVars = useMemo(
|
|
201
|
+
() => buildThemeVars(config.theme, config.colorScheme),
|
|
202
|
+
[config.theme, config.colorScheme]
|
|
203
|
+
);
|
|
204
|
+
const themeClassName = cn("ws-root", isDark && "ws-theme-dark");
|
|
205
|
+
const themeAttrs = useMemo(
|
|
206
|
+
() => ({ className: themeClassName, style: themeVars }),
|
|
207
|
+
[themeClassName, themeVars]
|
|
208
|
+
);
|
|
209
|
+
const classNames = config.classNames ?? EMPTY_CLASS_NAMES;
|
|
210
|
+
return /* @__PURE__ */ jsx(WorkspaceContext.Provider, { value, children: /* @__PURE__ */ jsx(WorkspaceStylesContext.Provider, { value: classNames, children: /* @__PURE__ */ jsx(WorkspaceThemeContext.Provider, { value: themeAttrs, children: /* @__PURE__ */ jsx(
|
|
211
|
+
"div",
|
|
212
|
+
{
|
|
213
|
+
className: themeClassName,
|
|
214
|
+
style: { display: "contents", ...themeVars },
|
|
215
|
+
children
|
|
216
|
+
}
|
|
217
|
+
) }) }) });
|
|
218
|
+
}
|
|
219
|
+
function useWorkspaceContext() {
|
|
220
|
+
const ctx = useContext3(WorkspaceContext);
|
|
221
|
+
if (!ctx)
|
|
222
|
+
throw new Error(
|
|
223
|
+
"useWorkspaceContext must be used within <WorkspaceProvider>"
|
|
224
|
+
);
|
|
225
|
+
return ctx;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/api/index.ts
|
|
229
|
+
function createWorkspaceApis(client) {
|
|
230
|
+
const workspaceTypeApi = {
|
|
231
|
+
list: () => client.get("/workspace-types"),
|
|
232
|
+
get: (id) => client.get(`/workspace-types/${id}`),
|
|
233
|
+
create: (payload) => client.post("/workspace-types", payload),
|
|
234
|
+
update: (id, payload) => client.patch(`/workspace-types/${id}`, payload),
|
|
235
|
+
delete: (id) => client.delete(`/workspace-types/${id}`)
|
|
236
|
+
};
|
|
237
|
+
const workspaceApi = {
|
|
238
|
+
list: (params) => {
|
|
239
|
+
const cleanParams = Object.fromEntries(
|
|
240
|
+
Object.entries(params || {}).filter(([, v]) => v != null && v !== "" && v !== "all").map(([k, v]) => [k, String(v)])
|
|
241
|
+
);
|
|
242
|
+
const qs = Object.keys(cleanParams).length > 0 ? "?" + new URLSearchParams(cleanParams).toString() : "";
|
|
243
|
+
return client.get(`/workspaces${qs}`);
|
|
244
|
+
},
|
|
245
|
+
get: (id) => client.get(`/workspaces/${id}`),
|
|
246
|
+
create: (payload) => client.post("/workspaces", payload),
|
|
247
|
+
update: (id, payload) => client.patch(`/workspaces/${id}`, payload),
|
|
248
|
+
updateStatus: (id, status) => client.patch(`/workspaces/${id}/status`, { status }),
|
|
249
|
+
delete: (id) => client.delete(`/workspaces/${id}`),
|
|
250
|
+
startTyping: (id) => client.post(`/workspaces/${id}/writing/start`, {}),
|
|
251
|
+
stopTyping: (id) => client.post(`/workspaces/${id}/writing/stop`, {}),
|
|
252
|
+
timeline: (workspaceId, params) => {
|
|
253
|
+
const qs = params ? "?" + new URLSearchParams(
|
|
254
|
+
Object.fromEntries(
|
|
255
|
+
Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])
|
|
256
|
+
)
|
|
257
|
+
).toString() : "";
|
|
258
|
+
return client.get(`/workspaces/${workspaceId}/timeline${qs}`);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
const memberApi = {
|
|
262
|
+
list: (workspaceId) => client.get(`/workspaces/${workspaceId}/members`),
|
|
263
|
+
add: (workspaceId, payload) => client.post(`/workspaces/${workspaceId}/members`, payload),
|
|
264
|
+
updateRole: (workspaceId, memberId, role) => client.patch(`/workspaces/${workspaceId}/members/${memberId}`, { role }),
|
|
265
|
+
remove: (workspaceId, memberId) => client.delete(`/workspaces/${workspaceId}/members/${memberId}`)
|
|
266
|
+
};
|
|
267
|
+
const messageApi = {
|
|
268
|
+
list: (workspaceId, params) => {
|
|
269
|
+
const qs = params ? "?" + new URLSearchParams(
|
|
270
|
+
Object.fromEntries(
|
|
271
|
+
Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])
|
|
272
|
+
)
|
|
273
|
+
).toString() : "";
|
|
274
|
+
return client.get(`/workspaces/${workspaceId}/messages${qs}`);
|
|
275
|
+
},
|
|
276
|
+
send: (workspaceId, payload) => client.post(`/workspaces/${workspaceId}/messages`, payload),
|
|
277
|
+
update: (workspaceId, messageId, payload) => client.patch(`/workspaces/${workspaceId}/messages/${messageId}`, payload),
|
|
278
|
+
delete: (workspaceId, messageId) => client.delete(`/workspaces/${workspaceId}/messages/${messageId}`),
|
|
279
|
+
togglePin: (workspaceId, messageId) => client.patch(`/workspaces/${workspaceId}/messages/${messageId}/pin`, {})
|
|
280
|
+
};
|
|
281
|
+
const artefactApi = {
|
|
282
|
+
upload: async (workspaceId, files, messageId, artefactType) => {
|
|
283
|
+
const formData = new FormData();
|
|
284
|
+
files.forEach((f) => formData.append("files", f));
|
|
285
|
+
if (messageId) formData.append("messageId", messageId);
|
|
286
|
+
if (artefactType) formData.append("type", artefactType);
|
|
287
|
+
return client.upload(`/workspaces/${workspaceId}/artefacts`, formData);
|
|
288
|
+
},
|
|
289
|
+
list: (workspaceId, artefactType) => {
|
|
290
|
+
const query = artefactType ? `?type=${artefactType}` : "";
|
|
291
|
+
return client.get(
|
|
292
|
+
`/workspaces/${workspaceId}/artefacts${query}`
|
|
293
|
+
);
|
|
294
|
+
},
|
|
295
|
+
listPaginated: (workspaceId, params) => {
|
|
296
|
+
const entries = [];
|
|
297
|
+
if (params) {
|
|
298
|
+
if (params.type) entries.push(["type", params.type]);
|
|
299
|
+
if (params.limit != null) entries.push(["limit", String(params.limit)]);
|
|
300
|
+
if (params.offset != null) entries.push(["offset", String(params.offset)]);
|
|
301
|
+
}
|
|
302
|
+
const qs = entries.length > 0 ? "?" + new URLSearchParams(entries).toString() : "";
|
|
303
|
+
return client.get(
|
|
304
|
+
`/workspaces/${workspaceId}/artefacts${qs}`
|
|
305
|
+
);
|
|
306
|
+
},
|
|
307
|
+
delete: (workspaceId, artefactId) => client.delete(`/workspaces/${workspaceId}/artefacts/${artefactId}`),
|
|
308
|
+
getSignedUrl: (fileId) => client.get(`/files/${fileId}/signed-url`),
|
|
309
|
+
getDownloadBlob: (fileId) => client.getBlob(`/files/${fileId}/download`),
|
|
310
|
+
messagesWithArtefacts: (workspaceId, params) => {
|
|
311
|
+
const entries = [];
|
|
312
|
+
if (params) {
|
|
313
|
+
if (params.limit != null) entries.push(["limit", String(params.limit)]);
|
|
314
|
+
if (params.offset != null) entries.push(["offset", String(params.offset)]);
|
|
315
|
+
if (params.sort) entries.push(["sort", params.sort]);
|
|
316
|
+
if (params.members && params.members.length > 0)
|
|
317
|
+
entries.push(["members", params.members.join(",")]);
|
|
318
|
+
if (params.concernsMe) entries.push(["concernsMe", "true"]);
|
|
319
|
+
if (params.pinned) entries.push(["pinned", "true"]);
|
|
320
|
+
}
|
|
321
|
+
const qs = entries.length > 0 ? "?" + new URLSearchParams(entries).toString() : "";
|
|
322
|
+
return client.get(
|
|
323
|
+
`/workspaces/${workspaceId}/messages/with-artefacts${qs}`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
const reactionApi = {
|
|
328
|
+
toggle: (workspaceId, messageId, reaction) => client.post(
|
|
329
|
+
`/workspaces/${workspaceId}/messages/${messageId}/reactions/toggle`,
|
|
330
|
+
{ reaction }
|
|
331
|
+
)
|
|
332
|
+
};
|
|
333
|
+
const agentApi = {
|
|
334
|
+
list: (search) => {
|
|
335
|
+
const qs = search ? `?search=${encodeURIComponent(search)}` : "";
|
|
336
|
+
return client.get(`/workspace-agents${qs}`);
|
|
337
|
+
},
|
|
338
|
+
create: (body) => client.post("/workspace-agents", body),
|
|
339
|
+
update: (id, body) => client.patch(`/workspace-agents/${id}`, body),
|
|
340
|
+
delete: (id) => client.delete(`/workspace-agents/${id}`),
|
|
341
|
+
getDeleteInfo: (id) => client.get(`/workspace-agents/${id}/delete-info`)
|
|
342
|
+
};
|
|
343
|
+
const summaryApi = {
|
|
344
|
+
generate: (workspaceId) => client.post(`/workspaces/${workspaceId}/summaries/generate`, {}),
|
|
345
|
+
getLatest: (workspaceId) => client.get(`/workspaces/${workspaceId}/summaries/latest`),
|
|
346
|
+
getHistory: (workspaceId) => client.get(`/workspaces/${workspaceId}/summaries`),
|
|
347
|
+
create: (workspaceId, payload) => client.post(`/workspaces/${workspaceId}/summaries`, payload),
|
|
348
|
+
update: (workspaceId, summaryId, payload) => client.patch(
|
|
349
|
+
`/workspaces/${workspaceId}/summaries/${summaryId}`,
|
|
350
|
+
payload
|
|
351
|
+
)
|
|
352
|
+
};
|
|
353
|
+
const webhookApi = {
|
|
354
|
+
list: (params) => {
|
|
355
|
+
const cleanParams = Object.fromEntries(
|
|
356
|
+
Object.entries(params || {}).filter(([, v]) => v !== void 0 && v !== "")
|
|
357
|
+
);
|
|
358
|
+
const qs = Object.keys(cleanParams).length > 0 ? "?" + new URLSearchParams(cleanParams).toString() : "";
|
|
359
|
+
return client.get(`/webhook-subscriptions${qs}`);
|
|
360
|
+
},
|
|
361
|
+
create: (data) => client.post("/webhook-subscriptions", data),
|
|
362
|
+
update: (id, data) => client.patch(`/webhook-subscriptions/${id}`, data),
|
|
363
|
+
delete: (id) => client.delete(`/webhook-subscriptions/${id}`)
|
|
364
|
+
};
|
|
365
|
+
const syncApi = {
|
|
366
|
+
getAvailableProviders: () => client.get("/workspaces/sync/providers").then((res) => res.providers),
|
|
367
|
+
getStatus: (workspaceId) => client.get(`/workspaces/${workspaceId}/sync-status`),
|
|
368
|
+
toggleSync: (workspaceId, provider, enabled) => client.patch(`/workspaces/${workspaceId}/sync`, { provider, enabled })
|
|
369
|
+
};
|
|
370
|
+
return {
|
|
371
|
+
workspaceTypeApi,
|
|
372
|
+
workspaceApi,
|
|
373
|
+
memberApi,
|
|
374
|
+
messageApi,
|
|
375
|
+
artefactApi,
|
|
376
|
+
reactionApi,
|
|
377
|
+
agentApi,
|
|
378
|
+
summaryApi,
|
|
379
|
+
webhookApi,
|
|
380
|
+
syncApi
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/hooks/useWorkspaceApi.ts
|
|
385
|
+
import { useMemo as useMemo2 } from "react";
|
|
386
|
+
function useWorkspaceApi() {
|
|
387
|
+
const { apiClient } = useWorkspaceContext();
|
|
388
|
+
return useMemo2(() => createWorkspaceApis(apiClient), [apiClient]);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/components/ui/Modal.tsx
|
|
392
|
+
import { useEffect, useId, useState } from "react";
|
|
393
|
+
import { createPortal } from "react-dom";
|
|
394
|
+
import { X } from "lucide-react";
|
|
395
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
396
|
+
var MODAL_SIZE_CLASS = {
|
|
397
|
+
sm: "w-full min-w-0 max-w-sm",
|
|
398
|
+
md: "w-full min-w-0 max-w-md",
|
|
399
|
+
lg: "w-full min-w-0 max-w-lg",
|
|
400
|
+
xl: "w-full min-w-0 max-w-xl",
|
|
401
|
+
"2xl": "w-full min-w-0 max-w-2xl",
|
|
402
|
+
"3xl": "w-full min-w-0 max-w-3xl",
|
|
403
|
+
"4xl": "w-full min-w-0 max-w-4xl",
|
|
404
|
+
"5xl": "w-full min-w-0 max-w-5xl",
|
|
405
|
+
"6xl": "w-full min-w-0 max-w-6xl",
|
|
406
|
+
"7xl": "w-full min-w-0 max-w-7xl",
|
|
407
|
+
full: "w-full min-w-0 max-w-[calc(100vw-2rem)] sm:max-w-[min(96vw,90rem)]"
|
|
408
|
+
};
|
|
409
|
+
function Modal({
|
|
410
|
+
open,
|
|
411
|
+
onClose,
|
|
412
|
+
children,
|
|
413
|
+
zIndexClass = "z-[100]",
|
|
414
|
+
backdropClassName = "bg-black/40 backdrop-blur-sm",
|
|
415
|
+
size,
|
|
416
|
+
panelClassName,
|
|
417
|
+
className,
|
|
418
|
+
closeOnBackdropClick = false,
|
|
419
|
+
closeOnEscape = true,
|
|
420
|
+
ariaLabelledBy: ariaLabelledByProp,
|
|
421
|
+
ariaDescribedBy: ariaDescribedByProp,
|
|
422
|
+
title,
|
|
423
|
+
description,
|
|
424
|
+
titleIcon,
|
|
425
|
+
headingId,
|
|
426
|
+
modalHeaderVariant = "strip",
|
|
427
|
+
hideCloseButton = false,
|
|
428
|
+
headerTrailing,
|
|
429
|
+
closeButtonAriaLabel = "Fermer",
|
|
430
|
+
closeButtonDisabled = false,
|
|
431
|
+
closeButtonClassName,
|
|
432
|
+
closeIconSize = 20,
|
|
433
|
+
headerClassName,
|
|
434
|
+
titleClassName,
|
|
435
|
+
descriptionClassName = "text-text-secondary mt-1 text-sm",
|
|
436
|
+
classNames
|
|
437
|
+
}) {
|
|
438
|
+
const slot = useSlots("modal", classNames, className);
|
|
439
|
+
const [mounted, setMounted] = useState(false);
|
|
440
|
+
const autoHeadingId = useId();
|
|
441
|
+
const autoDescIdByModal = useId();
|
|
442
|
+
const themeAttrs = useWorkspaceThemeAttrs();
|
|
443
|
+
const hasHeader = title != null;
|
|
444
|
+
const resolvedHeadingId = headingId ?? ariaLabelledByProp ?? autoHeadingId;
|
|
445
|
+
const assignAutoDescriptionId = hasHeader && description != null && ariaDescribedByProp === void 0 && (typeof description === "string" || typeof description === "number");
|
|
446
|
+
const resolvedAriaDescribedBy = assignAutoDescriptionId ? autoDescIdByModal : ariaDescribedByProp;
|
|
447
|
+
const resolvedAriaLabelledBy = hasHeader ? ariaLabelledByProp ?? resolvedHeadingId : ariaLabelledByProp;
|
|
448
|
+
useEffect(() => {
|
|
449
|
+
setMounted(true);
|
|
450
|
+
}, []);
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
if (!open) return;
|
|
453
|
+
const prevOverflow = document.body.style.overflow;
|
|
454
|
+
document.body.style.overflow = "hidden";
|
|
455
|
+
return () => {
|
|
456
|
+
document.body.style.overflow = prevOverflow;
|
|
457
|
+
};
|
|
458
|
+
}, [open]);
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
if (!open || !closeOnEscape) return;
|
|
461
|
+
const onKeyDown = (e) => {
|
|
462
|
+
if (e.key === "Escape") onClose();
|
|
463
|
+
};
|
|
464
|
+
window.addEventListener("keydown", onKeyDown);
|
|
465
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
466
|
+
}, [open, closeOnEscape, onClose]);
|
|
467
|
+
const defaultCloseClasses = closeButtonClassName ?? "text-text-secondary hover:text-text rounded p-1 transition-colors";
|
|
468
|
+
const trailing = headerTrailing ?? (!hideCloseButton ? /* @__PURE__ */ jsx2(
|
|
469
|
+
"button",
|
|
470
|
+
{
|
|
471
|
+
type: "button",
|
|
472
|
+
disabled: closeButtonDisabled,
|
|
473
|
+
onClick: onClose,
|
|
474
|
+
className: slot(
|
|
475
|
+
"close",
|
|
476
|
+
"ws-modal__close",
|
|
477
|
+
defaultCloseClasses,
|
|
478
|
+
closeButtonDisabled && "opacity-40"
|
|
479
|
+
),
|
|
480
|
+
"aria-label": closeButtonAriaLabel,
|
|
481
|
+
children: /* @__PURE__ */ jsx2(X, { size: closeIconSize })
|
|
482
|
+
}
|
|
483
|
+
) : null);
|
|
484
|
+
const descriptionSlot = description == null ? null : typeof description === "string" || typeof description === "number" ? /* @__PURE__ */ jsx2(
|
|
485
|
+
"p",
|
|
486
|
+
{
|
|
487
|
+
id: assignAutoDescriptionId ? autoDescIdByModal : void 0,
|
|
488
|
+
className: slot("description", "ws-modal__description", descriptionClassName),
|
|
489
|
+
children: description
|
|
490
|
+
}
|
|
491
|
+
) : description;
|
|
492
|
+
const headerShellClass = modalHeaderVariant === "strip" ? "border-border flex shrink-0 items-start justify-between gap-3 border-b px-6 py-4" : "mb-4 flex shrink-0 items-start justify-between gap-3";
|
|
493
|
+
const modalHeaderMarkup = hasHeader ? /* @__PURE__ */ jsxs("div", { className: slot("header", "ws-modal__header", headerShellClass, headerClassName), children: [
|
|
494
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-start gap-3", children: [
|
|
495
|
+
titleIcon != null ? /* @__PURE__ */ jsx2("div", { className: "mt-0.5 shrink-0", children: titleIcon }) : null,
|
|
496
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
497
|
+
/* @__PURE__ */ jsx2(
|
|
498
|
+
"h2",
|
|
499
|
+
{
|
|
500
|
+
id: resolvedHeadingId,
|
|
501
|
+
className: slot(
|
|
502
|
+
"title",
|
|
503
|
+
"ws-modal__title",
|
|
504
|
+
"text-text text-lg font-semibold",
|
|
505
|
+
titleClassName
|
|
506
|
+
),
|
|
507
|
+
children: title
|
|
508
|
+
}
|
|
509
|
+
),
|
|
510
|
+
descriptionSlot
|
|
511
|
+
] })
|
|
512
|
+
] }),
|
|
513
|
+
trailing ? /* @__PURE__ */ jsx2("div", { className: "flex shrink-0 items-start", children: trailing }) : null
|
|
514
|
+
] }) : null;
|
|
515
|
+
if (!mounted || !open) return null;
|
|
516
|
+
return createPortal(
|
|
517
|
+
/* @__PURE__ */ jsx2(
|
|
518
|
+
"div",
|
|
519
|
+
{
|
|
520
|
+
className: slot(
|
|
521
|
+
"root",
|
|
522
|
+
"ws-modal",
|
|
523
|
+
"fixed inset-0 flex items-center justify-center p-4",
|
|
524
|
+
themeAttrs.className,
|
|
525
|
+
zIndexClass,
|
|
526
|
+
backdropClassName
|
|
527
|
+
),
|
|
528
|
+
style: themeAttrs.style,
|
|
529
|
+
onClick: closeOnBackdropClick ? onClose : void 0,
|
|
530
|
+
"aria-hidden": false,
|
|
531
|
+
children: /* @__PURE__ */ jsxs(
|
|
532
|
+
"div",
|
|
533
|
+
{
|
|
534
|
+
role: "dialog",
|
|
535
|
+
"aria-modal": "true",
|
|
536
|
+
"aria-labelledby": resolvedAriaLabelledBy,
|
|
537
|
+
"aria-describedby": resolvedAriaDescribedBy,
|
|
538
|
+
className: slot(
|
|
539
|
+
"panel",
|
|
540
|
+
"ws-modal__panel",
|
|
541
|
+
// Solid, opaque panel by default — guarantees the modal is never
|
|
542
|
+
// transparent even if the caller forgets to pass `panelClassName`.
|
|
543
|
+
"bg-surface text-text border-border relative flex max-h-[calc(100vh-2rem)] w-full min-w-0 flex-col overflow-hidden rounded-2xl border shadow-panel animate-ws-scale-in",
|
|
544
|
+
size ? MODAL_SIZE_CLASS[size] : void 0,
|
|
545
|
+
panelClassName
|
|
546
|
+
),
|
|
547
|
+
onClick: (e) => e.stopPropagation(),
|
|
548
|
+
children: [
|
|
549
|
+
modalHeaderMarkup,
|
|
550
|
+
children
|
|
551
|
+
]
|
|
552
|
+
}
|
|
553
|
+
)
|
|
554
|
+
}
|
|
555
|
+
),
|
|
556
|
+
document.body
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// src/components/ui/Button.tsx
|
|
561
|
+
import React4 from "react";
|
|
562
|
+
import { Loader2 } from "lucide-react";
|
|
563
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
564
|
+
var VARIANT_CLASSES = {
|
|
565
|
+
primary: "bg-primary text-primary-contrast hover:bg-primary-hover border border-transparent shadow-xs",
|
|
566
|
+
secondary: "bg-secondary text-secondary-contrast hover:bg-secondary-hover border border-transparent shadow-xs",
|
|
567
|
+
outline: "bg-surface text-text border border-border hover:bg-bg-subtle hover:border-text-muted/40",
|
|
568
|
+
ghost: "bg-transparent text-text-secondary hover:bg-bg-subtle hover:text-text border border-transparent",
|
|
569
|
+
danger: "bg-danger text-white hover:brightness-95 border border-transparent shadow-xs",
|
|
570
|
+
subtle: "bg-primary-light text-primary hover:bg-primary-light/70 border border-transparent"
|
|
571
|
+
};
|
|
572
|
+
var SIZE_CLASSES = {
|
|
573
|
+
xs: "h-7 gap-1 px-2.5 text-xs rounded-lg",
|
|
574
|
+
sm: "h-9 gap-1.5 px-3.5 text-sm rounded-lg",
|
|
575
|
+
md: "h-10 gap-2 px-4 text-sm rounded-xl",
|
|
576
|
+
lg: "h-11 gap-2 px-5 text-[15px] rounded-xl"
|
|
577
|
+
};
|
|
578
|
+
var Button = React4.forwardRef(
|
|
579
|
+
({
|
|
580
|
+
variant = "primary",
|
|
581
|
+
size = "md",
|
|
582
|
+
loading = false,
|
|
583
|
+
fullWidth = false,
|
|
584
|
+
leftIcon,
|
|
585
|
+
rightIcon,
|
|
586
|
+
className,
|
|
587
|
+
children,
|
|
588
|
+
disabled,
|
|
589
|
+
type = "button",
|
|
590
|
+
...props
|
|
591
|
+
}, ref) => {
|
|
592
|
+
const isDisabled = disabled || loading;
|
|
593
|
+
const slot = useSlots("button", void 0, className);
|
|
594
|
+
return /* @__PURE__ */ jsxs2(
|
|
595
|
+
"button",
|
|
596
|
+
{
|
|
597
|
+
ref,
|
|
598
|
+
type,
|
|
599
|
+
disabled: isDisabled,
|
|
600
|
+
"data-variant": variant,
|
|
601
|
+
"data-size": size,
|
|
602
|
+
className: slot(
|
|
603
|
+
"root",
|
|
604
|
+
"ws-button",
|
|
605
|
+
`ws-button--${variant}`,
|
|
606
|
+
`ws-button--${size}`,
|
|
607
|
+
"inline-flex items-center justify-center font-medium whitespace-nowrap transition-all duration-150",
|
|
608
|
+
"focus-visible:ring-primary/40 focus-visible:ring-2 focus-visible:outline-none",
|
|
609
|
+
"active:scale-[0.98]",
|
|
610
|
+
"disabled:pointer-events-none disabled:opacity-55",
|
|
611
|
+
VARIANT_CLASSES[variant],
|
|
612
|
+
SIZE_CLASSES[size],
|
|
613
|
+
fullWidth && "w-full"
|
|
614
|
+
),
|
|
615
|
+
...props,
|
|
616
|
+
children: [
|
|
617
|
+
loading ? /* @__PURE__ */ jsx3(Loader2, { className: "animate-spin", size: size === "lg" ? 18 : 16 }) : leftIcon,
|
|
618
|
+
children,
|
|
619
|
+
!loading && rightIcon
|
|
620
|
+
]
|
|
621
|
+
}
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
);
|
|
625
|
+
Button.displayName = "Button";
|
|
626
|
+
|
|
627
|
+
// src/components/ui/ConfirmModal.tsx
|
|
628
|
+
import React5 from "react";
|
|
629
|
+
import { AlertTriangle } from "lucide-react";
|
|
630
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
631
|
+
var ConfirmModal = ({
|
|
632
|
+
open,
|
|
633
|
+
onClose,
|
|
634
|
+
onConfirm,
|
|
635
|
+
title,
|
|
636
|
+
description,
|
|
637
|
+
confirmText = "Confirmer",
|
|
638
|
+
cancelText = "Annuler",
|
|
639
|
+
danger = false,
|
|
640
|
+
className,
|
|
641
|
+
classNames
|
|
642
|
+
}) => {
|
|
643
|
+
const [loading, setLoading] = React5.useState(false);
|
|
644
|
+
const handleConfirm = async () => {
|
|
645
|
+
setLoading(true);
|
|
646
|
+
try {
|
|
647
|
+
await onConfirm();
|
|
648
|
+
} finally {
|
|
649
|
+
setLoading(false);
|
|
650
|
+
onClose();
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
return /* @__PURE__ */ jsx4(
|
|
654
|
+
Modal,
|
|
655
|
+
{
|
|
656
|
+
open,
|
|
657
|
+
onClose: () => {
|
|
658
|
+
if (!loading) onClose();
|
|
659
|
+
},
|
|
660
|
+
size: "sm",
|
|
661
|
+
closeOnBackdropClick: !loading,
|
|
662
|
+
closeOnEscape: !loading,
|
|
663
|
+
hideCloseButton: true,
|
|
664
|
+
zIndexClass: "z-[120]",
|
|
665
|
+
className: cn("ws-confirm-modal", className),
|
|
666
|
+
classNames,
|
|
667
|
+
children: /* @__PURE__ */ jsxs3("div", { className: "ws-confirm-modal__body flex flex-col gap-5 p-6", children: [
|
|
668
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-start gap-3.5", children: [
|
|
669
|
+
/* @__PURE__ */ jsx4(
|
|
670
|
+
"div",
|
|
671
|
+
{
|
|
672
|
+
className: cn(
|
|
673
|
+
"flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full",
|
|
674
|
+
danger ? "bg-danger-light" : "bg-primary-light"
|
|
675
|
+
),
|
|
676
|
+
children: /* @__PURE__ */ jsx4(
|
|
677
|
+
AlertTriangle,
|
|
678
|
+
{
|
|
679
|
+
size: 20,
|
|
680
|
+
className: danger ? "text-danger" : "text-primary"
|
|
681
|
+
}
|
|
682
|
+
)
|
|
683
|
+
}
|
|
684
|
+
),
|
|
685
|
+
/* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1 pt-0.5", children: [
|
|
686
|
+
/* @__PURE__ */ jsx4("h3", { className: "text-text text-base font-semibold", children: title }),
|
|
687
|
+
description && /* @__PURE__ */ jsx4("div", { className: "text-text-secondary mt-1.5 text-sm leading-relaxed", children: description })
|
|
688
|
+
] })
|
|
689
|
+
] }),
|
|
690
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-2.5", children: [
|
|
691
|
+
/* @__PURE__ */ jsx4(
|
|
692
|
+
Button,
|
|
693
|
+
{
|
|
694
|
+
variant: "outline",
|
|
695
|
+
size: "sm",
|
|
696
|
+
onClick: onClose,
|
|
697
|
+
disabled: loading,
|
|
698
|
+
children: cancelText
|
|
699
|
+
}
|
|
700
|
+
),
|
|
701
|
+
/* @__PURE__ */ jsx4(
|
|
702
|
+
Button,
|
|
703
|
+
{
|
|
704
|
+
variant: danger ? "danger" : "primary",
|
|
705
|
+
size: "sm",
|
|
706
|
+
loading,
|
|
707
|
+
onClick: handleConfirm,
|
|
708
|
+
children: confirmText
|
|
709
|
+
}
|
|
710
|
+
)
|
|
711
|
+
] })
|
|
712
|
+
] })
|
|
713
|
+
}
|
|
714
|
+
);
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
export {
|
|
718
|
+
createWorkspaceClient,
|
|
719
|
+
createWorkspaceSocket,
|
|
720
|
+
buildThemeVars,
|
|
721
|
+
WorkspaceThemeContext,
|
|
722
|
+
useWorkspaceThemeAttrs,
|
|
723
|
+
WorkspaceStylesContext,
|
|
724
|
+
useWorkspaceClassNames,
|
|
725
|
+
useSlots,
|
|
726
|
+
cn,
|
|
727
|
+
WorkspaceProvider,
|
|
728
|
+
useWorkspaceContext,
|
|
729
|
+
createWorkspaceApis,
|
|
730
|
+
useWorkspaceApi,
|
|
731
|
+
Modal,
|
|
732
|
+
Button,
|
|
733
|
+
ConfirmModal
|
|
734
|
+
};
|
|
735
|
+
//# sourceMappingURL=chunk-4UDSYAAD.mjs.map
|