• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ringcentral / ringcentral-js-widgets / 21017357600

15 Jan 2026 02:19AM UTC coverage: 61.994% (-1.1%) from 63.06%
21017357600

push

github

web-flow
misc: sync features and bugfixes from 8f9fbb5dfe (#1784)

* misc: sync crius

* chore: update babel-setting deps

* feat(core): update logger and add fromWatch

* misc: fix tsconfig

* misc: fix tsconfig

* misc: update eslint-settings and new eslint-plugin-crius package

* chore: remove ci and nx from package.json

* feat(i18n): new getAcceptLocaleMap and preudo string support

* chore: add preudo i18n

* misc(locale-loader): convert to ts, new format support

* misc(locale-settings): use ts

* chore: add format test in phone number lib

* feat(react-hooks): add more hooks

* chore: add comments

* misc: add more mock files

* misc: update test utils

* misc: update utils

* chore: update tsconfig

* misc: update i18n string, and convert to ts

* feat: update ui components, support emoji input, new video setting ui

* feat: new rcvideo v2 module

* feat: use new subscription register api

* misc(commons): update enums/interfaces

* misc: update Analytics lib

* misc: upgrade uuid and update import

* misc(commons): update formatDuration lib

* misc(test): add test steps

* chore: update tests and more feature tests

* misc: update demo project

* misc: update cli template

* misc: fix deps issue

* misc: remove glip widgets package

* misc: fix wrong import path

* misc: limit jest worker memory

* chore: use npm trusted-publishers

10285 of 18150 branches covered (56.67%)

Branch coverage included in aggregate %.

986 of 2186 new or added lines in 228 files covered. (45.11%)

44 existing lines in 23 files now uncovered.

17404 of 26514 relevant lines covered (65.64%)

167640.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.32
/packages/ringcentral-widgets/components/SelectListBasic/SelectListBasic.tsx
1
import { emptyFn, format } from '@ringcentral-integration/utils';
2
import {
3
  RcIcon,
4
  RcTextField,
5
  useDepsChange,
6
  useRefState,
7
} from '@ringcentral/juno';
8
import { Search } from '@ringcentral/juno-icon';
9
import clsx from 'clsx';
10
import type { FunctionComponent } from 'react';
11
import React, { useEffect, useRef, useState } from 'react';
12

13
import { TOOLTIP_LONG_DELAY_TIME } from '../../lib/toolTipDelayTime';
14
import { AnimationPanel } from '../AnimationPanel';
15
import BackHeader from '../BackHeaderV2';
16
import { Tooltip } from '../Rcui/Tooltip';
17
import type { ListViewProps } from '../SelectList/ListView';
18
import selectListI18n from '../SelectList/i18n';
19

20
import i18n from './i18n';
21
import styles from './styles.scss';
22

23
export type SelectListBasicProps = {
24
  title: string;
25
  options?: ListViewProps['options'];
26
  otherOptions?: object[];
27
  associatedOptions?: object[];
28
  showMatched?: boolean;
29
  showOtherSection?: boolean;
30
  showAssociatedSection?: boolean;
31
  showRecentlySection?: boolean;
32
  placeholder?: string;
33
  searchOption: (option: any, text: string) => any;
34
  rightIcon?: JSX.Element;
35
  currentLocale: string;
36
  matchedTitle?: string;
37
  otherTitle?: string;
38
  associatedTitle?: string;
39
  recentlyTitle?: string;
40
  renderListView?: (
41
    data: any,
42
    type: 'matched' | 'other' | 'associated' | 'custom' | 'recently',
43
    filter: string,
44
    // TODO: need type check
45
    scrollCheck: any,
46
  ) => React.ReactNode;
47
  open?: boolean;
48
  setOpen?: (...args: any[]) => any;
49
  scrollCheck?: (
50
    scrollElmRef: any,
51
    matchElmRef: any,
52
    elm: any,
53
    type: any,
54
  ) => any;
55
  selectListBasicClassName?: string;
56
  backHeaderClassName?: string;
57
  listContainerClassName?: string;
58
  classes?: {
59
    searchInput?: string;
60
    noResult?: string;
61
    placeholder?: string;
62
  };
63
  onBackClick?: () => any;
64
  contactSearch?: (arg: {
65
    searchString: string;
66
    fromField: SelectListBasicProps['field'];
67
  }) => Promise<void>;
68
  field?: string;
69
  foundFromServerTitle?: string;
70
  showFoundFromServer?: boolean;
71
  foundFromServerEntities?: any[];
72
  recentlyEntities?: any[];
73
  serverEntitiesClientFilter?: 'none';
74
  appName?: string;
75
  isSearching?: boolean;
76
  setShowSearchFromServerHint?: (state: boolean) => any;
77
  showSearchFromServerHint?: boolean;
78
  disabled?: boolean;
79
};
80

81
const defaultRenderListView = () => {
2✔
82
  return null;
×
83
};
84

85
export const SelectListBasic: FunctionComponent<SelectListBasicProps> = ({
2✔
86
  options = [],
×
87
  otherOptions = [],
×
88
  associatedOptions = [],
11✔
89
  showOtherSection = true,
11✔
90
  showAssociatedSection = false,
11✔
91
  showRecentlySection = false,
11✔
92
  placeholder = '',
×
93
  rightIcon = null,
11✔
94
  setOpen = emptyFn,
11✔
95
  open = false,
×
96
  renderListView = defaultRenderListView,
×
97
  scrollCheck = emptyFn,
11✔
98
  selectListBasicClassName = null,
11✔
99
  backHeaderClassName = null,
11✔
100
  listContainerClassName = null,
11✔
101
  classes = {},
11✔
102
  onBackClick = undefined,
11✔
103
  matchedTitle = null,
×
104
  otherTitle = null,
×
105
  associatedTitle = null,
11✔
106
  recentlyTitle = null,
11✔
107
  contactSearch = null,
11✔
108
  field = null,
11✔
109
  foundFromServerTitle = null,
11✔
110
  showFoundFromServer = false,
11✔
111
  foundFromServerEntities = [],
11✔
112
  recentlyEntities,
113
  serverEntitiesClientFilter,
114
  appName = null,
11✔
115
  isSearching = false,
11✔
116
  disabled = false,
11✔
117
  title,
118
  searchOption,
119
  currentLocale,
120
  showMatched = true,
11✔
121
  ...rest
122
}) => {
123
  const [filterRef, setFilter] = useRefState<string>('');
11✔
124
  const [showSearchFromServerHint, setShowSearchFromServerHint] =
125
    useState(false);
11✔
126
  const scrollElmRef = useRef();
11✔
127
  const matchElmRef = useRef();
11✔
128

129
  // When open change clear filter
130
  useEffect(() => {
11✔
131
    setFilter('');
4✔
132
    setShowSearchFromServerHint(true);
4✔
133
    // eslint-disable-next-line react-hooks/exhaustive-deps
134
  }, [open]);
135
  useEffect(() => {
11✔
136
    if (isSearching) {
4!
137
      setShowSearchFromServerHint(false);
×
138
    }
139
  }, [isSearching]);
140

141
  useDepsChange(() => {
11✔
142
    // null is invalid for RcTextField at disabled status but empty string works
143
    if (disabled) setFilter('', false);
4!
144
  }, [disabled]);
145
  const filter = filterRef.current;
11✔
146

147
  // @ts-expect-error TS(2774): This condition will always return true since this ... Remove this comment to see the full error message
148
  const hasSearch = searchOption && filter;
11✔
149
  const matchOptions = hasSearch
11✔
150
    ? options.filter((option) => searchOption(option, filter))
9✔
151
    : options;
152
  const matchOtherOptions = hasSearch
11✔
153
    ? otherOptions.filter((option) => searchOption(option, filter))
6✔
154
    : otherOptions;
155
  const matchAssociatedOptions = hasSearch
11✔
156
    ? associatedOptions.filter((option) => searchOption(option, filter))
×
157
    : associatedOptions;
158
  const filteredFoundFromServerOptions =
159
    hasSearch && serverEntitiesClientFilter !== 'none'
11✔
NEW
160
      ? foundFromServerEntities.filter((option) => searchOption(option, filter))
×
161
      : foundFromServerEntities;
162

163
  const matchRecentlyOptions =
164
    hasSearch && recentlyEntities
11!
NEW
165
      ? recentlyEntities.filter((option) => searchOption(option, filter))
×
166
      : recentlyEntities;
167
  const hasResult =
168
    matchOptions.length +
11✔
169
      matchOtherOptions.length +
170
      matchAssociatedOptions.length +
171
      (matchRecentlyOptions?.length || 0) >
22✔
172
      0 ||
173
    options.length +
174
      otherOptions.length +
175
      associatedOptions.length +
176
      (recentlyEntities?.length || 0) ===
2✔
177
      0;
178
  const backHeaderOnclick = () => {
11✔
179
    setOpen(false);
×
180
    if (onBackClick) {
×
181
      return onBackClick();
×
182
    }
183
  };
184
  const foundFromServerHint = (
185
    <p className={styles.hint}>
11✔
186
      {format(i18n.getString('foundFromServerHint', currentLocale), {
187
        appName,
188
      })}
189
    </p>
190
  );
191
  const notResultFoundFromServer = (
192
    <p className={styles.loading}>
11✔
193
      {' '}
194
      {i18n.getString('notResultFoundFromServer', currentLocale)}
195
    </p>
196
  );
197
  const loading = (
198
    <p className={styles.loading}>{i18n.getString('loading', currentLocale)}</p>
11✔
199
  );
200
  const notFoundFromServer = showSearchFromServerHint
11✔
201
    ? foundFromServerHint
202
    : notResultFoundFromServer;
203
  const showLoading = isSearching ? loading : notFoundFromServer;
11!
204

205
  matchedTitle =
11✔
206
    matchedTitle || selectListI18n.getString('matched', currentLocale);
11!
207

208
  otherTitle = otherTitle || selectListI18n.getString('other', currentLocale);
11!
209

210
  foundFromServerTitle =
11✔
211
    foundFromServerTitle ||
22✔
212
    format(selectListI18n.getString('foundFromServer', currentLocale), {
213
      appName,
214
    });
215

216
  associatedTitle =
11✔
217
    associatedTitle || selectListI18n.getString('associated', currentLocale);
22✔
218

219
  recentlyTitle =
11✔
220
    recentlyTitle || selectListI18n.getString('recently', currentLocale);
22✔
221

222
  return (
11✔
223
    // @ts-expect-error TS(2322): Type 'string | null' is not assignable to type 'st... Remove this comment to see the full error message
224
    <AnimationPanel open={open} className={selectListBasicClassName} {...rest}>
225
      {open ? (
11!
226
        <>
227
          <BackHeader
228
            currentLocale={currentLocale}
229
            title={title}
230
            onBackClick={backHeaderOnclick}
231
            rightIcon={rightIcon}
232
            // @ts-expect-error TS(2322): Type 'string | null' is not assignable to type 'st... Remove this comment to see the full error message
233
            className={backHeaderClassName}
234
          />
235
          <main className={styles.main} data-sign="selectList">
236
            <Tooltip title={placeholder} enterDelay={TOOLTIP_LONG_DELAY_TIME}>
237
              <div className={clsx(styles.search, classes.searchInput)}>
238
                {!filter && (
19✔
239
                  <span
240
                    className={clsx(styles.placeholder, classes.placeholder)}
241
                  >
242
                    {placeholder}
243
                  </span>
244
                )}
245
                <RcTextField
246
                  variant="outline"
247
                  size="small"
248
                  value={filter}
249
                  fullWidth
250
                  radius="round"
251
                  InputProps={{
252
                    startAdornment: (
253
                      <RcIcon
254
                        symbol={Search}
255
                        color="neutral.f04"
256
                        size="small"
257
                      />
258
                    ),
259
                  }}
260
                  data-sign="searchBar"
261
                  onChange={(event: any) => {
262
                    if (event.target) {
3!
263
                      const value = event.target.value || '';
3!
264
                      setFilter(value);
3✔
265
                    }
266
                  }}
267
                  onKeyDown={(event) => {
268
                    // Press enter to search contacts from server
NEW
269
                    if (event.key !== 'Enter' || !showFoundFromServer) return;
×
NEW
270
                    if (typeof contactSearch === 'function') {
×
271
                      const searchString = filter ? filter.trim() : '';
×
272
                      if (searchString.length) {
×
273
                        contactSearch({
×
274
                          searchString,
275
                          // @ts-expect-error TS(2322): Type 'string | null' is not assignable to type 'st... Remove this comment to see the full error message
276
                          fromField: field,
277
                        });
278
                      }
279
                    }
280
                  }}
281
                  disabled={disabled}
282
                />
283
              </div>
284
            </Tooltip>
285
            <div
286
              className={clsx(styles.listContainer, listContainerClassName)}
287
              // @ts-expect-error TS(2322): Type 'MutableRefObject<undefined>' is not assignab... Remove this comment to see the full error message
288
              ref={scrollElmRef}
289
              data-sign="searchResult"
290
            >
291
              {hasResult || showFoundFromServer ? (
23✔
292
                <>
293
                  {showRecentlySection && (
10!
294
                    <div className={styles.text} data-sign="recently">
295
                      {recentlyTitle && (
×
296
                        <div className={styles.title}>
297
                          {recentlyTitle} ({matchRecentlyOptions?.length || 0})
×
298
                        </div>
299
                      )}
300
                      {matchRecentlyOptions &&
×
301
                        matchRecentlyOptions.length > 0 &&
302
                        renderListView(
303
                          matchRecentlyOptions,
304
                          'recently',
305
                          filter,
306
                          (elm: any, type: any) =>
NEW
307
                            scrollCheck(scrollElmRef, matchElmRef, elm, type),
×
308
                        )}
309
                    </div>
310
                  )}
311
                  {showMatched && (
20✔
312
                    <div
313
                      // @ts-expect-error TS(2322): Type 'MutableRefObject<undefined>' is not assignab... Remove this comment to see the full error message
314
                      ref={matchElmRef}
315
                      className={styles.text}
316
                      data-sign={matchedTitle}
317
                    >
318
                      {matchedTitle && (
20✔
319
                        <div className={styles.title}>
320
                          {matchedTitle} ({matchOptions.length})
321
                        </div>
322
                      )}
323
                      {matchOptions.length > 0 &&
20✔
324
                        renderListView(
325
                          matchOptions,
326
                          'matched',
327
                          filter,
328
                          (elm: any, type: any) =>
NEW
329
                            scrollCheck(scrollElmRef, matchElmRef, elm, type),
×
330
                        )}
331
                    </div>
332
                  )}
333
                  {showOtherSection && (
20✔
334
                    <div className={styles.text} data-sign={otherTitle}>
335
                      {otherTitle && (
20✔
336
                        <div className={styles.title}>
337
                          {otherTitle} ({matchOtherOptions.length})
338
                        </div>
339
                      )}
340
                      {matchOtherOptions.length > 0 &&
19✔
341
                        renderListView(
342
                          matchOtherOptions,
343
                          'other',
344
                          filter,
345
                          (elm: any, type: any) =>
346
                            scrollCheck(scrollElmRef, matchElmRef, elm, type),
×
347
                        )}
348
                    </div>
349
                  )}
350
                  {showAssociatedSection && (
10!
351
                    <div className={styles.text} data-sign="Associated">
352
                      {associatedTitle && (
×
353
                        <div className={styles.title}>
354
                          {associatedTitle} ({matchAssociatedOptions.length})
355
                        </div>
356
                      )}
357
                      {matchAssociatedOptions.length > 0 &&
×
358
                        renderListView(
359
                          matchAssociatedOptions,
360
                          'other',
361
                          filter,
362
                          (elm: any, type: any) =>
363
                            scrollCheck(scrollElmRef, matchElmRef, elm, type),
×
364
                        )}
365
                    </div>
366
                  )}
367
                  {showFoundFromServer && (
10!
368
                    <div className={styles.text} data-sign="foundFromServer">
369
                      {foundFromServerTitle && (
×
370
                        <div className={styles.title}>
371
                          {foundFromServerTitle} (
372
                          {filteredFoundFromServerOptions.length})
373
                        </div>
374
                      )}
375
                      {filteredFoundFromServerOptions &&
×
376
                      filteredFoundFromServerOptions.length > 0
377
                        ? renderListView(
378
                            filteredFoundFromServerOptions,
379
                            'custom',
380
                            filter,
381
                            (elm: any, type: any) =>
382
                              scrollCheck(scrollElmRef, matchElmRef, elm, type),
×
383
                          )
384
                        : showLoading}
385
                    </div>
386
                  )}
387
                </>
388
              ) : (
389
                <div
390
                  className={clsx(styles.search, styles.text, classes.noResult)}
391
                >
392
                  {`${i18n.getString(
393
                    'noResultFoundFor',
394
                    currentLocale,
395
                  )} "${filter}"`}
396
                </div>
397
              )}
398
            </div>
399
          </main>
400
        </>
401
      ) : null}
402
    </AnimationPanel>
403
  );
404
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc