Skip to content

Commit

Permalink
Update: updated...
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanuelonah committed Apr 1, 2024
1 parent 3d2c11b commit 7d263db
Show file tree
Hide file tree
Showing 20 changed files with 347 additions and 20 deletions.
14 changes: 0 additions & 14 deletions .storybook/preview.ts

This file was deleted.

35 changes: 35 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';

import { Preview } from '@storybook/react';
import { ThemeProvider } from 'styled-components';
import { QueryClient, QueryClientProvider } from 'react-query';

import { GlobalStore } from '../src/global-store';
import { theme, GlobalStyles } from '../src/design-system';

const preview: Preview = {
decorators: [
(Story) => (
<ThemeProvider theme={theme}>
<GlobalStyles theme={theme} />
<QueryClientProvider client={new QueryClient()}>
<GlobalStore>
<div style={{ width: '100vw', height: '100vh', backgroundColor: '#fff' }}>
<Story />
</div>
</GlobalStore>
</QueryClientProvider>
</ThemeProvider>
),
],
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};

export default preview;
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"test:cv": "yarn run test --coverage --watchAll=false",
"lint": "eslint . --ext .ts --ext .tsx",
"format": "prettier --write .",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"sb": "storybook dev -p 6006",
"build:sb": "storybook build",
"pkgs:audit": " yarn audit --json > audit.json",
"performance:audit": "lighthouse http://localhost:3000/",
"pre:commit": "yarn run lint",
Expand Down
40 changes: 40 additions & 0 deletions src/shared/components/async-renderer/index.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';

import { TextLoader } from 'shared/components';

type PrimitiveDivPropTypes = React.ComponentPropsWithoutRef<'div'>;
type AsyncRendererElement = React.ElementRef<'div'>;

interface AsyncRendererPropTypes<DataType> extends Omit<PrimitiveDivPropTypes, 'children'> {
isLoading?: boolean;
error?: Error | null;
data?: DataType | null;
loader?: React.ReactNode;
children?: ((data: DataType) => React.ReactNode) | React.ReactNode;
}

export const AsyncRenderer = React.forwardRef<AsyncRendererElement, AsyncRendererPropTypes<any>>(
({ isLoading, error, data, loader, children, ...rest }, forwardedRef) => {
if (isLoading) {
return (
<div {...rest} ref={forwardedRef}>
{loader || <TextLoader isLoading={isLoading} />}
</div>
);
}

if (error) {
return (
<div {...rest} ref={forwardedRef}>
{error.message}
</div>
);
}

return (
<div {...rest} ref={forwardedRef}>
{typeof children === 'function' ? children(data) : children}
</div>
);
}
);
24 changes: 24 additions & 0 deletions src/shared/components/async-renderer/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import { StoryFn, Meta } from '@storybook/react';

import { AsyncRenderer } from './index.component';

export default {
title: 'Components/AsyncRenderer',
component: AsyncRenderer,
} as Meta<typeof AsyncRenderer>;

export const IsLoading: StoryFn<typeof AsyncRenderer> = () => <AsyncRenderer isLoading />;

export const WithError: StoryFn<typeof AsyncRenderer> = () => (
<AsyncRenderer error={new Error('Not authorized!')} />
);

export const WithJsxData: StoryFn<typeof AsyncRenderer> = () => (
<AsyncRenderer>Hello, World!</AsyncRenderer>
);

export const WithFCCData: StoryFn<typeof AsyncRenderer> = () => (
<AsyncRenderer data={{ name: 'Foo Bar Baz' }}>{(data) => data.name}</AsyncRenderer>
);
51 changes: 51 additions & 0 deletions src/shared/components/async-renderer/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { render, screen } from 'test';

import { AsyncRenderer } from './index.component';

describe('<AsyncRenderer/>', () => {
it('should render loader when isLoading is true', () => {
render(
<AsyncRenderer
isLoading={true}
error={null}
data={null}
loader="Loading..."
children={() => null}
/>
);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});

it('should render error message when error is not null', () => {
render(
<AsyncRenderer
isLoading={false}
error={new Error('Error message')}
data={null}
children={() => null}
/>
);
expect(screen.getByText('Error message')).toBeInTheDocument();
});

it('should render children when isLoading is false and error is null', () => {
render(
<AsyncRenderer
isLoading={false}
error={null}
data={{ name: 'Foo Bar Baz' }}
children={(data) => data.name}
/>
);
expect(screen.getByText('Foo Bar Baz')).toBeInTheDocument();
});

it('should render children when isLoading is false and error is null', () => {
render(
<AsyncRenderer isLoading={false} error={null} data={null}>
Children
</AsyncRenderer>
);
expect(screen.getByText('Children')).toBeInTheDocument();
});
});
File renamed without changes.
2 changes: 2 additions & 0 deletions src/shared/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './loader/index.component';
export * from './headings/index.component';
export * from './error-boundary/index.component';
export * from './async-renderer/index.component';
export * from './internet-notifier/index.component';
7 changes: 3 additions & 4 deletions src/shared/components/internet-notifier/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { StoryFn, Meta } from '@storybook/react';

import { Meta } from '@storybook/react';

import { InternetNotifier } from './index.component';

Expand All @@ -8,6 +9,4 @@ export default {
component: InternetNotifier,
} as Meta<typeof InternetNotifier>;

export const TurnOnAndOffYourWifiToSee: StoryFn<typeof InternetNotifier> = () => (
<InternetNotifier />
);
export const TurnOnAndOffYourWifiToSee = () => <InternetNotifier />;
24 changes: 24 additions & 0 deletions src/shared/components/loader/base/index.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import { useBaseLogic } from './useBaseLogic';

type PrimitiveDivPropTypes = React.ComponentPropsWithoutRef<'div'>;
export type LoaderElement = React.ElementRef<'div'>;
export interface BasePropTypes extends PrimitiveDivPropTypes {
isLoading: boolean;
benefitOf?: number;
}

export const Base = React.forwardRef<LoaderElement, BasePropTypes>(function Base(
{ isLoading, benefitOf, children, ...restProps },
forwardedRef
) {
const { isDoneLoading } = useBaseLogic({ isLoading, benefitOf });

return (
<div {...restProps} ref={forwardedRef} aria-live="polite" aria-busy={isLoading}>
<div>{children}</div>
{isDoneLoading && <p>Done Loading</p>}
</div>
);
});
53 changes: 53 additions & 0 deletions src/shared/components/loader/base/useBaseLogic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useState, useEffect } from 'react';

type ArgType = {
isLoading: boolean;
/**
* @benefitOf the maximum time in milliseconds if the
* side effect if not DoneLoading the loader will be shown
*/
benefitOf?: number;
};

export const useBaseLogic = ({ isLoading, benefitOf = 2000 }: ArgType) => {
const [loading, setLoading] = useState(false);

/**
* @isDoneLoading is a state that will be set to true to notify
* the accessibility gadget users that loading is done
*/
const [isDoneLoading, setDoneLoading] = useState(false);

/**
* Give the side effect a benefit of 20000ms to show the loader
*/
useEffect(() => {
let timer: NodeJS.Timeout;
if (isLoading) timer = setTimeout(() => setLoading(true), benefitOf);
return () => clearTimeout(timer);
});

/**
* Tell accessibility gadget users that loading is done
*/
useEffect(() => {
if (loading && !isLoading && !isDoneLoading) {
setLoading(false);
setDoneLoading(true);
}
}, [isDoneLoading, isLoading, loading]);

/**
* Turn isDoneLoading to false after 2000ms
*/
useEffect(() => {
let timer: NodeJS.Timeout;
if (isDoneLoading) timer = setTimeout(() => setDoneLoading(false), 2000);
return () => clearTimeout(timer);
});

return {
loading,
isDoneLoading,
};
};
15 changes: 15 additions & 0 deletions src/shared/components/loader/gif/index.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

import { OuterRing, InnerRing } from './index.style';
import { Base, LoaderElement, BasePropTypes } from '../base/index.component';

export const GifLoader = React.forwardRef<LoaderElement, BasePropTypes>(
function GifLoader(props, forwardedRef) {
return (
<Base {...props} ref={forwardedRef}>
<OuterRing />
<InnerRing />
</Base>
);
}
);
41 changes: 41 additions & 0 deletions src/shared/components/loader/gif/index.style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import styled, { keyframes } from 'styled-components';

const rotate = keyframes`
0% {
transform:rotate(0deg);
}
100% {
transform:rotate(360deg);
}
`;

const OuterRing = styled.div`
position: absolute;
left: calc(50% - 150px);
height: 150px;
width: 150px;
border-radius: 50%;
background-image: linear-gradient(
135deg,
#feed07 0%,
#fe6a50 5%,
#ed00aa 15%,
#2fe3fe 50%,
#8900ff 100%
);
animation-duration: 2s;
animation-name: ${rotate};
animation-iteration-count: infinite;
`;

const InnerRing = styled.div`
position: absolute;
left: calc(50% - 140px);
height: 140px;
width: 140px;
border-radius: 50%;
background-image: linear-gradient(0deg, #36295e, #1c1045);
`;

export { OuterRing, InnerRing };
3 changes: 3 additions & 0 deletions src/shared/components/loader/index.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './base/index.component';
export * from './gif/index.component';
export * from './text/index.component';
14 changes: 14 additions & 0 deletions src/shared/components/loader/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

import { Meta } from '@storybook/react';

import * as Loaders from './index.component';

export default {
title: 'Components/Loaders',
component: Loaders.Base,
} as Meta<typeof Loaders.Base>;

export const TextLoader = () => <Loaders.TextLoader isLoading />;

export const GifLoader = () => <Loaders.GifLoader isLoading />;
9 changes: 9 additions & 0 deletions src/shared/components/loader/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as Loaders from './index.component';

describe('<Loaders/>', () => {
describe('<TextLoader/>', () => {});

describe('<GifLoader/>', () => {});

describe('<SkeletonLoader/>', () => {});
});
Empty file.
14 changes: 14 additions & 0 deletions src/shared/components/loader/text/index.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

import { Text } from './index.style';
import { Base, LoaderElement, BasePropTypes } from '../base/index.component';

export const TextLoader = React.forwardRef<LoaderElement, BasePropTypes>(
function TextLoader(props, forwardedRef) {
return (
<Base {...props} ref={forwardedRef}>
<Text>Loading...</Text>
</Base>
);
}
);
16 changes: 16 additions & 0 deletions src/shared/components/loader/text/index.style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import styled, { keyframes } from 'styled-components';

const dim = keyframes`
0% {
color: inherit;
}
100% {
color: hsl(200, 20%, 95%);
}
`;

export const Text = styled.p`
opacity: 0.7;
animation: ${dim} 1s linear infinite alternate;
`;
Loading

0 comments on commit 7d263db

Please sign in to comment.