mirror of https://github.com/buster-so/buster.git
scroll to bottom logic
This commit is contained in:
parent
b0fa427648
commit
037a5c640f
|
@ -2,10 +2,16 @@ import { Command } from 'cmdk';
|
|||
import { cn } from '@/lib/utils';
|
||||
import { IndeterminateLinearLoader } from '../../loaders';
|
||||
|
||||
export const SearchLoading = ({ loading = false }: { loading?: boolean }) => {
|
||||
export const SearchLoading = ({
|
||||
loading = false,
|
||||
showTopLoading = false,
|
||||
}: {
|
||||
loading?: boolean;
|
||||
showTopLoading?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Command.Loading className="w-full border-b swag relative">
|
||||
{loading && (
|
||||
{showTopLoading && loading && (
|
||||
<IndeterminateLinearLoader
|
||||
className={cn('w-full absolute top-0 left-0 right-0')}
|
||||
height={0.5}
|
||||
|
|
|
@ -60,6 +60,7 @@ export const Default: Story = {
|
|||
onChangeValue: fn(),
|
||||
onSelect: fn(),
|
||||
onViewSearchItem: fn(),
|
||||
onScrollToBottom: fn(),
|
||||
emptyState: 'No results found',
|
||||
placeholder: 'Search for something',
|
||||
filterContent: <div>Filter</div>,
|
||||
|
|
|
@ -24,7 +24,9 @@ export const SearchModalContent = <M, T extends string>({
|
|||
secondaryContent,
|
||||
openSecondaryContent,
|
||||
shouldFilter = true,
|
||||
showTopLoading = false,
|
||||
filter,
|
||||
onScrollToBottom,
|
||||
}: SearchModalContentProps<M, T>) => {
|
||||
const { handleKeyDown, focusedValue, setFocusedValue } = useViewSearchItem({
|
||||
searchItems,
|
||||
|
@ -71,14 +73,16 @@ export const SearchModalContent = <M, T extends string>({
|
|||
open={open}
|
||||
/>
|
||||
|
||||
<SearchLoading loading={loading} />
|
||||
<SearchLoading loading={loading} showTopLoading={showTopLoading} />
|
||||
|
||||
<SearchModalItemsContainer
|
||||
searchItems={searchItems}
|
||||
secondaryContent={secondaryContent}
|
||||
openSecondaryContent={openSecondaryContent}
|
||||
loading={loading}
|
||||
onSelectGlobal={onSelectGlobal}
|
||||
onViewSearchItem={onViewSearchItem}
|
||||
onScrollToBottom={onScrollToBottom}
|
||||
/>
|
||||
|
||||
<SearchEmptyState emptyState={emptyState} />
|
||||
|
|
|
@ -18,16 +18,45 @@ type CommonProps<M, T extends string> = {
|
|||
onSelectGlobal: (d: SearchItem<M, T>) => void;
|
||||
};
|
||||
|
||||
const SCROLL_THRESHOLD = 55;
|
||||
|
||||
export const SearchModalContentItems = <M, T extends string>({
|
||||
searchItems,
|
||||
loading,
|
||||
onSelectGlobal,
|
||||
}: Pick<SearchModalContentProps<M, T>, 'searchItems' | 'onViewSearchItem'> & CommonProps<M, T>) => {
|
||||
onScrollToBottom,
|
||||
}: Pick<
|
||||
SearchModalContentProps<M, T>,
|
||||
'loading' | 'onScrollToBottom' | 'searchItems' | 'onViewSearchItem'
|
||||
> &
|
||||
CommonProps<M, T>) => {
|
||||
const hasFiredRef = useRef(false);
|
||||
|
||||
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const target = e.currentTarget;
|
||||
const scrollHeight = target.scrollHeight;
|
||||
const scrollTop = target.scrollTop;
|
||||
const clientHeight = target.clientHeight;
|
||||
|
||||
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
||||
|
||||
if (distanceFromBottom <= SCROLL_THRESHOLD) {
|
||||
if (!hasFiredRef.current) {
|
||||
hasFiredRef.current = true;
|
||||
onScrollToBottom?.();
|
||||
}
|
||||
} else {
|
||||
hasFiredRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Command.List
|
||||
className={cn(
|
||||
'flex flex-col overflow-y-auto flex-1 px-3 pt-1.5 pb-1.5',
|
||||
'[&_[hidden]+[data-separator-after-hidden]]:hidden'
|
||||
)}
|
||||
onScroll={onScrollToBottom ? handleScroll : undefined}
|
||||
>
|
||||
{searchItems.map((item, index) => (
|
||||
<ItemsSelecter
|
||||
|
@ -36,6 +65,14 @@ export const SearchModalContentItems = <M, T extends string>({
|
|||
onSelectGlobal={onSelectGlobal}
|
||||
/>
|
||||
))}
|
||||
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center my-1.5">
|
||||
<Text size={'sm'} variant={'secondary'}>
|
||||
Loading...
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</Command.List>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { useCommandState } from 'cmdk';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { SearchLoading } from './SearchLoading';
|
||||
import { SearchModalContentItems } from './SearchModalContentItems';
|
||||
import type { SearchItem, SearchItems, SearchModalContentProps } from './search-modal.types';
|
||||
|
||||
|
@ -14,12 +12,16 @@ export const SearchModalItemsContainer = <M, T extends string>({
|
|||
onViewSearchItem,
|
||||
secondaryContent,
|
||||
openSecondaryContent,
|
||||
loading,
|
||||
onScrollToBottom,
|
||||
}: {
|
||||
searchItems: SearchItems<M, T>[];
|
||||
loading: SearchModalContentProps<M, T>['loading'];
|
||||
onSelectGlobal: (d: SearchItem<M, T>) => void;
|
||||
onViewSearchItem: (item: SearchItem<M, T>) => void;
|
||||
secondaryContent: SearchModalContentProps<M, T>['secondaryContent'];
|
||||
openSecondaryContent: SearchModalContentProps<M, T>['openSecondaryContent'];
|
||||
onScrollToBottom: SearchModalContentProps<M, T>['onScrollToBottom'];
|
||||
}) => {
|
||||
const hasResults = useCommandState((x) => x.filtered.count) > 0;
|
||||
|
||||
|
@ -33,8 +35,10 @@ export const SearchModalItemsContainer = <M, T extends string>({
|
|||
>
|
||||
<SearchModalContentItems
|
||||
searchItems={searchItems}
|
||||
loading={loading}
|
||||
onSelectGlobal={onSelectGlobal}
|
||||
onViewSearchItem={onViewSearchItem}
|
||||
onScrollToBottom={onScrollToBottom}
|
||||
/>
|
||||
</motion.div>
|
||||
<AnimatePresence>
|
||||
|
|
|
@ -43,8 +43,10 @@ export type SearchModalContentProps<M = unknown, T extends string = string> = {
|
|||
emptyState?: React.ReactNode | string;
|
||||
placeholder?: string;
|
||||
loading?: boolean;
|
||||
showTopLoading?: boolean;
|
||||
secondaryContent?: React.ReactNode | null;
|
||||
openSecondaryContent?: boolean; //if undefined it will close and open with the secondary content
|
||||
onScrollToBottom?: () => void;
|
||||
} & Pick<React.ComponentProps<typeof Command>, 'filter' | 'shouldFilter'>;
|
||||
|
||||
export type SearchModalProps = SearchModalContentProps & {
|
||||
|
|
Loading…
Reference in New Issue