@proteos/ui 0.31.0 → 0.32.1
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/index.cjs +1302 -516
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +259 -1
- package/dist/index.d.ts +259 -1
- package/dist/index.js +1292 -520
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/draft-message-composer/create-draft-message-client.ts +27 -0
- package/src/components/draft-message-composer/draft-message-actions-store.test.ts +62 -0
- package/src/components/draft-message-composer/draft-message-actions-store.ts +81 -0
- package/src/components/draft-message-composer/draft-message-composer.stories.tsx +175 -0
- package/src/components/draft-message-composer/draft-message-composer.test.tsx +276 -0
- package/src/components/draft-message-composer/draft-message-composer.tsx +470 -0
- package/src/components/draft-message-composer/draft-message-outcome-store.test.ts +59 -0
- package/src/components/draft-message-composer/draft-message-outcome-store.ts +56 -0
- package/src/components/draft-message-composer/index.ts +19 -0
- package/src/components/draft-message-composer/types.ts +32 -0
- package/src/components/message-composer/index.ts +13 -0
- package/src/components/message-composer/message-composer.stories.tsx +155 -0
- package/src/components/message-composer/message-composer.test.tsx +320 -0
- package/src/components/message-composer/message-composer.tsx +480 -0
- package/src/components/recipient-select/index.ts +2 -0
- package/src/components/recipient-select/recipient-select.test.tsx +139 -0
- package/src/components/recipient-select/recipient-select.tsx +94 -0
- package/src/index.ts +3 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { ContactAddress, SendRecipient } from '@proteos/sdk'
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import type { SearchRecipients } from '../message-composer/message-composer'
|
|
5
|
+
import { MultiSelect, type MultiSelectOption } from '../multi-select/multi-select'
|
|
6
|
+
|
|
7
|
+
/** Debounce for the injected directory search (`q`). */
|
|
8
|
+
const SEARCH_DEBOUNCE_MS = 250
|
|
9
|
+
|
|
10
|
+
/* =========================================================================
|
|
11
|
+
RecipientSelect — a searchable multi-select over the contact-address
|
|
12
|
+
directory a connection can reach, emitting SendRecipient[] (all
|
|
13
|
+
kind=contact-address). Backs the composer's To/Cc/Bcc rows. Typing filters
|
|
14
|
+
the loaded options locally AND (debounced) runs the injected
|
|
15
|
+
`searchRecipients` server-side, so parties beyond the first page are
|
|
16
|
+
findable; already-selected ids are always included so their chips render
|
|
17
|
+
even when off the current result page. Fetch-agnostic: the host injects
|
|
18
|
+
the search; stale responses are ignored and a failed search keeps the
|
|
19
|
+
previous options (best-effort).
|
|
20
|
+
========================================================================= */
|
|
21
|
+
|
|
22
|
+
export interface RecipientSelectProps {
|
|
23
|
+
connectionId: string
|
|
24
|
+
searchRecipients: SearchRecipients
|
|
25
|
+
value: SendRecipient[]
|
|
26
|
+
onChange: (next: SendRecipient[]) => void
|
|
27
|
+
placeholder?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function RecipientSelect({
|
|
31
|
+
connectionId,
|
|
32
|
+
searchRecipients,
|
|
33
|
+
value,
|
|
34
|
+
onChange,
|
|
35
|
+
placeholder,
|
|
36
|
+
}: RecipientSelectProps) {
|
|
37
|
+
const [search, setSearch] = useState('')
|
|
38
|
+
const [debouncedSearch, setDebouncedSearch] = useState('')
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const handle = window.setTimeout(() => setDebouncedSearch(search.trim()), SEARCH_DEBOUNCE_MS)
|
|
41
|
+
return () => window.clearTimeout(handle)
|
|
42
|
+
}, [search])
|
|
43
|
+
|
|
44
|
+
const [addresses, setAddresses] = useState<ContactAddress[]>([])
|
|
45
|
+
// Monotonic request counter — only the latest in-flight search may land.
|
|
46
|
+
const requestIdRef = useRef(0)
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (connectionId === '') return
|
|
49
|
+
const requestId = ++requestIdRef.current
|
|
50
|
+
searchRecipients(connectionId, debouncedSearch)
|
|
51
|
+
.then((results) => {
|
|
52
|
+
if (requestId === requestIdRef.current) setAddresses(results)
|
|
53
|
+
})
|
|
54
|
+
.catch(() => {
|
|
55
|
+
// Best-effort directory: keep the previous options on failure.
|
|
56
|
+
})
|
|
57
|
+
}, [connectionId, debouncedSearch, searchRecipients])
|
|
58
|
+
|
|
59
|
+
const options = useMemo<MultiSelectOption<string>[]>(() => {
|
|
60
|
+
const map = new Map<string, MultiSelectOption<string>>()
|
|
61
|
+
for (const recipient of value) {
|
|
62
|
+
map.set(recipient.external_id, { id: recipient.external_id, label: recipient.external_id })
|
|
63
|
+
}
|
|
64
|
+
for (const address of addresses) {
|
|
65
|
+
const identifier = address.raw_value || address.value
|
|
66
|
+
map.set(identifier, {
|
|
67
|
+
id: identifier,
|
|
68
|
+
label: address.name || identifier,
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
return Array.from(map.values())
|
|
72
|
+
}, [addresses, value])
|
|
73
|
+
|
|
74
|
+
// The composer renders the row label (inline mono, like the Via/Subject
|
|
75
|
+
// rows) — none here.
|
|
76
|
+
return (
|
|
77
|
+
<MultiSelect
|
|
78
|
+
size="sm"
|
|
79
|
+
isSearchable
|
|
80
|
+
onSearchChange={setSearch}
|
|
81
|
+
{...(placeholder !== undefined ? { placeholder } : {})}
|
|
82
|
+
options={options}
|
|
83
|
+
selectedKeys={value.map((recipient) => recipient.external_id)}
|
|
84
|
+
onSelectionChange={(keys) =>
|
|
85
|
+
onChange(
|
|
86
|
+
Array.from(keys).map((id) => ({
|
|
87
|
+
kind: 'contact-address' as const,
|
|
88
|
+
external_id: String(id),
|
|
89
|
+
})),
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
/>
|
|
93
|
+
)
|
|
94
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from './components/currency-input'
|
|
|
14
14
|
export * from './components/data-table'
|
|
15
15
|
export * from './components/date-picker'
|
|
16
16
|
export * from './components/date-time-picker'
|
|
17
|
+
export * from './components/draft-message-composer'
|
|
17
18
|
export * from './components/empty-state'
|
|
18
19
|
export * from './components/error-animation'
|
|
19
20
|
export * from './components/field'
|
|
@@ -24,12 +25,14 @@ export * from './components/image-lightbox'
|
|
|
24
25
|
export * from './components/input'
|
|
25
26
|
export * from './components/kbd'
|
|
26
27
|
export * from './components/menu'
|
|
28
|
+
export * from './components/message-composer'
|
|
27
29
|
export * from './components/modal'
|
|
28
30
|
export * from './components/multi-select'
|
|
29
31
|
export * from './components/nav'
|
|
30
32
|
export * from './components/popover'
|
|
31
33
|
export * from './components/progress-bar'
|
|
32
34
|
export * from './components/radio-group'
|
|
35
|
+
export * from './components/recipient-select'
|
|
33
36
|
export * from './components/record-filter'
|
|
34
37
|
export * from './components/segmented-control'
|
|
35
38
|
export * from './components/select'
|