Skip to content

Commit

Permalink
feat: add new hook useContentLoadingIndicator (#126)
Browse files Browse the repository at this point in the history
## Description

<!-- Write and explain of the changes introduced by this PR for the reviewers to fully understand -->

## Screenshot

<!-- Provide a screenshot or gif of the change to demonstrate it -->

## Test Plan

<!-- Explain what you tested and why -->

<!--
  Have any questions? Check out the contributing doc for more
-->
  • Loading branch information
mkarajohn committed Mar 13, 2024
2 parents e48e733 + 886aae2 commit 80df1bf
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 6 deletions.
43 changes: 43 additions & 0 deletions documentation/docs/api/Hooks/useContentLoadingIndicator.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
id: 'useContentLoadingIndicator'
title: 'useContentLoadingIndicator'
sidebar_label: 'useContentLoadingIndicator'
sidebar_position: 3.1
---

```ts
import { useContentLoadingIndicator } from '@orfium/toolbox';
```

:::info
Only descendants of [`Scaffold`](../components/Scaffold) can use this hook.
:::

## Description

A hook that simply activates/deactivates an indeterminate loader meant to indicate that the content of the page is
still loading.

The hook will automatically reset the value to `false` upon unmounting, so you do not have to manually clean up anything.

**Example usage**

```tsx
import { useContentLoadingIndicator } from '@orfium/toolbox';
import { useQuery } from '@tanstack/react-query';

function Page() {
const {data, isLoading} = useQuery(/***/)
useContentLoadingIndicator(isLoading);

return <div>{data ? data.toString() : '-'}</div>;
}
```

## Parameters

- `active: boolean` - Indicates whether the loading indicator should be shown or not.

## Return value

Ƭ `undefined`
20 changes: 20 additions & 0 deletions src/contexts/content-loading-indicator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createContext, Dispatch, SetStateAction } from 'react';

export type ContentLoadingIndicatorContextValue = {
loadingIndicator: boolean;
setLoadingIndicator: Dispatch<SetStateAction<boolean>>;
};

export const defaultContentLoadingIndicatorContextValue: ContentLoadingIndicatorContextValue = {
loadingIndicator: false,
setLoadingIndicator: new Proxy(() => {}, {
apply: () => {
throw new Error(
'You can only use useContentLoadingIndicator inside descendants of the Scaffold component'
);
},
}),
};
export const ContentLoadingIndicatorContext = createContext<ContentLoadingIndicatorContextValue>(
defaultContentLoadingIndicatorContextValue
);
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useAuthentication, type UseAuthenticationReturnValue } from './useAuthentication';
export { useContentLoadingIndicator } from './useContentLoadingIndicator';
export { useDeferredValue } from './useDeferredValue';
export { useOrfiumProducts, type UseOrfiumProductsReturnValue } from './useOrfiumProducts';
export { useOrganizations, type UseOrganizationsReturnValue } from './useOrganizations';
Expand Down
17 changes: 17 additions & 0 deletions src/hooks/useContentLoadingIndicator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useContext, useEffect } from 'react';
import { ContentLoadingIndicatorContext } from '../contexts/content-loading-indicator';

export const _useContentLoadingIndicator = () => useContext(ContentLoadingIndicatorContext);
export const useContentLoadingIndicator = (active: boolean) => {
const { setLoadingIndicator } = useContext(ContentLoadingIndicatorContext);

useEffect(() => {
setLoadingIndicator(active);

return function () {
setLoadingIndicator(false);
};
}, [setLoadingIndicator, active]);
};

export type UseLoadingIndicatorReturnValue = undefined;
18 changes: 18 additions & 0 deletions src/providers/ContentLoadingIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ReactNode, useState } from 'react';
import { ContentLoadingIndicatorContext } from '../contexts/content-loading-indicator';

export function ContentLoadingIndicator(props: { children: ReactNode }) {
const { children } = props;
const [loadingIndicator, setLoadingIndicator] = useState<boolean>(false);

return (
<ContentLoadingIndicatorContext.Provider
value={{
loadingIndicator,
setLoadingIndicator,
}}
>
{children}
</ContentLoadingIndicatorContext.Provider>
);
}
10 changes: 8 additions & 2 deletions src/ui/Scaffold/Scaffold.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ export const Header = styled.header`
export const Contents = styled.main`
grid-area: main;
position: relative;
padding: ${({ theme }) => theme.spacing.md} ${({ theme }) => theme.spacing.md}
${({ theme }) => theme.spacing.lg};
padding: ${({ theme }) => `0 ${theme.spacing.md} ${theme.spacing.lg}`};
overflow: auto;
`;

Expand All @@ -40,3 +39,10 @@ export const SideNav = styled.aside`
position: relative;
z-index: 101;
`;

export const LoadingBarWrapper = styled.div`
position: absolute;
left: 0;
right: 0;
top: 0;
`;
35 changes: 31 additions & 4 deletions src/ui/Scaffold/Scaffold.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,52 @@
import { Global } from '@emotion/react';
import { useTheme } from '@orfium/ictinus';
import { Loader, useTheme } from '@orfium/ictinus';
import { type ReactElement, type ReactNode } from 'react';
import { backGround, Contents, GridContainer, Header, SideNav } from './Scaffold.styles';
import { _useContentLoadingIndicator } from '../../hooks/useContentLoadingIndicator';
import { ContentLoadingIndicator } from '../../providers/ContentLoadingIndicator';
import {
Contents,
GridContainer,
Header,
LoadingBarWrapper,
SideNav,
backGround,
} from './Scaffold.styles';

export type ScaffoldProps = {
navigationSlot: ReactElement;
headerSlot: ReactElement;
children: ReactNode;
};

export function Scaffold(props: ScaffoldProps) {
function ScaffoldInternal(props: ScaffoldProps) {
const { navigationSlot, headerSlot, children } = props;
const { loadingIndicator } = _useContentLoadingIndicator();

const theme = useTheme();

return (
<GridContainer>
<Global styles={{ body: backGround(theme) }} />
<SideNav>{navigationSlot}</SideNav>
<Header>{headerSlot}</Header>
<Header>
{loadingIndicator ? (
<LoadingBarWrapper>
<Loader type={'indeterminate'} />
</LoadingBarWrapper>
) : null}
{headerSlot}
</Header>
<Contents>{children}</Contents>
</GridContainer>
);
}

export function Scaffold(props: ScaffoldProps) {
const { children, ...rest } = props;

return (
<ContentLoadingIndicator>
<ScaffoldInternal {...rest}>{children}</ScaffoldInternal>
</ContentLoadingIndicator>
);
}

0 comments on commit 80df1bf

Please sign in to comment.