feat: Skeleton loading #404 (#458)

* feat: Skeleton loading #404

* fix: skeleton loading

* fix: skeleton loading

* feat: Skeleton loading #404

* fix: skeleton loading

* fix: skeleton loading

* Update CompanyPickerSkeleton.tsx

* updated changes
This commit is contained in:
Deepak Singh
2023-07-05 19:20:36 +05:30
committed by GitHub
parent 9ea765fcfd
commit 6e1ffdcc72
10 changed files with 126 additions and 48 deletions

View File

@ -30,6 +30,7 @@
"react-datepicker": "^4.11.0", "react-datepicker": "^4.11.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.4.0", "react-hotkeys-hook": "^4.4.0",
"react-loading-skeleton": "^3.3.1",
"react-modal": "^3.16.1", "react-modal": "^3.16.1",
"react-router-dom": "^6.4.4", "react-router-dom": "^6.4.4",
"react-textarea-autosize": "^8.4.1", "react-textarea-autosize": "^8.4.1",

View File

@ -14,6 +14,7 @@ import { UserProvider } from './providers/user/UserProvider';
import { App } from './App'; import { App } from './App';
import './index.css'; import './index.css';
import 'react-loading-skeleton/dist/skeleton.css';
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement, document.getElementById('root') as HTMLElement,

View File

@ -59,6 +59,7 @@ export function CompanyAccountOwnerPicker({ company }: OwnProps) {
<SingleEntitySelect <SingleEntitySelect
onEntitySelected={handleEntitySelected} onEntitySelected={handleEntitySelected}
entities={{ entities={{
loading: companies.loading,
entitiesToSelect: companies.entitiesToSelect, entitiesToSelect: companies.entitiesToSelect,
selectedEntity: companies.selectedEntities[0], selectedEntity: companies.selectedEntities[0],
}} }}

View File

@ -64,6 +64,7 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
entities={{ entities={{
entitiesToSelect: companies.entitiesToSelect, entitiesToSelect: companies.entitiesToSelect,
selectedEntity: companies.selectedEntities[0], selectedEntity: companies.selectedEntities[0],
loading: companies.loading,
}} }}
/> />
); );

View File

@ -16,6 +16,7 @@ import { SingleEntitySelectBase } from './SingleEntitySelectBase';
export type EntitiesForSingleEntitySelect< export type EntitiesForSingleEntitySelect<
CustomEntityForSelect extends EntityForSelect, CustomEntityForSelect extends EntityForSelect,
> = { > = {
loading: boolean;
selectedEntity: CustomEntityForSelect; selectedEntity: CustomEntityForSelect;
entitiesToSelect: CustomEntityForSelect[]; entitiesToSelect: CustomEntityForSelect[];
}; };

View File

@ -10,6 +10,9 @@ import { isDefined } from '@/utils/type-guards/isDefined';
import { useEntitySelectScroll } from '../hooks/useEntitySelectScroll'; import { useEntitySelectScroll } from '../hooks/useEntitySelectScroll';
import { CompanyPickerSkeleton } from './skeletons/CompanyPickerSkeleton';
import { DropdownMenuItemContainerSkeleton } from './skeletons/DropdownMenuItemContainerSkeleton';
export type EntitiesForSingleEntitySelect< export type EntitiesForSingleEntitySelect<
CustomEntityForSelect extends EntityForSelect, CustomEntityForSelect extends EntityForSelect,
> = { > = {
@ -50,24 +53,29 @@ export function SingleEntitySelectBase<
return ( return (
<DropdownMenuItemContainer ref={containerRef}> <DropdownMenuItemContainer ref={containerRef}>
{entitiesInDropdown?.map((entity, index) => ( {entities.loading ? (
<DropdownMenuSelectableItem <DropdownMenuItemContainerSkeleton>
key={entity.id} <CompanyPickerSkeleton count={10} />
selected={entities.selectedEntity?.id === entity.id} </DropdownMenuItemContainerSkeleton>
hovered={hoveredIndex === index} ) : entitiesInDropdown.length === 0 ? (
onClick={() => onEntitySelected(entity)}
>
<Avatar
avatarUrl={entity.avatarUrl}
placeholder={entity.name}
size={16}
type={entity.avatarType ?? 'rounded'}
/>
{entity.name}
</DropdownMenuSelectableItem>
))}
{entitiesInDropdown?.length === 0 && (
<DropdownMenuItem>No result</DropdownMenuItem> <DropdownMenuItem>No result</DropdownMenuItem>
) : (
entitiesInDropdown?.map((entity, index) => (
<DropdownMenuSelectableItem
key={entity.id}
selected={entities.selectedEntity?.id === entity.id}
hovered={hoveredIndex === index}
onClick={() => onEntitySelected(entity)}
>
<Avatar
avatarUrl={entity.avatarUrl}
placeholder={entity.name}
size={16}
type={entity.avatarType ?? 'rounded'}
/>
{entity.name}
</DropdownMenuSelectableItem>
))
)} )}
</DropdownMenuItemContainer> </DropdownMenuItemContainer>
); );

View File

@ -0,0 +1,31 @@
import Skeleton from 'react-loading-skeleton';
import styled from '@emotion/styled';
type OwnProps = {
count: number;
};
export const SkeletonContainer = styled.div`
align-items: center;
display: inline-flex;
margin-bottom: ${({ theme }) => theme.spacing(1)};
position: relative;
width: 100%;
`;
export const SkeletonEntityName = styled.div`
margin-left: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
export function CompanyPickerSkeleton({ count }: OwnProps) {
const loadSkeletons = Array(count).fill(1);
return loadSkeletons.map((_, i) => (
<SkeletonContainer key={i}>
<Skeleton width={15} height={15} />
<SkeletonEntityName>
<Skeleton height={8} />
</SkeletonEntityName>
</SkeletonContainer>
));
}

View File

@ -0,0 +1,20 @@
import styled from '@emotion/styled';
export const DropdownMenuItemContainerSkeleton = styled.div`
--horizontal-padding: ${({ theme }) => theme.spacing(1.5)};
--vertical-padding: ${({ theme }) => theme.spacing(2)};
align-items: center;
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.sm};
gap: ${({ theme }) => theme.spacing(2)};
height: calc(100% - 2 * var(--vertical-padding));
padding: var(--vertical-padding) var(--horizontal-padding);
width: calc(100% - 2 * var(--horizontal-padding));
`;

View File

@ -71,18 +71,19 @@ export function useFilteredSearchEntityQuery<
limit?: number; limit?: number;
searchFilter: string; searchFilter: string;
}): EntitiesForMultipleEntitySelect<CustomEntityForSelect> { }): EntitiesForMultipleEntitySelect<CustomEntityForSelect> {
const { data: selectedEntitiesData } = queryHook({ const { loading: selectedEntitiesLoading, data: selectedEntitiesData } =
variables: { queryHook({
where: { variables: {
id: { where: {
in: selectedIds, id: {
in: selectedIds,
},
}, },
}, orderBy: {
orderBy: { [orderByField]: sortOrder,
[orderByField]: sortOrder, },
}, } as QueryVariables,
} as QueryVariables, });
});
const searchFilterByField = searchOnFields.map((field) => ({ const searchFilterByField = searchOnFields.map((field) => ({
[field]: { [field]: {
@ -91,7 +92,10 @@ export function useFilteredSearchEntityQuery<
}, },
})); }));
const { data: filteredSelectedEntitiesData } = queryHook({ const {
loading: filteredSelectedEntitiesLoading,
data: filteredSelectedEntitiesData,
} = queryHook({
variables: { variables: {
where: { where: {
AND: [ AND: [
@ -111,26 +115,27 @@ export function useFilteredSearchEntityQuery<
} as QueryVariables, } as QueryVariables,
}); });
const { data: entitiesToSelectData } = queryHook({ const { loading: entitiesToSelectLoading, data: entitiesToSelectData } =
variables: { queryHook({
where: { variables: {
AND: [ where: {
{ AND: [
OR: searchFilterByField, {
}, OR: searchFilterByField,
{
id: {
notIn: selectedIds,
}, },
}, {
], id: {
}, notIn: selectedIds,
limit: limit ?? DEFAULT_SEARCH_REQUEST_LIMIT, },
orderBy: { },
[orderByField]: sortOrder, ],
}, },
} as QueryVariables, limit: limit ?? DEFAULT_SEARCH_REQUEST_LIMIT,
}); orderBy: {
[orderByField]: sortOrder,
},
} as QueryVariables,
});
return { return {
selectedEntities: (selectedEntitiesData?.searchResults ?? []).map( selectedEntities: (selectedEntitiesData?.searchResults ?? []).map(
@ -142,5 +147,9 @@ export function useFilteredSearchEntityQuery<
entitiesToSelect: (entitiesToSelectData?.searchResults ?? []).map( entitiesToSelect: (entitiesToSelectData?.searchResults ?? []).map(
mappingFunction, mappingFunction,
), ),
loading:
entitiesToSelectLoading ||
filteredSelectedEntitiesLoading ||
selectedEntitiesLoading,
}; };
} }

View File

@ -14507,6 +14507,11 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-loading-skeleton@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/react-loading-skeleton/-/react-loading-skeleton-3.3.1.tgz#cd6e3a626ee86c76a46c14e2379243f2f8834e1b"
integrity sha512-NilqqwMh2v9omN7LteiDloEVpFyMIa0VGqF+ukqp0ncVlYu1sKYbYGX9JEl+GtOT9TKsh04zCHAbavnQ2USldA==
react-modal@^3.16.1: react-modal@^3.16.1:
version "3.16.1" version "3.16.1"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.16.1.tgz#34018528fc206561b1a5467fc3beeaddafb39b2b" resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.16.1.tgz#34018528fc206561b1a5467fc3beeaddafb39b2b"