@sentroy-co/client-sdk 2.2.1 → 2.3.2
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 +117 -1
- package/dist/http.d.ts +13 -2
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +22 -6
- package/dist/http.js.map +1 -1
- package/dist/react/MediaManager.d.ts +13 -1
- package/dist/react/MediaManager.d.ts.map +1 -1
- package/dist/react/MediaManager.js +14 -5
- package/dist/react/MediaManager.js.map +1 -1
- package/dist/react/MediaManagerTrigger.d.ts +63 -0
- package/dist/react/MediaManagerTrigger.d.ts.map +1 -0
- package/dist/react/MediaManagerTrigger.js +81 -0
- package/dist/react/MediaManagerTrigger.js.map +1 -0
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +4 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/lib/utils.d.ts +14 -0
- package/dist/react/lib/utils.d.ts.map +1 -1
- package/dist/react/lib/utils.js +36 -0
- package/dist/react/lib/utils.js.map +1 -1
- package/dist/types.d.ts +10 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/http.ts +26 -8
- package/src/react/MediaManager.tsx +28 -5
- package/src/react/MediaManagerTrigger.tsx +260 -0
- package/src/react/index.ts +5 -0
- package/src/react/lib/utils.ts +34 -0
- package/src/types.ts +10 -2
|
@@ -14,4 +14,18 @@ export declare function detectKind(file: {
|
|
|
14
14
|
}): MediaKind;
|
|
15
15
|
export type MediaKind = "image" | "video" | "audio" | "pdf" | "doc" | "archive" | "code" | "other";
|
|
16
16
|
export declare const KIND_LABELS: Record<MediaKind, string>;
|
|
17
|
+
/**
|
|
18
|
+
* `<input accept="...">` semantics — pattern listesi virgülle ayrılır,
|
|
19
|
+
* her parça:
|
|
20
|
+
* - `image/*` → MIME wildcard (image/png, image/jpeg ✓)
|
|
21
|
+
* - `image/png` → MIME exact match
|
|
22
|
+
* - `.png` → file extension (case-insensitive)
|
|
23
|
+
*
|
|
24
|
+
* Hiçbir parça eşleşmezse false. Accept boş/undefined olursa caller
|
|
25
|
+
* filter'ı atlamalı — bu fonksiyon o yüzden boş string'i false döner.
|
|
26
|
+
*/
|
|
27
|
+
export declare function matchAccept(file: {
|
|
28
|
+
mimeType?: string | null;
|
|
29
|
+
fileName?: string;
|
|
30
|
+
}, accept: string): boolean;
|
|
17
31
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/react/lib/utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,EAAE,CAChB,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,GACpD,MAAM,CAER;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CAU/D;AAED;0DAC0D;AAC1D,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,GAAG,SAAS,CAsBZ;AAED,MAAM,MAAM,SAAS,GACjB,OAAO,GACP,OAAO,GACP,OAAO,GACP,KAAK,GACL,KAAK,GACL,SAAS,GACT,MAAM,GACN,OAAO,CAAA;AAEX,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CASjD,CAAA"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/react/lib/utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,EAAE,CAChB,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,GACpD,MAAM,CAER;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CAU/D;AAED;0DAC0D;AAC1D,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,GAAG,SAAS,CAsBZ;AAED,MAAM,MAAM,SAAS,GACjB,OAAO,GACP,OAAO,GACP,OAAO,GACP,KAAK,GACL,KAAK,GACL,SAAS,GACT,MAAM,GACN,OAAO,CAAA;AAEX,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CASjD,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EACrD,MAAM,EAAE,MAAM,GACb,OAAO,CAmBT"}
|
package/dist/react/lib/utils.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.KIND_LABELS = void 0;
|
|
|
4
4
|
exports.cn = cn;
|
|
5
5
|
exports.formatBytes = formatBytes;
|
|
6
6
|
exports.detectKind = detectKind;
|
|
7
|
+
exports.matchAccept = matchAccept;
|
|
7
8
|
/**
|
|
8
9
|
* Minimal class name combiner — clsx + twMerge gibi davranır ama
|
|
9
10
|
* dependency yok. Falsy filtrele, dedup yapma. Tema override'larında
|
|
@@ -59,4 +60,39 @@ exports.KIND_LABELS = {
|
|
|
59
60
|
code: "Code",
|
|
60
61
|
other: "Other",
|
|
61
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* `<input accept="...">` semantics — pattern listesi virgülle ayrılır,
|
|
65
|
+
* her parça:
|
|
66
|
+
* - `image/*` → MIME wildcard (image/png, image/jpeg ✓)
|
|
67
|
+
* - `image/png` → MIME exact match
|
|
68
|
+
* - `.png` → file extension (case-insensitive)
|
|
69
|
+
*
|
|
70
|
+
* Hiçbir parça eşleşmezse false. Accept boş/undefined olursa caller
|
|
71
|
+
* filter'ı atlamalı — bu fonksiyon o yüzden boş string'i false döner.
|
|
72
|
+
*/
|
|
73
|
+
function matchAccept(file, accept) {
|
|
74
|
+
const patterns = accept
|
|
75
|
+
.split(",")
|
|
76
|
+
.map((s) => s.trim().toLowerCase())
|
|
77
|
+
.filter(Boolean);
|
|
78
|
+
if (patterns.length === 0)
|
|
79
|
+
return false;
|
|
80
|
+
const mt = (file.mimeType ?? "").toLowerCase();
|
|
81
|
+
const fn = (file.fileName ?? "").toLowerCase();
|
|
82
|
+
for (const p of patterns) {
|
|
83
|
+
if (p.startsWith(".")) {
|
|
84
|
+
if (fn.endsWith(p))
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
else if (p.endsWith("/*")) {
|
|
88
|
+
const prefix = p.slice(0, -1); // "image/*" → "image/"
|
|
89
|
+
if (mt.startsWith(prefix))
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
else if (p === mt) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
62
98
|
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/react/lib/utils.ts"],"names":[],"mappings":";;;AAMA,gBAIC;AAED,kCAUC;AAID,gCAyBC;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/react/lib/utils.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -10,8 +10,16 @@ export interface SentroyClientConfig {
|
|
|
10
10
|
baseUrl: string;
|
|
11
11
|
/** Company slug */
|
|
12
12
|
companySlug: string;
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Access token (`stk_...`). Same token works for mail + storage.
|
|
15
|
+
*
|
|
16
|
+
* Optional: when omitted, the client uses **cookie auth**
|
|
17
|
+
* (`credentials: "include"` on every fetch) — useful for browser code
|
|
18
|
+
* running inside the Sentroy site itself, where the user's session
|
|
19
|
+
* cookie is already valid against `sentroy.com`. End users never have
|
|
20
|
+
* to paste an API key when the SDK is embedded in our own UI.
|
|
21
|
+
*/
|
|
22
|
+
accessToken?: string;
|
|
15
23
|
/** Request timeout in milliseconds (default: 30000) */
|
|
16
24
|
timeout?: number;
|
|
17
25
|
}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/http.ts
CHANGED
|
@@ -13,10 +13,17 @@ export class SentroyError extends Error {
|
|
|
13
13
|
|
|
14
14
|
export class HttpClient {
|
|
15
15
|
private baseUrl: string
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Bearer access token. Tanımsızsa Authorization header eklenmez ve
|
|
18
|
+
* `credentials: "include"` ile cookie auth kullanılır — bu mod Sentroy
|
|
19
|
+
* site içinden (sentroy.com / mail.sentroy.com / storage.sentroy.com)
|
|
20
|
+
* MediaManager gibi React component'leri kullanırken caller'ın access
|
|
21
|
+
* token üretmesine gerek bırakmaz; mevcut session cookie'si yeter.
|
|
22
|
+
*/
|
|
23
|
+
private token: string | undefined
|
|
17
24
|
private timeout: number
|
|
18
25
|
|
|
19
|
-
constructor(baseUrl: string, token: string, timeout = 30_000) {
|
|
26
|
+
constructor(baseUrl: string, token: string | undefined, timeout = 30_000) {
|
|
20
27
|
this.baseUrl = baseUrl.replace(/\/+$/, "")
|
|
21
28
|
this.token = token
|
|
22
29
|
this.timeout = timeout
|
|
@@ -34,9 +41,19 @@ export class HttpClient {
|
|
|
34
41
|
return url.toString()
|
|
35
42
|
}
|
|
36
43
|
|
|
44
|
+
private authHeaders(): Record<string, string> {
|
|
45
|
+
return this.token ? { Authorization: `Bearer ${this.token}` } : {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Cookie modunda credentials include; bearer modunda omit. */
|
|
49
|
+
private get fetchCredentials(): RequestCredentials {
|
|
50
|
+
return this.token ? "omit" : "include"
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
/**
|
|
38
54
|
* Raw fetch — JSON parse etmez, Response döner. Binary endpoint'ler
|
|
39
|
-
* (media download) için kullanılır. Authorization header eklenir
|
|
55
|
+
* (media download) için kullanılır. Authorization header eklenir veya
|
|
56
|
+
* cookie auth ile çalışılır.
|
|
40
57
|
*/
|
|
41
58
|
async fetchRaw(
|
|
42
59
|
path: string,
|
|
@@ -48,7 +65,8 @@ export class HttpClient {
|
|
|
48
65
|
try {
|
|
49
66
|
return await fetch(url, {
|
|
50
67
|
method: init?.method || "GET",
|
|
51
|
-
headers:
|
|
68
|
+
headers: this.authHeaders(),
|
|
69
|
+
credentials: this.fetchCredentials,
|
|
52
70
|
signal: controller.signal,
|
|
53
71
|
})
|
|
54
72
|
} finally {
|
|
@@ -69,9 +87,7 @@ export class HttpClient {
|
|
|
69
87
|
const timer = setTimeout(() => controller.abort(), this.timeout)
|
|
70
88
|
|
|
71
89
|
try {
|
|
72
|
-
const headers: Record<string, string> = {
|
|
73
|
-
Authorization: `Bearer ${this.token}`,
|
|
74
|
-
}
|
|
90
|
+
const headers: Record<string, string> = { ...this.authHeaders() }
|
|
75
91
|
if (options?.body) {
|
|
76
92
|
headers["Content-Type"] = "application/json"
|
|
77
93
|
}
|
|
@@ -80,6 +96,7 @@ export class HttpClient {
|
|
|
80
96
|
method,
|
|
81
97
|
headers,
|
|
82
98
|
body: options?.body ? JSON.stringify(options.body) : undefined,
|
|
99
|
+
credentials: this.fetchCredentials,
|
|
83
100
|
signal: controller.signal,
|
|
84
101
|
})
|
|
85
102
|
|
|
@@ -131,7 +148,8 @@ export class HttpClient {
|
|
|
131
148
|
try {
|
|
132
149
|
const res = await fetch(url, {
|
|
133
150
|
method: "POST",
|
|
134
|
-
headers:
|
|
151
|
+
headers: this.authHeaders(),
|
|
152
|
+
credentials: this.fetchCredentials,
|
|
135
153
|
body: form,
|
|
136
154
|
signal: controller.signal,
|
|
137
155
|
})
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
detectKind,
|
|
15
15
|
formatBytes,
|
|
16
16
|
KIND_LABELS,
|
|
17
|
+
matchAccept,
|
|
17
18
|
type MediaKind,
|
|
18
19
|
} from "./lib/utils"
|
|
19
20
|
|
|
@@ -64,7 +65,19 @@ export interface MediaManagerProps {
|
|
|
64
65
|
bucketSlug?: string
|
|
65
66
|
/** Birden fazla seçilebilir mi? Default false. */
|
|
66
67
|
multiple?: boolean
|
|
67
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Multi-mode'da cap. multiple=true iken cap'e ulaşılınca yeni seçim
|
|
70
|
+
* sessizce engellenir. multiple=false iken yok sayılır (single = 1).
|
|
71
|
+
* Default: undefined (cap yok).
|
|
72
|
+
*/
|
|
73
|
+
maxItems?: number
|
|
74
|
+
/**
|
|
75
|
+
* Upload + grid filter için MIME pattern — örn `"image/*"`,
|
|
76
|
+
* `"image/png,image/jpeg"`, `"video/*,application/pdf"`.
|
|
77
|
+
* - Upload `<input type="file">` `accept` özniteliğine geçer.
|
|
78
|
+
* - Grid'de `accept` ile uyumlu olmayan item'lar gizlenir
|
|
79
|
+
* (kullanıcı yine de bucket'ında görebilir ama picker'da seçemez).
|
|
80
|
+
*/
|
|
68
81
|
accept?: string
|
|
69
82
|
/** Kullanıcının önceden seçtiği item'lar — Media obje ya da id string. */
|
|
70
83
|
initialValue?: Array<Media | string>
|
|
@@ -102,6 +115,7 @@ export function MediaManager(props: MediaManagerProps) {
|
|
|
102
115
|
client,
|
|
103
116
|
bucketSlug: initialBucketSlug,
|
|
104
117
|
multiple = false,
|
|
118
|
+
maxItems,
|
|
105
119
|
accept,
|
|
106
120
|
initialValue,
|
|
107
121
|
onChange,
|
|
@@ -171,8 +185,16 @@ export function MediaManager(props: MediaManagerProps) {
|
|
|
171
185
|
setSelectedIds((prev) => {
|
|
172
186
|
const next = new Set(prev)
|
|
173
187
|
if (multiple) {
|
|
174
|
-
if (next.has(media.id))
|
|
175
|
-
|
|
188
|
+
if (next.has(media.id)) {
|
|
189
|
+
next.delete(media.id)
|
|
190
|
+
} else {
|
|
191
|
+
// maxItems cap — multi-mode'da limite ulaşıldıysa yeni
|
|
192
|
+
// seçimi engelle (mevcut state'i değiştirmeden döndür).
|
|
193
|
+
if (typeof maxItems === "number" && next.size >= maxItems) {
|
|
194
|
+
return prev
|
|
195
|
+
}
|
|
196
|
+
next.add(media.id)
|
|
197
|
+
}
|
|
176
198
|
} else {
|
|
177
199
|
// Single: aynısı tıklanırsa deselect
|
|
178
200
|
if (next.has(media.id) && next.size === 1) next.clear()
|
|
@@ -184,7 +206,7 @@ export function MediaManager(props: MediaManagerProps) {
|
|
|
184
206
|
return next
|
|
185
207
|
})
|
|
186
208
|
},
|
|
187
|
-
[multiple],
|
|
209
|
+
[multiple, maxItems],
|
|
188
210
|
)
|
|
189
211
|
|
|
190
212
|
const selected = useMemo(
|
|
@@ -207,9 +229,10 @@ export function MediaManager(props: MediaManagerProps) {
|
|
|
207
229
|
return items.filter((m) => {
|
|
208
230
|
if (q && !m.fileName.toLowerCase().includes(q)) return false
|
|
209
231
|
if (kindFilter !== "all" && detectKind(m) !== kindFilter) return false
|
|
232
|
+
if (accept && !matchAccept(m, accept)) return false
|
|
210
233
|
return true
|
|
211
234
|
})
|
|
212
|
-
}, [items, search, kindFilter])
|
|
235
|
+
}, [items, search, kindFilter, accept])
|
|
213
236
|
|
|
214
237
|
// ── Upload ─────────────────────────────────────────────────────────────
|
|
215
238
|
const fileInputRef = useRef<HTMLInputElement | null>(null)
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState, type ReactNode } from "react"
|
|
2
|
+
import { createPortal } from "react-dom"
|
|
3
|
+
import type { Media } from "../types"
|
|
4
|
+
import { MediaManager, type MediaManagerProps } from "./MediaManager"
|
|
5
|
+
import { cn } from "./lib/utils"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* MediaManagerTrigger — herhangi bir consumer-defined öğe (button, avatar
|
|
9
|
+
* thumb, ikon, vb.) tıklandığında MediaManager'ı modal içinde açan
|
|
10
|
+
* sarmalayıcı.
|
|
11
|
+
*
|
|
12
|
+
* Tasarım hedefi: kullanıcı kendi tetikleyicisini (`trigger` prop) verir,
|
|
13
|
+
* tıklanma + modal yönetimi + confirm/cancel akışı SDK tarafında olur.
|
|
14
|
+
* Böylece consumer her seferinde Dialog state ve render boilerplate'ini
|
|
15
|
+
* yazmak zorunda kalmaz — sadece "şu butonum şu callback'i tetiklesin"
|
|
16
|
+
* kadar kısa olur.
|
|
17
|
+
*
|
|
18
|
+
* Modal Tailwind utility class kullanır (host app'in design token'ları)
|
|
19
|
+
* ve `react-dom` portal ile `<body>`'ye render edilir — parent
|
|
20
|
+
* overflow:hidden / transform stacking context'lerine takılmaz.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <MediaManagerTrigger
|
|
25
|
+
* client={client}
|
|
26
|
+
* trigger={<Button>Change avatar</Button>}
|
|
27
|
+
* maxItems={1}
|
|
28
|
+
* accept="image/*"
|
|
29
|
+
* onSelect={(media) => console.log(media[0].url)}
|
|
30
|
+
* />
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
export interface MediaManagerTriggerProps
|
|
35
|
+
extends Omit<MediaManagerProps, "onSelect" | "onChange" | "multiple"> {
|
|
36
|
+
/** Tıklanabilir herhangi bir öğe — button, image, div, vb. */
|
|
37
|
+
trigger: ReactNode
|
|
38
|
+
/**
|
|
39
|
+
* Maksimum seçilebilir item sayısı. 1 = single mode, >1 = multi up to
|
|
40
|
+
* cap. Default 1. Cap'e ulaşılınca yeni item seçimi sessizce engellenir.
|
|
41
|
+
*/
|
|
42
|
+
maxItems?: number
|
|
43
|
+
/** Confirm — kullanıcı "Use selection" butonuna bastığında çağrılır. */
|
|
44
|
+
onSelect: (selected: Media[]) => void
|
|
45
|
+
/** Modal başlığı. Default "Select media". */
|
|
46
|
+
title?: string
|
|
47
|
+
/** Modal description satırı (opsiyonel). */
|
|
48
|
+
description?: string
|
|
49
|
+
/**
|
|
50
|
+
* Controlled mode — open state'ini parent yönetmek isterse.
|
|
51
|
+
* Default uncontrolled (kendi içinde useState).
|
|
52
|
+
*/
|
|
53
|
+
open?: boolean
|
|
54
|
+
/** Controlled mode için open değişikliği callback'i. */
|
|
55
|
+
onOpenChange?: (open: boolean) => void
|
|
56
|
+
/** Modal panel class override. */
|
|
57
|
+
modalClassName?: string
|
|
58
|
+
/** Trigger wrapper class — default `inline-block cursor-pointer`. */
|
|
59
|
+
triggerClassName?: string
|
|
60
|
+
/** Confirm butonu metni. Default "Use selection". */
|
|
61
|
+
confirmLabel?: string
|
|
62
|
+
/** Cancel butonu metni. Default "Cancel". */
|
|
63
|
+
cancelLabel?: string
|
|
64
|
+
/** Trigger'ın disabled durumu — modal açılmaz. */
|
|
65
|
+
disabled?: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function MediaManagerTrigger(props: MediaManagerTriggerProps) {
|
|
69
|
+
const {
|
|
70
|
+
trigger,
|
|
71
|
+
maxItems = 1,
|
|
72
|
+
onSelect,
|
|
73
|
+
title = "Select media",
|
|
74
|
+
description,
|
|
75
|
+
open: controlledOpen,
|
|
76
|
+
onOpenChange,
|
|
77
|
+
modalClassName,
|
|
78
|
+
triggerClassName,
|
|
79
|
+
confirmLabel = "Use selection",
|
|
80
|
+
cancelLabel = "Cancel",
|
|
81
|
+
disabled = false,
|
|
82
|
+
...mmProps
|
|
83
|
+
} = props
|
|
84
|
+
|
|
85
|
+
const [internalOpen, setInternalOpen] = useState(false)
|
|
86
|
+
const [selected, setSelected] = useState<Media[]>([])
|
|
87
|
+
const [mounted, setMounted] = useState(false)
|
|
88
|
+
|
|
89
|
+
// SSR guard — portal yalnızca client'ta.
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
setMounted(true)
|
|
92
|
+
}, [])
|
|
93
|
+
|
|
94
|
+
const isControlled = controlledOpen !== undefined
|
|
95
|
+
const open = isControlled ? controlledOpen : internalOpen
|
|
96
|
+
const setOpen = useCallback(
|
|
97
|
+
(v: boolean) => {
|
|
98
|
+
if (!isControlled) setInternalOpen(v)
|
|
99
|
+
onOpenChange?.(v)
|
|
100
|
+
if (!v) setSelected([])
|
|
101
|
+
},
|
|
102
|
+
[isControlled, onOpenChange],
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
// ESC — modal'ı kapat.
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (!open) return
|
|
108
|
+
const onKey = (e: KeyboardEvent) => {
|
|
109
|
+
if (e.key === "Escape") {
|
|
110
|
+
e.preventDefault()
|
|
111
|
+
setOpen(false)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
document.addEventListener("keydown", onKey)
|
|
115
|
+
return () => document.removeEventListener("keydown", onKey)
|
|
116
|
+
}, [open, setOpen])
|
|
117
|
+
|
|
118
|
+
// Body scroll lock — modal açıkken arka planda scroll olmasın.
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (!open) return
|
|
121
|
+
const original = document.body.style.overflow
|
|
122
|
+
document.body.style.overflow = "hidden"
|
|
123
|
+
return () => {
|
|
124
|
+
document.body.style.overflow = original
|
|
125
|
+
}
|
|
126
|
+
}, [open])
|
|
127
|
+
|
|
128
|
+
const handleConfirm = useCallback(() => {
|
|
129
|
+
onSelect(selected)
|
|
130
|
+
setOpen(false)
|
|
131
|
+
}, [onSelect, selected, setOpen])
|
|
132
|
+
|
|
133
|
+
const handleTriggerClick = useCallback(() => {
|
|
134
|
+
if (disabled) return
|
|
135
|
+
setOpen(true)
|
|
136
|
+
}, [disabled, setOpen])
|
|
137
|
+
|
|
138
|
+
// Trigger — span'a click handler bağla. Consumer'ın trigger'ı zaten
|
|
139
|
+
// button olabilir; bu durumda nested button HTML invalid olur ama
|
|
140
|
+
// tarayıcılar tolere eder. İstenirse triggerClassName ile span yerine
|
|
141
|
+
// başka semantik kullanılabilir (consumer kendi trigger'ında onClick
|
|
142
|
+
// override edemez — modal handler'ı her zaman çalışır).
|
|
143
|
+
const triggerNode = (
|
|
144
|
+
<span
|
|
145
|
+
role="button"
|
|
146
|
+
tabIndex={disabled ? -1 : 0}
|
|
147
|
+
aria-haspopup="dialog"
|
|
148
|
+
aria-expanded={open}
|
|
149
|
+
aria-disabled={disabled}
|
|
150
|
+
onClick={handleTriggerClick}
|
|
151
|
+
onKeyDown={(e) => {
|
|
152
|
+
if (disabled) return
|
|
153
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
154
|
+
e.preventDefault()
|
|
155
|
+
setOpen(true)
|
|
156
|
+
}
|
|
157
|
+
}}
|
|
158
|
+
className={cn(
|
|
159
|
+
"inline-block",
|
|
160
|
+
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
|
161
|
+
triggerClassName,
|
|
162
|
+
)}
|
|
163
|
+
>
|
|
164
|
+
{trigger}
|
|
165
|
+
</span>
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const modalNode =
|
|
169
|
+
open && mounted ? (
|
|
170
|
+
<div
|
|
171
|
+
className="fixed inset-0 z-[100] flex items-center justify-center p-4"
|
|
172
|
+
role="dialog"
|
|
173
|
+
aria-modal="true"
|
|
174
|
+
aria-labelledby="sentroy-mm-title"
|
|
175
|
+
>
|
|
176
|
+
<div
|
|
177
|
+
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
|
178
|
+
onClick={() => setOpen(false)}
|
|
179
|
+
/>
|
|
180
|
+
<div
|
|
181
|
+
className={cn(
|
|
182
|
+
"relative z-10 flex h-[85vh] w-full max-w-5xl flex-col gap-3 rounded-xl border bg-background p-4 shadow-2xl",
|
|
183
|
+
modalClassName,
|
|
184
|
+
)}
|
|
185
|
+
>
|
|
186
|
+
<div className="flex items-start justify-between gap-2">
|
|
187
|
+
<div className="flex flex-col gap-0.5">
|
|
188
|
+
<h2 id="sentroy-mm-title" className="text-base font-semibold">
|
|
189
|
+
{title}
|
|
190
|
+
</h2>
|
|
191
|
+
{description && (
|
|
192
|
+
<p className="text-xs text-muted-foreground">{description}</p>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
onClick={() => setOpen(false)}
|
|
198
|
+
className="rounded-md p-1 text-muted-foreground hover:bg-muted/50"
|
|
199
|
+
aria-label="Close"
|
|
200
|
+
>
|
|
201
|
+
<svg
|
|
202
|
+
width="16"
|
|
203
|
+
height="16"
|
|
204
|
+
viewBox="0 0 24 24"
|
|
205
|
+
fill="none"
|
|
206
|
+
stroke="currentColor"
|
|
207
|
+
strokeWidth="2"
|
|
208
|
+
>
|
|
209
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
210
|
+
</svg>
|
|
211
|
+
</button>
|
|
212
|
+
</div>
|
|
213
|
+
<div className="min-h-0 flex-1 overflow-hidden">
|
|
214
|
+
<MediaManager
|
|
215
|
+
{...mmProps}
|
|
216
|
+
multiple={maxItems > 1}
|
|
217
|
+
maxItems={maxItems}
|
|
218
|
+
onChange={setSelected}
|
|
219
|
+
className={cn("h-full", mmProps.className)}
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
<div className="flex items-center justify-between gap-2 border-t pt-3">
|
|
223
|
+
<div className="text-xs text-muted-foreground">
|
|
224
|
+
{selected.length === 0
|
|
225
|
+
? maxItems === 1
|
|
226
|
+
? "Select an item"
|
|
227
|
+
: `Select up to ${maxItems} items`
|
|
228
|
+
: maxItems === 1
|
|
229
|
+
? "1 item selected"
|
|
230
|
+
: `${selected.length} / ${maxItems} selected`}
|
|
231
|
+
</div>
|
|
232
|
+
<div className="flex items-center gap-2">
|
|
233
|
+
<button
|
|
234
|
+
type="button"
|
|
235
|
+
onClick={() => setOpen(false)}
|
|
236
|
+
className="rounded-md border px-3 py-1.5 text-xs hover:bg-muted/50"
|
|
237
|
+
>
|
|
238
|
+
{cancelLabel}
|
|
239
|
+
</button>
|
|
240
|
+
<button
|
|
241
|
+
type="button"
|
|
242
|
+
onClick={handleConfirm}
|
|
243
|
+
disabled={selected.length === 0}
|
|
244
|
+
className="rounded-md bg-foreground px-3 py-1.5 text-xs font-medium text-background transition-opacity hover:opacity-90 disabled:opacity-50"
|
|
245
|
+
>
|
|
246
|
+
{confirmLabel}
|
|
247
|
+
</button>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
) : null
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<>
|
|
256
|
+
{triggerNode}
|
|
257
|
+
{mounted && modalNode ? createPortal(modalNode, document.body) : null}
|
|
258
|
+
</>
|
|
259
|
+
)
|
|
260
|
+
}
|
package/src/react/index.ts
CHANGED
|
@@ -3,11 +3,16 @@ export {
|
|
|
3
3
|
type MediaManagerProps,
|
|
4
4
|
type MediaManagerClassNames,
|
|
5
5
|
} from "./MediaManager"
|
|
6
|
+
export {
|
|
7
|
+
MediaManagerTrigger,
|
|
8
|
+
type MediaManagerTriggerProps,
|
|
9
|
+
} from "./MediaManagerTrigger"
|
|
6
10
|
export { Lightbox, type LightboxProps } from "./lib/Lightbox"
|
|
7
11
|
export {
|
|
8
12
|
cn,
|
|
9
13
|
formatBytes,
|
|
10
14
|
detectKind,
|
|
15
|
+
matchAccept,
|
|
11
16
|
KIND_LABELS,
|
|
12
17
|
type MediaKind,
|
|
13
18
|
} from "./lib/utils"
|
package/src/react/lib/utils.ts
CHANGED
|
@@ -71,3 +71,37 @@ export const KIND_LABELS: Record<MediaKind, string> = {
|
|
|
71
71
|
code: "Code",
|
|
72
72
|
other: "Other",
|
|
73
73
|
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* `<input accept="...">` semantics — pattern listesi virgülle ayrılır,
|
|
77
|
+
* her parça:
|
|
78
|
+
* - `image/*` → MIME wildcard (image/png, image/jpeg ✓)
|
|
79
|
+
* - `image/png` → MIME exact match
|
|
80
|
+
* - `.png` → file extension (case-insensitive)
|
|
81
|
+
*
|
|
82
|
+
* Hiçbir parça eşleşmezse false. Accept boş/undefined olursa caller
|
|
83
|
+
* filter'ı atlamalı — bu fonksiyon o yüzden boş string'i false döner.
|
|
84
|
+
*/
|
|
85
|
+
export function matchAccept(
|
|
86
|
+
file: { mimeType?: string | null; fileName?: string },
|
|
87
|
+
accept: string,
|
|
88
|
+
): boolean {
|
|
89
|
+
const patterns = accept
|
|
90
|
+
.split(",")
|
|
91
|
+
.map((s) => s.trim().toLowerCase())
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
if (patterns.length === 0) return false
|
|
94
|
+
const mt = (file.mimeType ?? "").toLowerCase()
|
|
95
|
+
const fn = (file.fileName ?? "").toLowerCase()
|
|
96
|
+
for (const p of patterns) {
|
|
97
|
+
if (p.startsWith(".")) {
|
|
98
|
+
if (fn.endsWith(p)) return true
|
|
99
|
+
} else if (p.endsWith("/*")) {
|
|
100
|
+
const prefix = p.slice(0, -1) // "image/*" → "image/"
|
|
101
|
+
if (mt.startsWith(prefix)) return true
|
|
102
|
+
} else if (p === mt) {
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return false
|
|
107
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -12,8 +12,16 @@ export interface SentroyClientConfig {
|
|
|
12
12
|
baseUrl: string
|
|
13
13
|
/** Company slug */
|
|
14
14
|
companySlug: string
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Access token (`stk_...`). Same token works for mail + storage.
|
|
17
|
+
*
|
|
18
|
+
* Optional: when omitted, the client uses **cookie auth**
|
|
19
|
+
* (`credentials: "include"` on every fetch) — useful for browser code
|
|
20
|
+
* running inside the Sentroy site itself, where the user's session
|
|
21
|
+
* cookie is already valid against `sentroy.com`. End users never have
|
|
22
|
+
* to paste an API key when the SDK is embedded in our own UI.
|
|
23
|
+
*/
|
|
24
|
+
accessToken?: string
|
|
17
25
|
/** Request timeout in milliseconds (default: 30000) */
|
|
18
26
|
timeout?: number
|
|
19
27
|
}
|