* 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:
@ -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",
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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],
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -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,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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[];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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>
|
||||||
|
));
|
||||||
|
}
|
||||||
@ -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));
|
||||||
|
`;
|
||||||
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user