chore: implement scroll sync

This commit is contained in:
Aaryan Khandelwal 2024-02-06 11:37:41 +05:30
parent b36afe6eab
commit b86c3b5cd6
8 changed files with 218 additions and 176 deletions

View File

@ -35,7 +35,7 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = observer((props)
// store hooks // store hooks
const { peekIssue } = useIssueDetail(); const { peekIssue } = useIssueDetail();
// chart hook // chart hook
const { activeBlock, dispatch, updateScrollTop } = useChart(); const { activeBlock, dispatch } = useChart();
// update the active block on hover // update the active block on hover
const updateActiveBlock = (block: IGanttBlock | null) => { const updateActiveBlock = (block: IGanttBlock | null) => {
@ -77,22 +77,12 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = observer((props)
}); });
}; };
const handleBlocksScroll = (e: React.UIEvent<HTMLDivElement, UIEvent>) => {
updateScrollTop(e.currentTarget.scrollTop);
const sidebarScrollContainer = document.getElementById("gantt-sidebar-scroll-container") as HTMLDivElement;
if (!sidebarScrollContainer) return;
sidebarScrollContainer.scrollTop = e.currentTarget.scrollTop;
};
return ( return (
<div <div
className="relative z-[5] mt-[72px] h-full overflow-hidden overflow-y-auto" className="absolute h-full w-full overflow-x-auto overflow-y-auto z-[1]"
style={{ width: `${itemsContainerWidth}px` }} // style={{ width: `${itemsContainerWidth}px` }}
onScroll={handleBlocksScroll}
> >
{blocks?.map((block) => { {blocks?.map((block, index) => {
// hide the block if it doesn't have start and target dates and showAllBlocks is false // hide the block if it doesn't have start and target dates and showAllBlocks is false
if (!showAllBlocks && !(block.start_date && block.target_date)) return; if (!showAllBlocks && !(block.start_date && block.target_date)) return;
@ -101,26 +91,34 @@ export const GanttChartBlocksList: FC<GanttChartBlocksProps> = observer((props)
return ( return (
<div <div
key={`block-${block.id}`} key={`block-${block.id}`}
className={cn("relative h-11", { className="absolute w-full"
"rounded bg-custom-background-80": activeBlock?.id === block.id, style={{
"rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70": height: "44px",
peekIssue?.issueId === block.data.id, top: `${index * 44}px`,
})} }}
onMouseEnter={() => updateActiveBlock(block)} onMouseEnter={() => updateActiveBlock(block)}
onMouseLeave={() => updateActiveBlock(null)} onMouseLeave={() => updateActiveBlock(null)}
> >
{isBlockVisibleOnChart ? ( <div
<ChartDraggable className={cn("relative h-full w-full", {
block={block} "rounded bg-custom-background-80": activeBlock?.id === block.id,
blockToRender={blockToRender} "rounded-l border border-r-0 border-custom-primary-70 hover:border-custom-primary-70":
handleBlock={(...args) => handleChartBlockPosition(block, ...args)} peekIssue?.issueId === block.data.id,
enableBlockLeftResize={enableBlockLeftResize} })}
enableBlockRightResize={enableBlockRightResize} >
enableBlockMove={enableBlockMove} {isBlockVisibleOnChart ? (
/> <ChartDraggable
) : ( block={block}
<ChartAddBlock block={block} blockUpdateHandler={blockUpdateHandler} /> blockToRender={blockToRender}
)} handleBlock={(...args) => handleChartBlockPosition(block, ...args)}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
/>
) : (
<ChartAddBlock block={block} blockUpdateHandler={blockUpdateHandler} />
)}
</div>
</div> </div>
); );
})} })}

View File

@ -3,6 +3,8 @@ import { Expand, Shrink } from "lucide-react";
import { useChart } from "../hooks"; import { useChart } from "../hooks";
// types // types
import { IGanttBlock, TGanttViews } from "../types"; import { IGanttBlock, TGanttViews } from "../types";
import { IMonthBlock } from "../views";
import { ScrollSyncPane } from "react-scroll-sync";
type Props = { type Props = {
blocks: IGanttBlock[] | null; blocks: IGanttBlock[] | null;
@ -17,48 +19,102 @@ type Props = {
export const GanttChartHeader: React.FC<Props> = (props) => { export const GanttChartHeader: React.FC<Props> = (props) => {
const { blocks, fullScreenMode, handleChartView, handleToday, loaderTitle, title, toggleFullScreenMode } = props; const { blocks, fullScreenMode, handleChartView, handleToday, loaderTitle, title, toggleFullScreenMode } = props;
// chart hook // chart hook
const { currentView, allViews } = useChart(); const { currentView, currentViewData, allViews, renderView } = useChart();
const monthBlocks: IMonthBlock[] = renderView;
return ( return (
<div className="relative flex w-full flex-shrink-0 flex-wrap items-center gap-2 whitespace-nowrap px-2.5 py-2 z-10"> <>
<div className="flex items-center gap-2 text-lg font-medium">{title}</div> <div className="relative flex w-full flex-shrink-0 flex-wrap items-center gap-2 whitespace-nowrap px-2.5 py-2 z-10">
<div className="flex items-center gap-2 text-lg font-medium">{title}</div>
<div className="ml-auto"> <div className="ml-auto">
<div className="ml-auto text-sm font-medium">{blocks ? `${blocks.length} ${loaderTitle}` : "Loading..."}</div> <div className="ml-auto text-sm font-medium">{blocks ? `${blocks.length} ${loaderTitle}` : "Loading..."}</div>
</div> </div>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
{allViews && {allViews &&
allViews.map((_chatView: any) => ( allViews.map((_chatView: any) => (
<div <div
key={_chatView?.key} key={_chatView?.key}
className={`cursor-pointer rounded-sm p-1 px-2 text-xs ${ className={`cursor-pointer rounded-sm p-1 px-2 text-xs ${
currentView === _chatView?.key ? `bg-custom-background-80` : `hover:bg-custom-background-90` currentView === _chatView?.key ? `bg-custom-background-80` : `hover:bg-custom-background-90`
}`} }`}
onClick={() => handleChartView(_chatView?.key)} onClick={() => handleChartView(_chatView?.key)}
> >
{_chatView?.title} {_chatView?.title}
</div> </div>
))} ))}
</div> </div>
<div className="flex items-center gap-1">
<button
type="button"
className="rounded-sm p-1 px-2 text-xs hover:bg-custom-background-80"
onClick={handleToday}
>
Today
</button>
</div>
<div className="flex items-center gap-1">
<button <button
type="button" type="button"
className="rounded-sm p-1 px-2 text-xs hover:bg-custom-background-80" className="flex items-center justify-center rounded-sm border border-custom-border-200 p-1 transition-all hover:bg-custom-background-80"
onClick={handleToday} onClick={toggleFullScreenMode}
> >
Today {fullScreenMode ? <Shrink className="h-4 w-4" /> : <Expand className="h-4 w-4" />}
</button> </button>
</div> </div>
<div className="flex w-full">
<ScrollSyncPane>
<div className="h-full flex flex-grow divide-x divide-custom-border-100/50 overflow-x-auto">
{monthBlocks?.map((block, rootIndex) => (
<div key={`month-${block?.month}-${block?.year}`} className="relative flex flex-col">
<div className="h-[60px] w-full">
<div className="relative h-[30px]">
<div className="sticky left-0 inline-flex whitespace-nowrap px-3 py-2 text-xs font-medium capitalize">
{block?.title}
</div>
</div>
<button <div className="flex h-[30px] w-full">
type="button" {block?.children?.map((monthDay, _idx) => (
className="flex items-center justify-center rounded-sm border border-custom-border-200 p-1 transition-all hover:bg-custom-background-80" <div
onClick={toggleFullScreenMode} key={`sub-title-${rootIndex}-${_idx}`}
> className="flex-shrink-0 border-b border-custom-border-200 py-1 text-center capitalize"
{fullScreenMode ? <Shrink className="h-4 w-4" /> : <Expand className="h-4 w-4" />} style={{ width: `${currentViewData?.data.width}px` }}
</button> >
</div> <div className="space-x-1 text-xs">
<span className="text-custom-text-200">{monthDay.dayData.shortTitle[0]}</span>{" "}
<span className={monthDay.today ? "rounded-full bg-custom-primary-100 px-1 text-white" : ""}>
{monthDay.day}
</span>
</div>
</div>
))}
</div>
</div>
{/* <div className="flex h-full w-full divide-x divide-custom-border-100/50">
{block?.children?.map((monthDay, index) => (
<div
key={`column-${rootIndex}-${index}`}
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
style={{ width: `${currentViewData?.data.width}px` }}
>
<div
className={cn("relative flex h-full w-full flex-1 justify-center", {
"bg-custom-background-90": ["sat", "sun"].includes(monthDay?.dayData?.shortTitle),
})}
/>
</div>
))}
</div> */}
</div>
))}
</div>
</ScrollSyncPane>
</div>
</>
); );
}; };

View File

@ -3,7 +3,6 @@ import {
BiWeekChartView, BiWeekChartView,
DayChartView, DayChartView,
GanttChartBlocksList, GanttChartBlocksList,
GanttChartSidebar,
HourChartView, HourChartView,
IBlockUpdateData, IBlockUpdateData,
IGanttBlock, IGanttBlock,
@ -16,6 +15,7 @@ import {
} from "components/gantt-chart"; } from "components/gantt-chart";
// helpers // helpers
import { cn } from "helpers/common.helper"; import { cn } from "helpers/common.helper";
import { ScrollSyncPane } from "react-scroll-sync";
type Props = { type Props = {
blocks: IGanttBlock[] | null; blocks: IGanttBlock[] | null;
@ -86,25 +86,23 @@ export const GanttChartMainContent: React.FC<Props> = (props) => {
<div <div
// DO NOT REMOVE THE ID // DO NOT REMOVE THE ID
id="gantt-container" id="gantt-container"
className={cn("relative h-full w-full flex flex-1 overflow-hidden border-t border-custom-border-200", { className={cn("relative z-[1] h-full w-full overflow-hidden border-t border-custom-border-200", {
"mb-8": bottomSpacing, "mb-8": bottomSpacing,
})} })}
> >
<GanttChartSidebar <div className="h-full w-full flex">
blocks={blocks} <ScrollSyncPane>
blockUpdateHandler={blockUpdateHandler} <div
enableReorder={enableReorder} // DO NOT REMOVE THE ID
sidebarToRender={sidebarToRender} id="scroll-container"
title={title} className="relative h-full w-full flex flex-1 overflow-hidden overflow-x-auto z-[1]"
/> // onScroll={onScroll}
<div >
// DO NOT REMOVE THE ID <ActiveChartView />
id="scroll-container" </div>
className="relative h-full w-full flex flex-col flex-1 overflow-hidden overflow-x-auto horizontal-scroll-enable" </ScrollSyncPane>
onScroll={onScroll}
>
<ActiveChartView />
{currentViewData && ( {currentViewData && (
// <ScrollSyncPane>
<GanttChartBlocksList <GanttChartBlocksList
itemsContainerWidth={itemsContainerWidth} itemsContainerWidth={itemsContainerWidth}
blocks={chartBlocks} blocks={chartBlocks}
@ -115,6 +113,7 @@ export const GanttChartMainContent: React.FC<Props> = (props) => {
enableBlockMove={enableBlockMove} enableBlockMove={enableBlockMove}
showAllBlocks={showAllBlocks} showAllBlocks={showAllBlocks}
/> />
// </ScrollSyncPane>
)} )}
</div> </div>
</div> </div>

View File

@ -21,6 +21,7 @@ import { cn } from "helpers/common.helper";
import { ChartDataType, IBlockUpdateData, IGanttBlock, TGanttViews } from "../types"; import { ChartDataType, IBlockUpdateData, IGanttBlock, TGanttViews } from "../types";
// data // data
import { currentViewDataWithView } from "../data"; import { currentViewDataWithView } from "../data";
import { ScrollSync } from "react-scroll-sync";
type ChartViewRootProps = { type ChartViewRootProps = {
border: boolean; border: boolean;
@ -180,37 +181,39 @@ export const ChartViewRoot: FC<ChartViewRootProps> = (props) => {
}; };
return ( return (
<div <ScrollSync>
className={cn("relative flex flex-col h-full select-none rounded-sm bg-custom-background-100 shadow", { <div
"fixed inset-0 z-[999999] bg-custom-background-100": fullScreenMode, className={cn("relative flex flex-col h-full select-none rounded-sm bg-custom-background-100 shadow", {
"border border-custom-border-200": border, "fixed inset-0 z-[999999] bg-custom-background-100": fullScreenMode,
})} "border border-custom-border-200": border,
> })}
<GanttChartHeader >
blocks={blocks} <GanttChartHeader
fullScreenMode={fullScreenMode} blocks={blocks}
toggleFullScreenMode={() => setFullScreenMode((prevData) => !prevData)} fullScreenMode={fullScreenMode}
handleChartView={(key) => updateCurrentViewRenderPayload(null, key)} toggleFullScreenMode={() => setFullScreenMode((prevData) => !prevData)}
handleToday={handleToday} handleChartView={(key) => updateCurrentViewRenderPayload(null, key)}
loaderTitle={loaderTitle} handleToday={handleToday}
title={title} loaderTitle={loaderTitle}
/> title={title}
<GanttChartMainContent />
blocks={blocks} <GanttChartMainContent
blockToRender={blockToRender} blocks={blocks}
blockUpdateHandler={blockUpdateHandler} blockToRender={blockToRender}
bottomSpacing={bottomSpacing} blockUpdateHandler={blockUpdateHandler}
chartBlocks={chartBlocks} bottomSpacing={bottomSpacing}
enableBlockLeftResize={enableBlockLeftResize} chartBlocks={chartBlocks}
enableBlockMove={enableBlockMove} enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize} enableBlockMove={enableBlockMove}
enableReorder={enableReorder} enableBlockRightResize={enableBlockRightResize}
itemsContainerWidth={itemsContainerWidth} enableReorder={enableReorder}
showAllBlocks={showAllBlocks} itemsContainerWidth={itemsContainerWidth}
sidebarToRender={sidebarToRender} showAllBlocks={showAllBlocks}
title={title} sidebarToRender={sidebarToRender}
updateCurrentViewRenderPayload={updateCurrentViewRenderPayload} title={title}
/> updateCurrentViewRenderPayload={updateCurrentViewRenderPayload}
</div> />
</div>
</ScrollSync>
); );
}; };

View File

@ -1,4 +1,3 @@
import { FC } from "react";
// hooks // hooks
import { useChart } from "components/gantt-chart"; import { useChart } from "components/gantt-chart";
// helpers // helpers
@ -6,65 +5,36 @@ import { cn } from "helpers/common.helper";
// types // types
import { IMonthBlock } from "../../views"; import { IMonthBlock } from "../../views";
export const MonthChartView: FC<any> = () => { export const MonthChartView = () => {
// chart hook // chart hook
const { currentViewData, renderView } = useChart(); const { currentViewData, renderView } = useChart();
const monthBlocks: IMonthBlock[] = renderView; const monthBlocks: IMonthBlock[] = renderView;
return ( return (
<> <div className="relative z-[1] h-full w-full flex flex-grow divide-x divide-custom-border-100/50">
<div className="absolute h-full flex flex-grow divide-x divide-custom-border-100/50"> {monthBlocks?.map((block, rootIndex) => (
{monthBlocks?.map((block, rootIndex) => ( <div key={`month-${block?.month}-${block?.year}`} className="relative flex flex-col">
<div key={`month-${block?.month}-${block?.year}`} className="relative flex flex-col"> <div className="flex h-full w-full divide-x divide-custom-border-100/50">
<div className="h-[60px] w-full"> {block?.children?.map((monthDay, index) => (
<div className="relative h-[30px]"> <div
<div className="sticky left-0 inline-flex whitespace-nowrap px-3 py-2 text-xs font-medium capitalize"> key={`column-${rootIndex}-${index}`}
{block?.title} className="relative flex h-full flex-col overflow-hidden whitespace-nowrap"
</div> style={{ width: `${currentViewData?.data.width}px` }}
</div> >
<div className="flex h-[30px] w-full">
{block?.children?.map((monthDay, _idx) => (
<div
key={`sub-title-${rootIndex}-${_idx}`}
className="flex-shrink-0 border-b border-custom-border-200 py-1 text-center capitalize"
style={{ width: `${currentViewData?.data.width}px` }}
>
<div className="space-x-1 text-xs">
<span className="text-custom-text-200">{monthDay.dayData.shortTitle[0]}</span>{" "}
<span className={monthDay.today ? "rounded-full bg-custom-primary-100 px-1 text-white" : ""}>
{monthDay.day}
</span>
</div>
</div>
))}
</div>
</div>
<div className="flex h-full w-full divide-x divide-custom-border-100/50">
{block?.children?.map((monthDay, index) => (
<div <div
key={`column-${rootIndex}-${index}`} className={cn("relative flex h-full w-full flex-1 justify-center", {
className="relative flex h-full flex-col overflow-hidden whitespace-nowrap" "bg-custom-background-90": ["sat", "sun"].includes(monthDay?.dayData?.shortTitle),
style={{ width: `${currentViewData?.data.width}px` }} })}
> />
<div <span className="absolute left-1/2 -translate-x-1/2 text-xs">
className={cn("relative flex h-full w-full flex-1 justify-center", { {block.monthData.shortTitle} {monthDay?.dayData?.shortTitle[0]} {monthDay?.day}
"bg-custom-background-90": ["sat", "sun"].includes(monthDay?.dayData?.shortTitle), </span>
})} </div>
> ))}
{/* highlight today */}
{/* {monthDay?.today && (
<div className="absolute top-0 bottom-0 w-[1px] bg-red-500" />
)} */}
</div>
</div>
))}
</div>
</div> </div>
))} </div>
</div> ))}
</> </div>
); );
}; };

View File

@ -240,17 +240,17 @@ export const ChartDraggable: React.FC<Props> = (props) => {
</div> </div>
)} )}
{/* move to right side hidden block button */} {/* move to right side hidden block button */}
{/* {isBlockHiddenOnRight && ( */} {isBlockHiddenOnRight && (
<div <div
className="fixed z-0 right-1 grid h-8 w-8 cursor-pointer place-items-center rounded border border-custom-border-300 bg-custom-background-80 text-custom-text-200 hover:text-custom-text-100" className="fixed z-0 right-1 grid h-8 w-8 cursor-pointer place-items-center rounded border border-custom-border-300 bg-custom-background-80 text-custom-text-200 hover:text-custom-text-100"
onClick={handleScrollToBlock} onClick={handleScrollToBlock}
style={{ style={{
top: `${(resizableRef.current?.getBoundingClientRect().top ?? 0) + 6}px`, top: `${(resizableRef.current?.getBoundingClientRect().top ?? 0) + 6}px`,
}} }}
> >
<ArrowRight className="h-3.5 w-3.5" /> <ArrowRight className="h-3.5 w-3.5" />
</div> </div>
{/* )} */} )}
<div <div
id={`block-${block.id}`} id={`block-${block.id}`}
ref={resizableRef} ref={resizableRef}

View File

@ -54,6 +54,7 @@
"react-hook-form": "^7.38.0", "react-hook-form": "^7.38.0",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-popper": "^2.3.0", "react-popper": "^2.3.0",
"react-scroll-sync": "^0.11.2",
"sharp": "^0.32.1", "sharp": "^0.32.1",
"swr": "^2.1.3", "swr": "^2.1.3",
"tailwind-merge": "^2.0.0", "tailwind-merge": "^2.0.0",
@ -69,6 +70,7 @@
"@types/react-color": "^3.0.6", "@types/react-color": "^3.0.6",
"@types/react-datepicker": "^4.8.0", "@types/react-datepicker": "^4.8.0",
"@types/react-dom": "^18.2.17", "@types/react-dom": "^18.2.17",
"@types/react-scroll-sync": "^0.11.2",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.48.2", "@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2", "@typescript-eslint/parser": "^5.48.2",

View File

@ -2808,6 +2808,13 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-scroll-sync@^0.11.2":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@types/react-scroll-sync/-/react-scroll-sync-0.9.0.tgz#f9c556cc1441b28344f6b820cac34099cab74f2b"
integrity sha512-DFJzqtF0lMUNxsKZ9aoFS+LGieGLXWQVWvCMTZWKm/qcGz+GCNTqwdHsnyWW3yy/aNSUxlXCAWdHaemGaES6lQ==
dependencies:
"@types/react" "*"
"@types/react-transition-group@^4.4.10": "@types/react-transition-group@^4.4.10":
version "4.4.10" version "4.4.10"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac"
@ -7231,7 +7238,7 @@ progress@^2.0.0, progress@^2.0.3:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1" version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -7626,6 +7633,13 @@ react-remove-scroll@2.5.4:
use-callback-ref "^1.3.0" use-callback-ref "^1.3.0"
use-sidecar "^1.1.2" use-sidecar "^1.1.2"
react-scroll-sync@^0.11.2:
version "0.11.2"
resolved "https://registry.yarnpkg.com/react-scroll-sync/-/react-scroll-sync-0.11.2.tgz#fe8a3a09eab5a44323bdf0f63283c1dd0cd99b45"
integrity sha512-n7m+bbRTSWuczhKQf6Evvl7PFGkTt4RfP4bhyUtUyv6znovpybhAScQDdrFr9Jh280nG39x9AWMY8j477PHL1A==
dependencies:
prop-types "^15.5.7"
react-selecto@^1.25.0: react-selecto@^1.25.0:
version "1.26.3" version "1.26.3"
resolved "https://registry.yarnpkg.com/react-selecto/-/react-selecto-1.26.3.tgz#f9081c006cee2e2fed85ac1811cfe17136cf81a5" resolved "https://registry.yarnpkg.com/react-selecto/-/react-selecto-1.26.3.tgz#f9081c006cee2e2fed85ac1811cfe17136cf81a5"