@ermis-network/ermis-chat-react 1.0.1 → 1.0.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.
@@ -0,0 +1,268 @@
1
+ /* ============================================================
2
+ UserPicker – Reusable User Selection Component
3
+ BEM: .ermis-user-picker__{element}--{modifier}
4
+ ============================================================ */
5
+
6
+ /* ---------- Root container ---------- */
7
+ .ermis-user-picker {
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: var(--ermis-spacing-md, 0.75rem);
11
+ }
12
+
13
+ /* ---------- Selected Users Chip Box ---------- */
14
+ .ermis-user-picker__selected-box {
15
+ display: flex;
16
+ flex-wrap: wrap;
17
+ gap: var(--ermis-spacing-xs, 0.25rem);
18
+ padding: var(--ermis-spacing-sm, 0.5rem);
19
+ border-radius: var(--ermis-radius-md, 0.5rem);
20
+ background-color: var(--ermis-bg-secondary, #111118);
21
+ border: 1px solid var(--ermis-border, rgba(255, 255, 255, 0.08));
22
+ min-height: 40px;
23
+ max-height: 120px;
24
+ overflow-y: auto;
25
+ }
26
+
27
+ .ermis-user-picker__selected-box:empty {
28
+ display: none;
29
+ }
30
+
31
+ .ermis-user-picker__selected-box::-webkit-scrollbar {
32
+ width: 4px;
33
+ }
34
+ .ermis-user-picker__selected-box::-webkit-scrollbar-track {
35
+ background: transparent;
36
+ }
37
+ .ermis-user-picker__selected-box::-webkit-scrollbar-thumb {
38
+ background: var(--ermis-border, rgba(255, 255, 255, 0.08));
39
+ border-radius: var(--ermis-radius-full, 9999px);
40
+ }
41
+
42
+ .ermis-user-picker__chip {
43
+ display: inline-flex;
44
+ align-items: center;
45
+ gap: var(--ermis-spacing-xs, 0.25rem);
46
+ padding: 2px 8px 2px 2px;
47
+ border-radius: var(--ermis-radius-full, 9999px);
48
+ background-color: var(--ermis-bg-active, rgba(99, 102, 241, 0.12));
49
+ color: var(--ermis-text-primary, #e5e7eb);
50
+ font-size: var(--ermis-font-size-xs, 0.75rem);
51
+ font-weight: 500;
52
+ line-height: 1;
53
+ white-space: nowrap;
54
+ transition: background-color var(--ermis-transition, 150ms ease);
55
+ animation: ermis-user-picker-chip-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);
56
+ }
57
+
58
+ @keyframes ermis-user-picker-chip-in {
59
+ from { opacity: 0; transform: scale(0.85); }
60
+ to { opacity: 1; transform: scale(1); }
61
+ }
62
+
63
+ .ermis-user-picker__chip:hover {
64
+ background-color: var(--ermis-accent, #6366f1);
65
+ color: #ffffff;
66
+ }
67
+
68
+ .ermis-user-picker__chip-name {
69
+ max-width: 100px;
70
+ overflow: hidden;
71
+ text-overflow: ellipsis;
72
+ }
73
+
74
+ .ermis-user-picker__chip-remove {
75
+ display: inline-flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ width: 16px;
79
+ height: 16px;
80
+ border: none;
81
+ background: none;
82
+ color: inherit;
83
+ cursor: pointer;
84
+ padding: 0;
85
+ border-radius: var(--ermis-radius-full, 9999px);
86
+ opacity: 0.7;
87
+ transition: opacity var(--ermis-transition, 150ms ease);
88
+ }
89
+
90
+ .ermis-user-picker__chip-remove:hover {
91
+ opacity: 1;
92
+ }
93
+
94
+ .ermis-user-picker__selected-empty {
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ width: 100%;
99
+ color: var(--ermis-text-muted, #6b7280);
100
+ font-size: var(--ermis-font-size-xs, 0.75rem);
101
+ }
102
+
103
+ /* ---------- Search Input ---------- */
104
+ .ermis-user-picker__search {
105
+ position: relative;
106
+ display: flex;
107
+ align-items: center;
108
+ }
109
+
110
+ .ermis-user-picker__search svg {
111
+ position: absolute;
112
+ left: 12px;
113
+ color: var(--ermis-text-muted, #6b7280);
114
+ pointer-events: none;
115
+ }
116
+
117
+ .ermis-user-picker__search input {
118
+ width: 100%;
119
+ padding: 10px 12px 10px 38px;
120
+ border-radius: var(--ermis-radius-md, 0.5rem);
121
+ border: 1px solid var(--ermis-border, rgba(255, 255, 255, 0.08));
122
+ background-color: var(--ermis-bg-secondary, #111118);
123
+ color: var(--ermis-text-primary, #e5e7eb);
124
+ font-size: var(--ermis-font-size-sm, 0.875rem);
125
+ font-family: inherit;
126
+ transition: border-color var(--ermis-transition, 150ms ease), box-shadow var(--ermis-transition, 150ms ease);
127
+ outline: none;
128
+ }
129
+
130
+ .ermis-user-picker__search input::placeholder {
131
+ color: var(--ermis-text-muted, #6b7280);
132
+ }
133
+
134
+ .ermis-user-picker__search input:focus {
135
+ border-color: var(--ermis-accent, #6366f1);
136
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
137
+ }
138
+
139
+ /* ---------- List Container ---------- */
140
+ .ermis-user-picker__list {
141
+ overflow: hidden;
142
+ height: 360px;
143
+ border-radius: var(--ermis-radius-md, 0.5rem);
144
+ }
145
+
146
+ /* ---------- User Row ---------- */
147
+ .ermis-user-picker__item {
148
+ display: flex;
149
+ align-items: center;
150
+ gap: var(--ermis-spacing-md, 0.75rem);
151
+ padding: 8px 12px;
152
+ border-radius: var(--ermis-radius-md, 0.5rem);
153
+ cursor: pointer;
154
+ transition: background-color var(--ermis-transition, 150ms ease);
155
+ user-select: none;
156
+ }
157
+
158
+ .ermis-user-picker__item:hover {
159
+ background-color: var(--ermis-bg-hover, rgba(255, 255, 255, 0.04));
160
+ }
161
+
162
+ .ermis-user-picker__item--selected {
163
+ background-color: var(--ermis-bg-active, rgba(99, 102, 241, 0.12));
164
+ }
165
+
166
+ .ermis-user-picker__item--selected:hover {
167
+ background-color: var(--ermis-bg-active, rgba(99, 102, 241, 0.12));
168
+ }
169
+
170
+ .ermis-user-picker__item--disabled {
171
+ opacity: 0.45;
172
+ cursor: not-allowed;
173
+ pointer-events: none;
174
+ }
175
+
176
+ /* ---------- Radio / Checkbox ---------- */
177
+ .ermis-user-picker__input {
178
+ position: relative;
179
+ width: 18px;
180
+ height: 18px;
181
+ min-width: 18px;
182
+ border: 2px solid var(--ermis-border, rgba(255, 255, 255, 0.08));
183
+ background: transparent;
184
+ transition: all var(--ermis-transition, 150ms ease);
185
+ display: flex;
186
+ align-items: center;
187
+ justify-content: center;
188
+ flex-shrink: 0;
189
+ }
190
+
191
+ .ermis-user-picker__input--radio {
192
+ border-radius: var(--ermis-radius-full, 9999px);
193
+ }
194
+
195
+ .ermis-user-picker__input--checkbox {
196
+ border-radius: var(--ermis-radius-sm, 0.375rem);
197
+ }
198
+
199
+ .ermis-user-picker__input--checked {
200
+ background-color: var(--ermis-accent, #6366f1);
201
+ border-color: var(--ermis-accent, #6366f1);
202
+ }
203
+
204
+ .ermis-user-picker__input--checked svg {
205
+ color: #ffffff;
206
+ }
207
+
208
+ /* ---------- User Info ---------- */
209
+ .ermis-user-picker__info {
210
+ flex: 1;
211
+ min-width: 0;
212
+ display: flex;
213
+ flex-direction: column;
214
+ gap: 2px;
215
+ }
216
+
217
+ .ermis-user-picker__name {
218
+ font-size: var(--ermis-font-size-sm, 0.875rem);
219
+ font-weight: 500;
220
+ color: var(--ermis-text-primary, #e5e7eb);
221
+ white-space: nowrap;
222
+ overflow: hidden;
223
+ text-overflow: ellipsis;
224
+ }
225
+
226
+ .ermis-user-picker__detail {
227
+ font-size: var(--ermis-font-size-xs, 0.75rem);
228
+ color: var(--ermis-text-muted, #6b7280);
229
+ white-space: nowrap;
230
+ overflow: hidden;
231
+ text-overflow: ellipsis;
232
+ }
233
+
234
+ /* ---------- Loading / Empty ---------- */
235
+ .ermis-user-picker__loading,
236
+ .ermis-user-picker__empty {
237
+ display: flex;
238
+ align-items: center;
239
+ justify-content: center;
240
+ padding: 32px 0;
241
+ color: var(--ermis-text-muted, #6b7280);
242
+ font-size: var(--ermis-font-size-sm, 0.875rem);
243
+ }
244
+
245
+ .ermis-user-picker__load-more {
246
+ display: flex;
247
+ align-items: center;
248
+ justify-content: center;
249
+ padding: 12px 0;
250
+ color: var(--ermis-text-muted, #6b7280);
251
+ font-size: var(--ermis-font-size-xs, 0.75rem);
252
+ }
253
+
254
+ /* ---------- Spinner keyframes ---------- */
255
+ .ermis-user-picker__spinner {
256
+ display: inline-block;
257
+ width: 16px;
258
+ height: 16px;
259
+ border: 2px solid var(--ermis-border, rgba(255, 255, 255, 0.08));
260
+ border-top-color: var(--ermis-accent, #6366f1);
261
+ border-radius: var(--ermis-radius-full, 9999px);
262
+ animation: ermis-user-picker-spin 0.6s linear infinite;
263
+ margin-right: var(--ermis-spacing-sm, 0.5rem);
264
+ }
265
+
266
+ @keyframes ermis-user-picker-spin {
267
+ to { transform: rotate(360deg); }
268
+ }
@@ -22,3 +22,6 @@
22
22
  @import './_channel-info.css';
23
23
  @import './_add-member-modal.css';
24
24
  @import './_search-panel.css';
25
+ @import './_search-panel.css';
26
+ @import './_user-picker.css';
27
+ @import './_create-channel-modal.css';
package/src/types.ts CHANGED
@@ -953,3 +953,103 @@ export type ChannelSettingsPanelProps = {
953
953
  /** Custom slow mode options */
954
954
  slowModeOptions?: { label: string; value: number }[];
955
955
  };
956
+
957
+ /* ----------------------------------------------------------
958
+ UserPicker types
959
+ ---------------------------------------------------------- */
960
+
961
+ /** Individual user item in UserPicker */
962
+ export type UserPickerUser = {
963
+ id: string;
964
+ name?: string;
965
+ email?: string;
966
+ phone?: string;
967
+ avatar?: string;
968
+ [key: string]: any;
969
+ };
970
+
971
+ /** Props for each user row in UserPicker */
972
+ export type UserPickerItemProps = {
973
+ user: UserPickerUser;
974
+ selected: boolean;
975
+ disabled: boolean;
976
+ mode: 'radio' | 'checkbox';
977
+ onToggle: (user: UserPickerUser) => void;
978
+ AvatarComponent: React.ComponentType<AvatarProps>;
979
+ };
980
+
981
+ /** Props for the selected users chip box */
982
+ export type UserPickerSelectedBoxProps = {
983
+ users: UserPickerUser[];
984
+ onRemove: (userId: string) => void;
985
+ AvatarComponent: React.ComponentType<AvatarProps>;
986
+ /** Label when no users selected */
987
+ emptyLabel?: string;
988
+ };
989
+
990
+ /** Main UserPicker props */
991
+ export type UserPickerProps = {
992
+ /** Selection mode: 'radio' for single, 'checkbox' for multi */
993
+ mode: 'radio' | 'checkbox';
994
+ /** Called whenever selection changes */
995
+ onSelectionChange?: (users: UserPickerUser[]) => void;
996
+ /** User IDs to exclude from the list (e.g. existing members) */
997
+ excludeUserIds?: string[];
998
+ /** Users that are pre-selected on mount */
999
+ initialSelectedUsers?: UserPickerUser[];
1000
+ /** Page size for queryUsers (default: 30) */
1001
+ pageSize?: number;
1002
+ /** Custom avatar component */
1003
+ AvatarComponent?: React.ComponentType<AvatarProps>;
1004
+ /** Custom user item component (replaces the default row) */
1005
+ UserItemComponent?: React.ComponentType<UserPickerItemProps>;
1006
+ /** Custom selected box component (checkbox mode only) */
1007
+ SelectedBoxComponent?: React.ComponentType<UserPickerSelectedBoxProps>;
1008
+ /** Custom search input component */
1009
+ SearchInputComponent?: React.ComponentType<{
1010
+ value: string;
1011
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
1012
+ placeholder: string;
1013
+ }>;
1014
+
1015
+ /** I18n labels */
1016
+ searchPlaceholder?: string;
1017
+ loadingText?: string;
1018
+ emptyText?: string;
1019
+ loadingMoreText?: string;
1020
+ selectedEmptyLabel?: string;
1021
+ };
1022
+
1023
+ /* ----------------------------------------------------------
1024
+ Create Channel Modal Props
1025
+ ---------------------------------------------------------- */
1026
+
1027
+ export type CreateChannelModalProps = {
1028
+ isOpen: boolean;
1029
+ onClose: () => void;
1030
+ onSuccess?: (channel: any) => void; // Uses 'any' or 'Channel' based on context
1031
+
1032
+ /** Override visual components */
1033
+ AvatarComponent?: React.ComponentType<AvatarProps>;
1034
+ UserItemComponent?: React.ComponentType<UserPickerItemProps>;
1035
+
1036
+ /** i18n labels */
1037
+ title?: string;
1038
+ directTabLabel?: string;
1039
+ groupTabLabel?: string;
1040
+ groupNameLabel?: string;
1041
+ groupNamePlaceholder?: string;
1042
+ groupDescriptionLabel?: string;
1043
+ groupDescriptionPlaceholder?: string;
1044
+ groupPublicLabel?: string;
1045
+ groupAvatarLabel?: string;
1046
+ userSearchPlaceholder?: string;
1047
+ cancelButtonLabel?: string;
1048
+ createButtonLabel?: string;
1049
+ creatingButtonLabel?: string;
1050
+
1051
+ /** File upload configuration for group channel images */
1052
+ imageAccept?: string;
1053
+ maxImageSize?: number; // bytes
1054
+ maxImageSizeError?: string;
1055
+ };