1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

Feat/pagination loading (#5325)

This PR makes changes to how the project overview skeleton screen works.
Important changes:

- Add skeleton screens to missing elements, creating a more
comprehensive loading screen
- Split the page into different loading sections, so that we can load
the table when we fetch the next page without affecting the rest of the
page.

https://www.loom.com/share/e5d30dc897ac488ea80cfae11ffab646

Next steps:
* Hide bar if total is less than 25
* Add FE testing
This commit is contained in:
Fredrik Strand Oseberg 2023-11-13 14:08:48 +01:00 committed by GitHub
parent 6a41ee6e9d
commit 834ae1d8a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 286 additions and 210 deletions

View File

@ -81,6 +81,7 @@ export const Search = ({
containerStyles, containerStyles,
expandable = false, expandable = false,
debounceTime = 200, debounceTime = 200,
...rest
}: ISearchProps) => { }: ISearchProps) => {
const searchInputRef = useRef<HTMLInputElement>(null); const searchInputRef = useRef<HTMLInputElement>(null);
const searchContainerRef = useRef<HTMLInputElement>(null); const searchContainerRef = useRef<HTMLInputElement>(null);
@ -126,6 +127,7 @@ export const Search = ({
ref={searchContainerRef} ref={searchContainerRef}
style={containerStyles} style={containerStyles}
active={expandable && showSuggestions} active={expandable && showSuggestions}
{...rest}
> >
<StyledSearch className={className}> <StyledSearch className={className}>
<SearchIcon <SearchIcon

View File

@ -13,7 +13,7 @@ export const SortableTableHeader = <T extends object>({
}) => ( }) => (
<TableHead className={className}> <TableHead className={className}>
{headerGroups.map((headerGroup) => ( {headerGroups.map((headerGroup) => (
<TableRow {...headerGroup.getHeaderGroupProps()}> <TableRow {...headerGroup.getHeaderGroupProps()} data-loading>
{headerGroup.headers.map((column: HeaderGroup<T>) => { {headerGroup.headers.map((column: HeaderGroup<T>) => {
const content = column.render('Header'); const content = column.render('Header');

View File

@ -8,6 +8,7 @@ interface IFeatureSeenCellProps {
export const FeatureEnvironmentSeenCell: VFC<IFeatureSeenCellProps> = ({ export const FeatureEnvironmentSeenCell: VFC<IFeatureSeenCellProps> = ({
feature, feature,
...rest
}) => { }) => {
const environments = feature.environments const environments = feature.environments
? Object.values(feature.environments) ? Object.values(feature.environments)
@ -17,6 +18,7 @@ export const FeatureEnvironmentSeenCell: VFC<IFeatureSeenCellProps> = ({
<FeatureEnvironmentSeen <FeatureEnvironmentSeen
featureLastSeen={feature.lastSeenAt} featureLastSeen={feature.lastSeenAt}
environments={environments} environments={environments}
{...rest}
/> />
); );
}; };

View File

@ -72,7 +72,7 @@ export const LastSeenTooltip = ({
Boolean(environment.lastSeenAt), Boolean(environment.lastSeenAt),
); );
return ( return (
<StyledDescription {...rest}> <StyledDescription {...rest} data-loading>
<StyledDescriptionHeader sx={{ mb: 0 }}> <StyledDescriptionHeader sx={{ mb: 0 }}>
Last usage reported Last usage reported
</StyledDescriptionHeader> </StyledDescriptionHeader>

View File

@ -74,6 +74,7 @@ export const FeatureEnvironmentSeen = ({
featureLastSeen, featureLastSeen,
environments, environments,
sx, sx,
...rest
}: IFeatureEnvironmentSeenProps) => { }: IFeatureEnvironmentSeenProps) => {
const getColor = useLastSeenColors(); const getColor = useLastSeenColors();
@ -95,6 +96,7 @@ export const FeatureEnvironmentSeen = ({
<LastSeenTooltip <LastSeenTooltip
featureLastSeen={lastSeen} featureLastSeen={lastSeen}
environments={environments} environments={environments}
{...rest}
/> />
} }
color={color} color={color}

View File

@ -228,6 +228,7 @@ export const Project = () => {
{filteredTabs.map((tab) => { {filteredTabs.map((tab) => {
return ( return (
<StyledTab <StyledTab
data-loading
key={tab.title} key={tab.title}
label={tab.title} label={tab.title}
value={tab.path} value={tab.path}

View File

@ -87,7 +87,7 @@ export const ActionsCell: VFC<IActionsCellProps> = ({
}; };
return ( return (
<StyledBoxCell> <StyledBoxCell data-loading>
<Tooltip title='Feature toggle actions' arrow describeChild> <Tooltip title='Feature toggle actions' arrow describeChild>
<IconButton <IconButton
id={id} id={id}

View File

@ -62,6 +62,7 @@ import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { ListItemType } from './ProjectFeatureToggles.types'; import { ListItemType } from './ProjectFeatureToggles.types';
import { createFeatureToggleCell } from './FeatureToggleSwitch/createFeatureToggleCell'; import { createFeatureToggleCell } from './FeatureToggleSwitch/createFeatureToggleCell';
import { useFeatureToggleSwitch } from './FeatureToggleSwitch/useFeatureToggleSwitch'; import { useFeatureToggleSwitch } from './FeatureToggleSwitch/useFeatureToggleSwitch';
import useLoading from 'hooks/useLoading';
const StyledResponsiveButton = styled(ResponsiveButton)(() => ({ const StyledResponsiveButton = styled(ResponsiveButton)(() => ({
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
@ -73,6 +74,7 @@ interface IPaginatedProjectFeatureTogglesProps {
loading: boolean; loading: boolean;
onChange: () => void; onChange: () => void;
total?: number; total?: number;
initialLoad: boolean;
searchValue: string; searchValue: string;
setSearchValue: React.Dispatch<React.SetStateAction<string>>; setSearchValue: React.Dispatch<React.SetStateAction<string>>;
paginationBar: JSX.Element; paginationBar: JSX.Element;
@ -87,6 +89,7 @@ const defaultSort: SortingRule<string> & {
export const PaginatedProjectFeatureToggles = ({ export const PaginatedProjectFeatureToggles = ({
features, features,
loading, loading,
initialLoad,
environments: newEnvironments = [], environments: newEnvironments = [],
onChange, onChange,
total, total,
@ -95,6 +98,8 @@ export const PaginatedProjectFeatureToggles = ({
paginationBar, paginationBar,
}: IPaginatedProjectFeatureTogglesProps) => { }: IPaginatedProjectFeatureTogglesProps) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const bodyLoadingRef = useLoading(loading);
const headerLoadingRef = useLoading(initialLoad);
const theme = useTheme(); const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const [strategiesDialogState, setStrategiesDialogState] = useState({ const [strategiesDialogState, setStrategiesDialogState] = useState({
@ -198,7 +203,10 @@ export const PaginatedProjectFeatureToggles = ({
accessor: 'lastSeenAt', accessor: 'lastSeenAt',
Cell: ({ value, row: { original: feature } }: any) => { Cell: ({ value, row: { original: feature } }: any) => {
return showEnvironmentLastSeen ? ( return showEnvironmentLastSeen ? (
<MemoizedFeatureEnvironmentSeenCell feature={feature} /> <MemoizedFeatureEnvironmentSeenCell
feature={feature}
data-loading
/>
) : ( ) : (
<FeatureSeenCell value={value} /> <FeatureSeenCell value={value} />
); );
@ -355,15 +363,20 @@ export const PaginatedProjectFeatureToggles = ({
); );
const data = useMemo(() => { const data = useMemo(() => {
if (loading) { if (initialLoad || loading) {
return Array(6).fill({ const loadingData = Array(15)
type: '-', .fill(null)
name: 'Feature name', .map((_, index) => ({
createdAt: new Date(), id: index, // Assuming `id` is a required property
environments: { type: '-',
production: { name: 'production', enabled: false }, name: `Feature name ${index}`,
}, createdAt: new Date().toISOString(),
}) as FeatureSchema[]; environments: {
production: { name: 'production', enabled: false },
},
}));
// Coerce loading data to FeatureSchema[]
return loadingData as unknown as FeatureSchema[];
} }
return featuresData; return featuresData;
}, [loading, featuresData]); }, [loading, featuresData]);
@ -491,168 +504,193 @@ export const PaginatedProjectFeatureToggles = ({
return ( return (
<> <>
<PageContent <PageContent
isLoading={loading} disableLoading
className={styles.container} className={styles.container}
sx={{ borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }} sx={{ borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }}
header={ header={
<PageHeader <div ref={headerLoadingRef}>
titleElement={ <PageHeader
showTitle titleElement={
? `Feature toggles (${total || rows.length})` showTitle
: null ? `Feature toggles (${
} total || rows.length
actions={ })`
<> : null
<ConditionallyRender
condition={!isSmallScreen}
show={
<Search
placeholder='Search and Filter'
expandable
initialValue={searchValue}
onChange={setSearchValue}
onFocus={() => setShowTitle(false)}
onBlur={() => setShowTitle(true)}
hasFilters
getSearchContext={getSearchContext}
id='projectFeatureToggles'
/>
}
/>
<ColumnsMenu
allColumns={allColumns}
staticColumns={staticColumns}
dividerAfter={['createdAt']}
dividerBefore={['Actions']}
isCustomized={Boolean(storedParams.columns)}
setHiddenColumns={setHiddenColumns}
/>
<PageHeader.Divider sx={{ marginLeft: 0 }} />
<ConditionallyRender
condition={Boolean(
uiConfig?.flags?.featuresExportImport,
)}
show={
<Tooltip
title='Export toggles visible in the table below'
arrow
>
<IconButton
onClick={() =>
setShowExportDialog(true)
}
sx={(theme) => ({
marginRight:
theme.spacing(2),
})}
>
<FileDownload />
</IconButton>
</Tooltip>
}
/>
<StyledResponsiveButton
onClick={() =>
navigate(getCreateTogglePath(projectId))
}
maxWidth='960px'
Icon={Add}
projectId={projectId}
permission={CREATE_FEATURE}
data-testid='NAVIGATE_TO_CREATE_FEATURE'
>
New feature toggle
</StyledResponsiveButton>
</>
}
>
<ConditionallyRender
condition={isSmallScreen}
show={
<Search
initialValue={searchValue}
onChange={setSearchValue}
hasFilters
getSearchContext={getSearchContext}
id='projectFeatureToggles'
/>
} }
/> actions={
</PageHeader> <>
<ConditionallyRender
condition={!isSmallScreen}
show={
<Search
data-loading
placeholder='Search and Filter'
expandable
initialValue={searchValue}
onChange={setSearchValue}
onFocus={() =>
setShowTitle(false)
}
onBlur={() =>
setShowTitle(true)
}
hasFilters
getSearchContext={
getSearchContext
}
id='projectFeatureToggles'
/>
}
/>
<ColumnsMenu
allColumns={allColumns}
staticColumns={staticColumns}
dividerAfter={['createdAt']}
dividerBefore={['Actions']}
isCustomized={Boolean(
storedParams.columns,
)}
setHiddenColumns={setHiddenColumns}
/>
<PageHeader.Divider
sx={{ marginLeft: 0 }}
/>
<ConditionallyRender
condition={Boolean(
uiConfig?.flags
?.featuresExportImport,
)}
show={
<Tooltip
title='Export toggles visible in the table below'
arrow
>
<IconButton
data-loading
onClick={() =>
setShowExportDialog(
true,
)
}
sx={(theme) => ({
marginRight:
theme.spacing(2),
})}
>
<FileDownload />
</IconButton>
</Tooltip>
}
/>
<StyledResponsiveButton
onClick={() =>
navigate(
getCreateTogglePath(projectId),
)
}
maxWidth='960px'
Icon={Add}
projectId={projectId}
permission={CREATE_FEATURE}
data-testid='NAVIGATE_TO_CREATE_FEATURE'
>
New feature toggle
</StyledResponsiveButton>
</>
}
>
<ConditionallyRender
condition={isSmallScreen}
show={
<Search
initialValue={searchValue}
onChange={setSearchValue}
hasFilters
getSearchContext={getSearchContext}
id='projectFeatureToggles'
/>
}
/>
</PageHeader>
</div>
} }
> >
<SearchHighlightProvider value={getSearchText(searchValue)}> <div ref={bodyLoadingRef}>
<VirtualizedTable <SearchHighlightProvider value={getSearchText(searchValue)}>
rows={rows} <VirtualizedTable
headerGroups={headerGroups} rows={rows}
prepareRow={prepareRow} headerGroups={headerGroups}
prepareRow={prepareRow}
/>
</SearchHighlightProvider>
<ConditionallyRender
condition={rows.length === 0}
show={
<ConditionallyRender
condition={searchValue?.length > 0}
show={
<TablePlaceholder>
No feature toggles found matching
&ldquo;
{searchValue}
&rdquo;
</TablePlaceholder>
}
elseShow={
<TablePlaceholder>
No feature toggles available. Get
started by adding a new feature toggle.
</TablePlaceholder>
}
/>
}
/> />
</SearchHighlightProvider> <EnvironmentStrategyDialog
<ConditionallyRender onClose={() =>
condition={rows.length === 0} setStrategiesDialogState((prev) => ({
show={ ...prev,
<ConditionallyRender open: false,
condition={searchValue?.length > 0} }))
show={ }
<TablePlaceholder> projectId={projectId}
No feature toggles found matching &ldquo; {...strategiesDialogState}
{searchValue} />
&rdquo; <FeatureStaleDialog
</TablePlaceholder> isStale={featureStaleDialogState.stale === true}
} isOpen={Boolean(featureStaleDialogState.featureId)}
elseShow={ onClose={() => {
<TablePlaceholder> setFeatureStaleDialogState({});
No feature toggles available. Get started by onChange();
adding a new feature toggle. }}
</TablePlaceholder> featureId={featureStaleDialogState.featureId || ''}
} projectId={projectId}
/> />
} <FeatureArchiveDialog
/> isOpen={Boolean(featureArchiveState)}
<EnvironmentStrategyDialog onConfirm={onChange}
onClose={() => onClose={() => {
setStrategiesDialogState((prev) => ({ setFeatureArchiveState(undefined);
...prev, }}
open: false, featureIds={[featureArchiveState || '']}
})) projectId={projectId}
} />
projectId={projectId} <ConditionallyRender
{...strategiesDialogState} condition={
/> Boolean(uiConfig?.flags?.featuresExportImport) &&
<FeatureStaleDialog !loading
isStale={featureStaleDialogState.stale === true} }
isOpen={Boolean(featureStaleDialogState.featureId)} show={
onClose={() => { <ExportDialog
setFeatureStaleDialogState({}); showExportDialog={showExportDialog}
onChange(); data={data}
}} onClose={() => setShowExportDialog(false)}
featureId={featureStaleDialogState.featureId || ''} environments={environments}
projectId={projectId} />
/> }
<FeatureArchiveDialog />
isOpen={Boolean(featureArchiveState)} {featureToggleModals}
onConfirm={onChange} </div>
onClose={() => {
setFeatureArchiveState(undefined);
}}
featureIds={[featureArchiveState || '']}
projectId={projectId}
/>
<ConditionallyRender
condition={
Boolean(uiConfig?.flags?.featuresExportImport) &&
!loading
}
show={
<ExportDialog
showExportDialog={showExportDialog}
data={data}
onClose={() => setShowExportDialog(false)}
environments={environments}
/>
}
/>
{featureToggleModals}
</PageContent> </PageContent>
{paginationBar} {paginationBar}

View File

@ -20,7 +20,7 @@ export const RowSelectCell: FC<IRowSelectCellProps> = ({
checked, checked,
title, title,
}) => ( }) => (
<StyledBoxCell data-testid={BATCH_SELECT}> <StyledBoxCell data-testid={BATCH_SELECT} data-loading>
<Checkbox onChange={onChange} title={title} checked={checked} /> <Checkbox onChange={onChange} title={title} checked={checked} />
</StyledBoxCell> </StyledBoxCell>
); );

View File

@ -40,7 +40,7 @@ export const HealthWidget = ({ projectId, health }: IHealthWidgetProps) => {
gap: (theme) => theme.spacing(2), gap: (theme) => theme.spacing(2),
}} }}
> >
<StyledPercentageText> <StyledPercentageText data-loading>
<PercentageCircle percentage={health} /> <PercentageCircle percentage={health} />
</StyledPercentageText> </StyledPercentageText>
<StyledParagraphEmphasizedText data-loading> <StyledParagraphEmphasizedText data-loading>

View File

@ -24,8 +24,8 @@ const StyledIDContainer = styled('div')(({ theme }) => ({
export const MetaWidget: FC<IMetaWidgetProps> = ({ id, description }) => { export const MetaWidget: FC<IMetaWidgetProps> = ({ id, description }) => {
return ( return (
<StyledProjectInfoWidgetContainer> <StyledProjectInfoWidgetContainer>
<StyledWidgetTitle>Project Meta</StyledWidgetTitle> <StyledWidgetTitle data-loading>Project Meta</StyledWidgetTitle>
<StyledIDContainer> <StyledIDContainer data-loading>
<Typography <Typography
component='span' component='span'
variant='body2' variant='body2'
@ -39,6 +39,7 @@ export const MetaWidget: FC<IMetaWidgetProps> = ({ id, description }) => {
condition={Boolean(description)} condition={Boolean(description)}
show={ show={
<Typography <Typography
data-loading
variant='body2' variant='body2'
sx={{ sx={{
marginTop: (theme) => theme.spacing(1.5), marginTop: (theme) => theme.spacing(1.5),

View File

@ -30,6 +30,7 @@ export const ProjectMembersWidget = ({
<StyledProjectInfoWidgetContainer> <StyledProjectInfoWidgetContainer>
<StyledWidgetTitle data-loading>Project members</StyledWidgetTitle> <StyledWidgetTitle data-loading>Project members</StyledWidgetTitle>
<Box <Box
data-loading
sx={{ sx={{
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
@ -37,7 +38,9 @@ export const ProjectMembersWidget = ({
> >
<StatusBox boxText={`${memberCount}`} change={change} /> <StatusBox boxText={`${memberCount}`} change={change} />
</Box> </Box>
<WidgetFooterLink to={link}>View all members</WidgetFooterLink> <WidgetFooterLink data-loading to={link}>
View all members
</WidgetFooterLink>
</StyledProjectInfoWidgetContainer> </StyledProjectInfoWidgetContainer>
); );
}; };

View File

@ -84,7 +84,9 @@ export const ToggleTypesWidget = ({ features }: IToggleTypesWidgetProps) => {
<StyledProjectInfoWidgetContainer <StyledProjectInfoWidgetContainer
sx={{ padding: (theme) => theme.spacing(3) }} sx={{ padding: (theme) => theme.spacing(3) }}
> >
<StyledWidgetTitle>Toggle types used</StyledWidgetTitle> <StyledWidgetTitle data-loading>
Toggle types used
</StyledWidgetTitle>
{Object.keys(featureTypeStats).map((type) => ( {Object.keys(featureTypeStats).map((type) => (
<ToggleTypesRow <ToggleTypesRow
type={type} type={type}

View File

@ -12,6 +12,7 @@ export const WidgetFooterLink: FC<IWidgetFooterLinkProps> = ({
}) => { }) => {
return ( return (
<Typography <Typography
data-loading
variant='body2' variant='body2'
textAlign='center' textAlign='center'
sx={{ sx={{

View File

@ -45,7 +45,7 @@ const PaginatedProjectOverview = () => {
const { project, loading: projectLoading } = useProject(projectId, { const { project, loading: projectLoading } = useProject(projectId, {
refreshInterval, refreshInterval,
}); });
const [pageLimit, setPageLimit] = useState(10); const [pageLimit, setPageLimit] = useState(25);
const [currentOffset, setCurrentOffset] = useState(0); const [currentOffset, setCurrentOffset] = useState(0);
const [searchValue, setSearchValue] = useState( const [searchValue, setSearchValue] = useState(
@ -57,6 +57,7 @@ const PaginatedProjectOverview = () => {
total, total,
refetch, refetch,
loading, loading,
initialLoad,
} = useFeatureSearch(currentOffset, pageLimit, projectId, searchValue, { } = useFeatureSearch(currentOffset, pageLimit, projectId, searchValue, {
refreshInterval, refreshInterval,
}); });
@ -96,6 +97,7 @@ const PaginatedProjectOverview = () => {
} }
features={searchFeatures} features={searchFeatures}
environments={environments} environments={environments}
initialLoad={initialLoad && searchFeatures.length === 0}
loading={loading && searchFeatures.length === 0} loading={loading && searchFeatures.length === 0}
onChange={refetch} onChange={refetch}
total={total} total={total}
@ -128,7 +130,7 @@ const StyledStickyBar = styled('div')(({ theme }) => ({
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
padding: theme.spacing(2), padding: theme.spacing(2),
marginLeft: theme.spacing(2), marginLeft: theme.spacing(2),
zIndex: theme.zIndex.mobileStepper, zIndex: 9999,
borderBottomLeftRadius: theme.shape.borderRadiusMedium, borderBottomLeftRadius: theme.shape.borderRadiusMedium,
borderBottomRightRadius: theme.shape.borderRadiusMedium, borderBottomRightRadius: theme.shape.borderRadiusMedium,
borderTop: `1px solid ${theme.palette.divider}`, borderTop: `1px solid ${theme.palette.divider}`,

View File

@ -65,7 +65,11 @@ export const StatusBox: FC<IStatusBoxProps> = ({
<> <>
<ConditionallyRender <ConditionallyRender
condition={Boolean(title)} condition={Boolean(title)}
show={<StyledTypographyHeader>{title}</StyledTypographyHeader>} show={
<StyledTypographyHeader data-loading>
{title}
</StyledTypographyHeader>
}
/> />
{children} {children}
<Box <Box
@ -75,11 +79,13 @@ export const StatusBox: FC<IStatusBoxProps> = ({
width: 'auto', width: 'auto',
}} }}
> >
<StyledTypographyCount>{boxText}</StyledTypographyCount> <StyledTypographyCount data-loading>
{boxText}
</StyledTypographyCount>
<ConditionallyRender <ConditionallyRender
condition={Boolean(customChangeElement)} condition={Boolean(customChangeElement)}
show={ show={
<StyledBoxChangeContainer> <StyledBoxChangeContainer data-loading>
{customChangeElement} {customChangeElement}
</StyledBoxChangeContainer> </StyledBoxChangeContainer>
} }
@ -87,7 +93,7 @@ export const StatusBox: FC<IStatusBoxProps> = ({
<ConditionallyRender <ConditionallyRender
condition={change !== undefined && change !== 0} condition={change !== undefined && change !== 0}
show={ show={
<StyledBoxChangeContainer> <StyledBoxChangeContainer data-loading>
<Box <Box
sx={{ sx={{
...flexRow, ...flexRow,
@ -109,7 +115,7 @@ export const StatusBox: FC<IStatusBoxProps> = ({
} }
elseShow={ elseShow={
<StyledBoxChangeContainer> <StyledBoxChangeContainer>
<StyledTypographySubtext> <StyledTypographySubtext data-loading>
No change No change
</StyledTypographySubtext> </StyledTypographySubtext>
</StyledBoxChangeContainer> </StyledBoxChangeContainer>

View File

@ -119,6 +119,7 @@ exports[`renders an empty list correctly 1`] = `
> >
<tr <tr
className="MuiTableRow-root MuiTableRow-head css-15lapfi-MuiTableRow-root" className="MuiTableRow-root MuiTableRow-head css-15lapfi-MuiTableRow-root"
data-loading={true}
role="row" role="row"
> >
<th <th

View File

@ -14,6 +14,7 @@ interface IUseFeatureSearchOutput {
features: IFeatureToggleListItem[]; features: IFeatureToggleListItem[];
total: number; total: number;
loading: boolean; loading: boolean;
initialLoad: boolean;
error: string; error: string;
refetch: () => void; refetch: () => void;
} }
@ -26,38 +27,52 @@ const fallbackData: {
total: 0, total: 0,
}; };
export const useFeatureSearch = ( const createFeatureSearch = () => {
offset: number, let total = 0;
limit: number, let initialLoad = true;
projectId = '',
searchValue = '',
options: SWRConfiguration = {},
): IUseFeatureSearchOutput => {
const { KEY, fetcher } = getFeatureSearchFetcher(
projectId,
offset,
limit,
searchValue,
);
const { data, error, mutate } = useSWR<IFeatureSearchResponse>(
KEY,
fetcher,
options,
);
const refetch = useCallback(() => { return (
mutate(); offset: number,
}, [mutate]); limit: number,
projectId = '',
searchValue = '',
options: SWRConfiguration = {},
): IUseFeatureSearchOutput => {
const { KEY, fetcher } = getFeatureSearchFetcher(
projectId,
offset,
limit,
searchValue,
);
const { data, error, mutate, isLoading } =
useSWR<IFeatureSearchResponse>(KEY, fetcher, options);
const returnData = data || fallbackData; const refetch = useCallback(() => {
return { mutate();
...returnData, }, [mutate]);
loading: false,
error, if (data?.total) {
refetch, total = data.total;
}
if (!isLoading && initialLoad) {
initialLoad = false;
}
const returnData = data || fallbackData;
return {
...returnData,
loading: isLoading,
error,
refetch,
total,
initialLoad: isLoading && initialLoad,
};
}; };
}; };
export const useFeatureSearch = createFeatureSearch();
const getFeatureSearchFetcher = ( const getFeatureSearchFetcher = (
projectId: string, projectId: string,
offset: number, offset: number,

View File

@ -12,7 +12,7 @@ const useLoading = (loading: boolean, selector = '[data-loading=true]') => {
if (loading) { if (loading) {
element.classList.add('skeleton'); element.classList.add('skeleton');
} else { } else {
element.classList.remove('skeleton'); setTimeout(() => element.classList.remove('skeleton'), 10);
} }
}); });
} }

View File

@ -36,7 +36,7 @@ button {
.skeleton { .skeleton {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
z-index: 9999; z-index: 9990;
box-shadow: none; box-shadow: none;
fill: none; fill: none;
pointer-events: none; pointer-events: none;