@box/user-selector 2.2.23 → 3.0.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.
@@ -1 +1 @@
1
- import{t as buildOptionsWithOverlay}from"./buildOptionsWithOverlay.js";import messages from"../esm/lib/messages.js";import{t as UserSelectorComboboxOption}from"./user-selector-list-item.js";import{isUserContactType}from"../esm/lib/utils/isUserContactType.js";import{defaultRenderCustomOption}from"../esm/lib/utils/defaultRenderCustomOption.js";import{getInitials}from"../esm/lib/utils/getInitials.js";import{forwardRef,useCallback}from"react";import clsx from"clsx";import{useIntl}from"react-intl";import{Avatar,Combobox,Divider,IconBadge,Text}from"@box/blueprint-web";import{ComboboxWithApi}from"@box/combobox-with-api";import{jsx}from"react/jsx-runtime";import '../styles/user-selector.css';var user_selector_module_default={overlayHeader:`_overlayHeader_u0715_1`,overlaySeparatorOption:`_overlaySeparatorOption_u0715_8`,textArea:`_textArea_u0715_15`},UserSelector=forwardRef((props,forwardedRef)=>{let{fetchedAvatarUrls,fetchUsers,onSelectedUsersChange,selectedUsers,variant,className,defaultOptions,overlayDividerIndex,overlayTitle,renderCustomOption=defaultRenderCustomOption,...rest}=props,intl=useIntl(),renderAvatar=option=>{if(!(option.value===`__combobox_overlay_header__`||option.value===`__combobox_overlay_separator__`)){if(isUserContactType(option)){let{name,isExternalUser,id}=option,avatar=jsx(Avatar,{alt:intl.formatMessage(messages.userAvatar),colorIndex:id,src:fetchedAvatarUrls[id],text:getInitials(name)});return isExternalUser?jsx(IconBadge,{offset:{right:-4,bottom:-2},size:`medium`,variant:`collaborator-external`,children:avatar}):avatar}return jsx(Avatar,{...option.avatar})}},displayChipVariant=option=>{if(!isUserContactType(option))return option.chipVariant},fetcherWithOverlay=useCallback(async inputValue=>{let options=await fetchUsers(inputValue);if(!overlayTitle||overlayDividerIndex==null||overlayDividerIndex<0)return options;let selectedValues=new Set(selectedUsers.map(s=>s.value)),visibleOptions=options.filter(opt=>!selectedValues.has(opt.value)),beforeCount=options.slice(0,overlayDividerIndex+1).filter(opt=>!selectedValues.has(opt.value)).length;return buildOptionsWithOverlay(visibleOptions,overlayTitle,beforeCount>0?beforeCount-1:-1)},[fetchUsers,overlayTitle,overlayDividerIndex,selectedUsers]);return jsx(ComboboxWithApi,{as:`input`,className:clsx(className,variant===`textarea`&&user_selector_module_default.textArea),defaultOptions,displayAvatar:renderAvatar,displayChipVariant,displayValue:option=>option.name,fetcher:fetcherWithOverlay,freeInput:!1,loadingAriaLabel:intl.formatMessage(messages.loading),multiselect:!0,noResultMessage:intl.formatMessage(messages.noResults),onValueChange:onSelectedUsersChange,renderOption:option=>option.value===`__combobox_overlay_header__`&&`label`in option?jsx(Combobox.Option,{className:user_selector_module_default.overlayHeader,disabled:!0,value:option.value,children:jsx(Text,{as:`span`,variant:`bodySmallSemibold`,color:`textOnLightSecondary`,children:option.label})}):option.value===`__combobox_overlay_separator__`?jsx(Combobox.Option,{className:user_selector_module_default.overlaySeparatorOption,disabled:!0,value:option.value,children:jsx(Divider,{"aria-hidden":!0})}):jsx(UserSelectorComboboxOption,{renderAvatar,renderCustomOption,userContact:option}),value:selectedUsers,ref:forwardedRef,...rest})});export{UserSelector as t};
1
+ import"../esm/lib/constants.js";import{buildOptionsWithSuggestedOptions}from"../esm/lib/utils/buildOptionsWithSuggestedOptions.js";import messages from"../esm/lib/messages.js";import{t as UserSelectorComboboxOption}from"./user-selector-list-item.js";import{isUserContactType}from"../esm/lib/utils/isUserContactType.js";import{defaultRenderCustomOption}from"../esm/lib/utils/defaultRenderCustomOption.js";import{getInitials}from"../esm/lib/utils/getInitials.js";import{forwardRef,useCallback}from"react";import clsx from"clsx";import{useIntl}from"react-intl";import{Avatar,Combobox,Divider,IconBadge,Text}from"@box/blueprint-web";import{ComboboxWithApi}from"@box/combobox-with-api";import{jsx}from"react/jsx-runtime";import '../styles/user-selector.css';var user_selector_module_default={overlayHeader:`_overlayHeader_u0715_1`,overlaySeparatorOption:`_overlaySeparatorOption_u0715_8`,textArea:`_textArea_u0715_15`},isOverlayOption=option=>option.value===`__combobox_overlay_header__`||option.value===`__combobox_overlay_separator__`,UserSelector=forwardRef((props,forwardedRef)=>{let{fetchedAvatarUrls,fetchUsers,onSelectedUsersChange,selectedUsers,variant,className,defaultOptions,suggestedOptions,renderCustomOption=defaultRenderCustomOption,...rest}=props,intl=useIntl(),renderAvatar=option=>{if(!isOverlayOption(option)){if(isUserContactType(option)){let{name,isExternalUser,id}=option,avatar=jsx(Avatar,{alt:intl.formatMessage(messages.userAvatar),colorIndex:id,src:fetchedAvatarUrls[id],text:getInitials(name)});return isExternalUser?jsx(IconBadge,{offset:{right:-4,bottom:-2},size:`medium`,variant:`collaborator-external`,children:avatar}):avatar}return jsx(Avatar,{...option.avatar})}},displayChipVariant=option=>{if(!isOverlayOption(option)&&!isUserContactType(option))return option.chipVariant},getOptionsWithSuggestions=useCallback((options,inputValue)=>!suggestedOptions||suggestedOptions.length===0?options:buildOptionsWithSuggestedOptions(options,suggestedOptions,selectedUsers,inputValue),[selectedUsers,suggestedOptions]);return jsx(ComboboxWithApi,{as:`input`,className:clsx(className,variant===`textarea`&&user_selector_module_default.textArea),defaultOptions,displayAvatar:renderAvatar,displayChipVariant,displayValue:option=>isOverlayOption(option)?``:option.name,fetcher:fetchUsers,freeInput:!1,loadingAriaLabel:intl.formatMessage(messages.loading),multiselect:!0,noResultMessage:intl.formatMessage(messages.noResults),onValueChange:onSelectedUsersChange,renderOption:option=>isOverlayOption(option)?option.value===`__combobox_overlay_header__`?jsx(Combobox.Option,{className:user_selector_module_default.overlayHeader,disabled:!0,value:option.value,children:jsx(Text,{as:`span`,variant:`bodySmallSemibold`,color:`textOnLightSecondary`,children:intl.formatMessage(messages.suggestedOptionsTitle)})}):jsx(Combobox.Option,{className:user_selector_module_default.overlaySeparatorOption,disabled:!0,value:option.value,children:jsx(Divider,{"aria-hidden":!0})}):jsx(UserSelectorComboboxOption,{renderAvatar,renderCustomOption,userContact:option}),transformOptions:getOptionsWithSuggestions,value:selectedUsers,ref:forwardedRef,...rest})});export{UserSelector as t};
package/dist/esm/index.js CHANGED
@@ -1 +1 @@
1
- import{n as OVERLAY_HEADER_VALUE,r as OVERLAY_SEPARATOR_VALUE}from"../chunks/buildOptionsWithOverlay.js";import{UserSelectorContainer}from"./lib/user-selector-container.js";import{UserSelectorHeadless}from"./lib/user-selector-headless/user-selector-headless.js";export{OVERLAY_HEADER_VALUE,OVERLAY_SEPARATOR_VALUE,UserSelectorContainer,UserSelectorHeadless};
1
+ import{UserSelectorContainer}from"./lib/user-selector-container.js";import{UserSelectorHeadless}from"./lib/user-selector-headless/user-selector-headless.js";export{UserSelectorContainer,UserSelectorHeadless};
@@ -0,0 +1 @@
1
+ var DEFAULT_MAX_GAPS=2,DEFAULT_MAX_SUGGESTIONS=3,DEFAULT_MIN_CHARACTERS=3,OVERLAY_HEADER_VALUE=`__combobox_overlay_header__`,OVERLAY_SEPARATOR_VALUE=`__combobox_overlay_separator__`;export{DEFAULT_MAX_GAPS,DEFAULT_MAX_SUGGESTIONS,DEFAULT_MIN_CHARACTERS,OVERLAY_HEADER_VALUE,OVERLAY_SEPARATOR_VALUE};
@@ -1 +1 @@
1
- import{defineMessages}from"react-intl";var messages=defineMessages({loading:{id:`groupSharedFeatures.userSelector.loading`,defaultMessage:`Loading`},noResults:{id:`groupSharedFeatures.userSelector.noResults`,defaultMessage:`No results`},tryAgain:{id:`groupSharedFeatures.userSelector.tryAgain`,defaultMessage:`Try again`},userAvatar:{id:`groupSharedFeatures.userSelector.userAvatar`,defaultMessage:`User avatar`},userName:{id:`groupSharedFeatures.userSelector.userName`,defaultMessage:`{name} {isCurrentUser, select, true { (me)} other {}}`},errorMessage:{id:`groupSharedFeatures.userSelector.errorMessage`,defaultMessage:`We were unable to load users`},errorMessageDescription:{id:`groupSharedFeatures.userSelector.errorMessageDescription`,defaultMessage:`Please try again later`},emptyMessage:{id:`groupSharedFeatures.userSelector.emptyMessage`,defaultMessage:`NO COLLABORATORS FOUND`},inviteCollaborators:{id:`groupSharedFeatures.userSelector.inviteCollaborators`,defaultMessage:`Invite Collaborators`},inviteCollaboratorsTooltip:{id:`groupSharedFeatures.userSelector.inviteCollaboratorsTooltip`,defaultMessage:`This person doesn't have access and won't be notified unless invited.`}});export{messages as default};
1
+ import{defineMessages}from"react-intl";var messages=defineMessages({loading:{id:`groupSharedFeatures.userSelector.loading`,defaultMessage:`Loading`},noResults:{id:`groupSharedFeatures.userSelector.noResults`,defaultMessage:`No results`},suggestedOptionsTitle:{id:`groupSharedFeatures.userSelector.suggestedOptionsTitle`,defaultMessage:`SUGGESTED`},tryAgain:{id:`groupSharedFeatures.userSelector.tryAgain`,defaultMessage:`Try again`},userAvatar:{id:`groupSharedFeatures.userSelector.userAvatar`,defaultMessage:`User avatar`},userName:{id:`groupSharedFeatures.userSelector.userName`,defaultMessage:`{name} {isCurrentUser, select, true { (me)} other {}}`},errorMessage:{id:`groupSharedFeatures.userSelector.errorMessage`,defaultMessage:`We were unable to load users`},errorMessageDescription:{id:`groupSharedFeatures.userSelector.errorMessageDescription`,defaultMessage:`Please try again later`},emptyMessage:{id:`groupSharedFeatures.userSelector.emptyMessage`,defaultMessage:`NO COLLABORATORS FOUND`},inviteCollaborators:{id:`groupSharedFeatures.userSelector.inviteCollaborators`,defaultMessage:`Invite Collaborators`},inviteCollaboratorsTooltip:{id:`groupSharedFeatures.userSelector.inviteCollaboratorsTooltip`,defaultMessage:`This person doesn't have access and won't be notified unless invited.`}});export{messages as default};
@@ -1 +1 @@
1
- import{isUserContactType}from"./utils/isUserContactType.js";import{t as UserSelector}from"../../chunks/user-selector.js";import{defaultRenderCustomOption}from"./utils/defaultRenderCustomOption.js";import{useCallback,useEffect,useRef,useState}from"react";import{jsx}from"react/jsx-runtime";var UserSelectorContainer=({fetchAvatarUrls,fetchUsers,onSelectedUsersChange,selectedUsers,renderCustomOption=defaultRenderCustomOption,inputRef,...rest})=>{let isFirstRender=useRef(!0),[fetchedOptions,setFetchedOptions]=useState([]),[fetchedAvatarUrls,setFetchedAvatarUrls]=useState({}),fetchOptions=useCallback(async inputVal=>{let options=await fetchUsers(inputVal);return setFetchedOptions(options),options},[fetchUsers,setFetchedOptions]),getAvatarUrls=useCallback(async users=>{try{setFetchedAvatarUrls(await fetchAvatarUrls(users))}catch{setFetchedAvatarUrls({})}},[fetchAvatarUrls,setFetchedAvatarUrls]);return useEffect(()=>{isFirstRender.current&&(isFirstRender.current=!1,selectedUsers.length>0&&setFetchedOptions(selectedUsers.filter(isUserContactType)))},[selectedUsers,setFetchedOptions]),useEffect(()=>{fetchedOptions.length>0&&getAvatarUrls(fetchedOptions.filter(isUserContactType))},[fetchedOptions,getAvatarUrls]),jsx(UserSelector,{fetchedAvatarUrls,fetchUsers:fetchOptions,onSelectedUsersChange,renderCustomOption,selectedUsers,ref:inputRef,...rest})};export{UserSelectorContainer};
1
+ import{isUserContactType}from"./utils/isUserContactType.js";import{defaultRenderCustomOption}from"./utils/defaultRenderCustomOption.js";import{t as UserSelector}from"../../chunks/user-selector.js";import{dedupeUserContactsById}from"./utils/dedupeUserContactsById.js";import{useCallback,useEffect,useRef,useState}from"react";import{jsx}from"react/jsx-runtime";var UserSelectorContainer=({fetchAvatarUrls,fetchUsers,onSelectedUsersChange,selectedUsers,suggestedOptions,renderCustomOption=defaultRenderCustomOption,inputRef,...rest})=>{let isFirstRender=useRef(!0),[fetchedOptions,setFetchedOptions]=useState([]),[fetchedAvatarUrls,setFetchedAvatarUrls]=useState({}),fetchOptions=useCallback(async inputVal=>{let options=await fetchUsers(inputVal);return setFetchedOptions(options),options},[fetchUsers,setFetchedOptions]),getAvatarUrls=useCallback(async users=>{try{setFetchedAvatarUrls(await fetchAvatarUrls(users))}catch{setFetchedAvatarUrls({})}},[fetchAvatarUrls,setFetchedAvatarUrls]);return useEffect(()=>{isFirstRender.current&&(isFirstRender.current=!1,selectedUsers.length>0&&setFetchedOptions(selectedUsers.filter(isUserContactType)))},[selectedUsers,setFetchedOptions]),useEffect(()=>{let userOptions=dedupeUserContactsById([...fetchedOptions,...suggestedOptions??[]].filter(isUserContactType));userOptions.length>0&&getAvatarUrls(userOptions)},[fetchedOptions,getAvatarUrls,suggestedOptions]),jsx(UserSelector,{fetchedAvatarUrls,fetchUsers:fetchOptions,onSelectedUsersChange,renderCustomOption,selectedUsers,suggestedOptions,ref:inputRef,...rest})};export{UserSelectorContainer};
@@ -0,0 +1 @@
1
+ import{OVERLAY_HEADER_VALUE,OVERLAY_SEPARATOR_VALUE}from"../constants.js";var normalizeFuzzyMatchValue=value=>value.toLowerCase().replace(/\s/g,``),getEmailAlias=email=>{let separatorIndex=email.indexOf(`@`);return separatorIndex===-1?``:email.substring(0,separatorIndex)},dedupeSortedOptionsById=sortedOptions=>{let seenIds=new Set;return sortedOptions.filter(sortedOption=>sortedOption.id===void 0?!0:seenIds.has(sortedOption.id)?!1:(seenIds.add(sortedOption.id),!0))},isFuzzyMatch=(search,content,minCharacters=3,maxGaps=2)=>{if(!content)return!1;let uniformContent=normalizeFuzzyMatchValue(content),uniformSearch=normalizeFuzzyMatchValue(search),contentLength=uniformContent.length,searchLength=uniformSearch.length;if(searchLength<minCharacters||searchLength>contentLength)return!1;let totalScore=0;for(let i=0;i<contentLength&&!(contentLength-i<searchLength);i+=1){let searchIndex=0,currentScore=0,subScore=0;for(let j=i;j<contentLength;j+=1)uniformContent[j]===uniformSearch[searchIndex]?(searchIndex+=1,currentScore+=1+currentScore):currentScore=0,subScore+=currentScore;if(searchIndex!==searchLength)break;subScore>totalScore&&(totalScore=subScore)}if(totalScore===0)return!1;let maxGroups=Math.min(maxGaps,searchLength),minScore=Math.max(maxGroups*2**Math.floor(searchLength/maxGroups-1),searchLength+1);return totalScore>=minScore},getDescendingSortedScore=(optionA,optionB)=>optionB.userScore-optionA.userScore,getMatchingSuggestedOptions=(options,suggestedOptions)=>suggestedOptions.reduce((matches,suggestedOption)=>{let option=options.find(currentOption=>currentOption.id===suggestedOption.id);return option&&matches.push({id:option.id,option,userScore:suggestedOption.userScore}),matches},[]),getFuzzySuggestedCollaborators=({exactMatchIds,inputValue,selectedIds,suggestedOptions})=>{if(!inputValue)return[];let normalizedInput=normalizeFuzzyMatchValue(inputValue);return suggestedOptions.filter(suggestedOption=>{if(selectedIds.has(suggestedOption.id)||exactMatchIds.has(suggestedOption.id))return!1;let nameMatches=isFuzzyMatch(inputValue,suggestedOption.name),emailAlias=suggestedOption.email?normalizeFuzzyMatchValue(getEmailAlias(suggestedOption.email)):``,emailMatches=!!emailAlias&&normalizedInput.length>=3&&emailAlias.includes(normalizedInput);return nameMatches||emailMatches}).map(suggestedOption=>({id:suggestedOption.id,option:suggestedOption,userScore:suggestedOption.userScore}))};function buildOptionsWithSuggestedOptions(options,suggestedOptions,selectedOptions,inputValue){let selectedIds=new Set(selectedOptions.map(({id})=>id)),matchingSuggestedOptions=getMatchingSuggestedOptions(options,suggestedOptions),exactMatchIds=new Set(matchingSuggestedOptions.reduce((ids,{id})=>(id!==void 0&&ids.push(id),ids),[])),eligibleMatchingSuggestedOptions=dedupeSortedOptionsById([...matchingSuggestedOptions].sort(getDescendingSortedScore).filter(({id})=>!selectedIds.has(id))),fuzzySuggestedOptions=eligibleMatchingSuggestedOptions.length>=3?[]:dedupeSortedOptionsById(getFuzzySuggestedCollaborators({exactMatchIds,inputValue,selectedIds,suggestedOptions}).sort(getDescendingSortedScore)),visibleSuggestedOptions=dedupeSortedOptionsById([...eligibleMatchingSuggestedOptions,...fuzzySuggestedOptions]).slice(0,3).map(({option})=>option);if(visibleSuggestedOptions.length===0)return options;let suggestedIds=new Set(visibleSuggestedOptions.map(({id})=>id)),visibleOptions=options.filter(option=>!selectedIds.has(option.id)&&!suggestedIds.has(option.id)),headerOption={disabled:!0,value:OVERLAY_HEADER_VALUE};if(visibleOptions.length===0)return[headerOption,...visibleSuggestedOptions];let dividerOption={disabled:!0,value:OVERLAY_SEPARATOR_VALUE};return[headerOption,...visibleSuggestedOptions,dividerOption,...visibleOptions]}export{buildOptionsWithSuggestedOptions};
@@ -0,0 +1 @@
1
+ var dedupeUserContactsById=users=>{let seenUserIds=new Set;return users.filter(user=>seenUserIds.has(user.id)?!1:(seenUserIds.add(user.id),!0))};export{dedupeUserContactsById};
@@ -0,0 +1,5 @@
1
+ export declare const DEFAULT_MAX_GAPS = 2;
2
+ export declare const DEFAULT_MAX_SUGGESTIONS = 3;
3
+ export declare const DEFAULT_MIN_CHARACTERS = 3;
4
+ export declare const OVERLAY_HEADER_VALUE = "__combobox_overlay_header__";
5
+ export declare const OVERLAY_SEPARATOR_VALUE = "__combobox_overlay_separator__";
@@ -9,6 +9,11 @@ declare const messages: {
9
9
  description: string;
10
10
  id: string;
11
11
  };
12
+ suggestedOptionsTitle: {
13
+ defaultMessage: string;
14
+ description: string;
15
+ id: string;
16
+ };
12
17
  tryAgain: {
13
18
  defaultMessage: string;
14
19
  description: string;
@@ -1,7 +1,5 @@
1
1
  import { ReactElement, SVGProps } from 'react';
2
- /** Option value for overlay header/divider rows (non-selectable). */
3
- export declare const OVERLAY_HEADER_VALUE = "__combobox_overlay_header__";
4
- export declare const OVERLAY_SEPARATOR_VALUE = "__combobox_overlay_separator__";
2
+ import { OVERLAY_HEADER_VALUE, OVERLAY_SEPARATOR_VALUE } from './constants';
5
3
  export type UserContactType = {
6
4
  chipVariant?: 'error' | 'default' | 'variable';
7
5
  email: string;
@@ -13,6 +11,13 @@ export type UserContactType = {
13
11
  value: string;
14
12
  type?: string;
15
13
  };
14
+ export interface SuggestedCollaborator extends Partial<UserContactType> {
15
+ id: number;
16
+ email: string;
17
+ name: string;
18
+ userScore: number;
19
+ value: string;
20
+ }
16
21
  export type CustomUserContactType = {
17
22
  name: string;
18
23
  subtitle?: string;
@@ -31,16 +36,13 @@ export type CustomUserContactType = {
31
36
  hideOnClick?: boolean;
32
37
  chipVariant?: 'default' | 'error' | 'variable';
33
38
  };
34
- export type OverlayHeaderOption = {
35
- name: string;
36
- value: typeof OVERLAY_HEADER_VALUE;
37
- label: string;
38
- };
39
- export type OverlaySeparatorOption = {
40
- name: string;
41
- value: typeof OVERLAY_SEPARATOR_VALUE;
42
- };
43
39
  export type UserSelectorOption = UserContactType | CustomUserContactType;
40
+ type OverlayValue = typeof OVERLAY_HEADER_VALUE | typeof OVERLAY_SEPARATOR_VALUE;
41
+ export type OverlayOption = {
42
+ disabled: true;
43
+ value: OverlayValue;
44
+ };
44
45
  export type FetchedAvatarUrls = {
45
46
  [userId: string]: string;
46
47
  };
48
+ export {};
@@ -16,4 +16,4 @@ export type UserSelectorContainerProps<T extends UserSelectorOption = UserContac
16
16
  */
17
17
  inputRef?: Ref<HTMLInputElement>;
18
18
  };
19
- export declare const UserSelectorContainer: <T extends UserSelectorOption = UserContactType>({ fetchAvatarUrls, fetchUsers, onSelectedUsersChange, selectedUsers, renderCustomOption, inputRef, ...rest }: UserSelectorContainerProps<T>) => import("react/jsx-runtime").JSX.Element;
19
+ export declare const UserSelectorContainer: <T extends UserSelectorOption = UserContactType>({ fetchAvatarUrls, fetchUsers, onSelectedUsersChange, selectedUsers, suggestedOptions, renderCustomOption, inputRef, ...rest }: UserSelectorContainerProps<T>) => import("react/jsx-runtime").JSX.Element;
@@ -1,18 +1,16 @@
1
- import { ForwardedRef, ReactElement } from 'react';
1
+ import { ForwardedRef, ReactElement, ReactNode } from 'react';
2
2
  import { AvatarProps } from '@box/blueprint-web';
3
3
  import { ComboboxWithApiProps } from '@box/combobox-with-api';
4
- import { CustomUserContactType, FetchedAvatarUrls, UserContactType, UserSelectorOption } from './types';
4
+ import { CustomUserContactType, FetchedAvatarUrls, OverlayOption, UserContactType, UserSelectorOption } from './types';
5
5
  export type BaseUserSelectorProps<T extends UserSelectorOption> = {
6
6
  /**
7
7
  * Default options to show when the input is empty
8
8
  */
9
9
  defaultOptions?: T[];
10
10
  /**
11
- * 0-based index (relative to the option list only) after which to insert a divider row.
12
- * The option list includes selected options when they are shown in the dropdown.
13
- * E.g. 1 = divider after the second option.
11
+ * Suggested options to show before regular options in the dropdown
14
12
  */
15
- overlayDividerIndex?: number;
13
+ suggestedOptions?: T[];
16
14
  /**
17
15
  * A dictionary with user ids mapped to avatar URLs
18
16
  */
@@ -21,10 +19,6 @@ export type BaseUserSelectorProps<T extends UserSelectorOption> = {
21
19
  * Function to fetch users when the input value changes
22
20
  */
23
21
  fetchUsers: (inputValue: string) => Promise<Array<T>>;
24
- /**
25
- * Label shown as the first row in the default options dropdown (e.g. "SUGGESTED").
26
- */
27
- overlayTitle?: string;
28
22
  /**
29
23
  * onChange for controlled selected users state
30
24
  */
@@ -32,7 +26,7 @@ export type BaseUserSelectorProps<T extends UserSelectorOption> = {
32
26
  /**
33
27
  * Function to render the custom option
34
28
  */
35
- renderCustomOption?: (option: CustomUserContactType, renderAvatar: () => ReactElement<AvatarProps>) => React.ReactNode;
29
+ renderCustomOption?: (option: CustomUserContactType, renderAvatar: () => ReactElement<AvatarProps>) => ReactNode;
36
30
  /**
37
31
  * Controlled selected users state
38
32
  */
@@ -42,7 +36,7 @@ export type BaseUserSelectorProps<T extends UserSelectorOption> = {
42
36
  */
43
37
  variant?: 'input' | 'textarea';
44
38
  };
45
- export type UserSelectorProps<T extends UserSelectorOption = UserContactType> = BaseUserSelectorProps<T> & Omit<ComboboxWithApiProps<true, false, T>, 'as' | 'fetcher' | 'freeInput' | 'hideSelectedOptions' | 'loadingAriaLabel' | 'multiselect' | 'onValueChange' | 'value'>;
39
+ export type UserSelectorProps<T extends UserSelectorOption = UserContactType> = BaseUserSelectorProps<T> & Omit<ComboboxWithApiProps<true, false, T, OverlayOption, UserContactType>, 'as' | 'fetcher' | 'freeInput' | 'hideSelectedOptions' | 'loadingAriaLabel' | 'multiselect' | 'onValueChange' | 'transformOptions' | 'value'>;
46
40
  declare const UserSelectorInner: <T extends UserSelectorOption = UserContactType>(props: UserSelectorProps<T>, forwardedRef: ForwardedRef<HTMLInputElement>) => import("react/jsx-runtime").JSX.Element;
47
41
  declare const UserSelector: <T extends UserSelectorOption = UserContactType>(props: UserSelectorProps<T> & {
48
42
  ref?: ForwardedRef<HTMLInputElement>;
@@ -0,0 +1,6 @@
1
+ import { OverlayOption, SuggestedCollaborator, UserContactType } from '../types';
2
+ /**
3
+ * Builds a flat options array for Blueprint Combobox from selectable options
4
+ * and a semantic suggested options group.
5
+ */
6
+ export declare function buildOptionsWithSuggestedOptions<T extends UserContactType>(options: T[], suggestedOptions: SuggestedCollaborator[], selectedOptions: T[], inputValue: string): Array<T | SuggestedCollaborator | OverlayOption>;
@@ -0,0 +1,2 @@
1
+ import { UserContactType } from '../types';
2
+ export declare const dedupeUserContactsById: (users: UserContactType[]) => UserContactType[];
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@box/user-selector",
3
- "version": "2.2.23",
3
+ "version": "3.0.0",
4
4
  "peerDependencies": {
5
5
  "@box/blueprint-web": "^16.20.6",
6
6
  "@box/blueprint-web-assets": "^5.7.13",
7
- "@box/combobox-with-api": "^2.2.23",
7
+ "@box/combobox-with-api": "^2.2.24",
8
8
  "lodash": "^4.17.15",
9
9
  "react": "^17.0.0 || ^18.0.0",
10
10
  "react-dom": "^17.0.0 || ^18.0.0",
@@ -13,7 +13,7 @@
13
13
  "devDependencies": {
14
14
  "@box/blueprint-web": "^16.20.6",
15
15
  "@box/blueprint-web-assets": "^5.7.13",
16
- "@box/combobox-with-api": "^2.2.23",
16
+ "@box/combobox-with-api": "^2.2.24",
17
17
  "@box/storybook-utils": "^1.2.22",
18
18
  "@types/lodash": "^4.17.14",
19
19
  "react": "^18.3.0",
@@ -1 +0,0 @@
1
- var OVERLAY_HEADER_VALUE=`__combobox_overlay_header__`,OVERLAY_SEPARATOR_VALUE=`__combobox_overlay_separator__`;function buildOptionsWithOverlay(options,overlayTitle,overlayDividerIndex){if(!options||options.length===0)return options??void 0;let result=[...options];if(overlayDividerIndex>=0&&overlayDividerIndex<result.length-1){let dividerOption={name:``,value:OVERLAY_SEPARATOR_VALUE};result=[...result.slice(0,overlayDividerIndex+1),dividerOption,...result.slice(overlayDividerIndex+1)]}return overlayTitle&&result.length>0&&overlayDividerIndex>=0&&(result=[{name:``,value:OVERLAY_HEADER_VALUE,label:overlayTitle},...result]),result}export{OVERLAY_HEADER_VALUE as n,OVERLAY_SEPARATOR_VALUE as r,buildOptionsWithOverlay as t};
@@ -1 +0,0 @@
1
- import{t as buildOptionsWithOverlay}from"../../../chunks/buildOptionsWithOverlay.js";export{buildOptionsWithOverlay};
@@ -1,8 +0,0 @@
1
- import { OverlayHeaderOption, OverlaySeparatorOption, UserSelectorOption } from '../types';
2
- /**
3
- * Builds an options array with optional overlay header and divider for UserSelector.
4
- * Used for both default options and fetcher results.
5
- * Divider is inserted after the given index (relative to options only). Then overlay header is prepended.
6
- * Returns the same reference when no overlay is needed or when options are undefined/empty.
7
- */
8
- export declare function buildOptionsWithOverlay<T extends UserSelectorOption>(options: T[] | undefined, overlayTitle: string, overlayDividerIndex: number): (T | OverlayHeaderOption | OverlaySeparatorOption)[] | undefined;