Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions system/libs/figa-hooks/src/lib/use-element-size/defs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { MutableRefObject } from 'react';

interface ElementSize {
width: number;
height: number;
Expand All @@ -19,18 +21,21 @@ type ElementSizeState = UndetectedState | DetectedState | UnsupportedState;

type ElementSizeStateStatus = ElementSizeState['status'];

/** Configuration object. */
interface UseElementSizeConfig {
/** It quantifies how much time is needed to broadcast the next event in milliseconds. */
interface ElementSizeConfig {
delay?: number;
}

type ElementSizeReturn<T extends HTMLElement> = Readonly<
[ElementSizeState, MutableRefObject<T | null>]
>;

export type {
UndetectedState,
ElementSize,
DetectedState,
UnsupportedState,
ElementSizeStateStatus,
ElementSizeState,
UseElementSizeConfig,
ElementSizeReturn,
ElementSizeConfig,
};
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ describe('Element size can be detected when: ', () => {

expect(observeSpy).toHaveBeenCalledTimes(1);
expect(observeSpy).toHaveBeenCalledWith(document.body);
expect(result.current.state).toEqual({
expect(result.current[0]).toEqual({
status: 'undetected',
} as ElementSizeState);

await waitFor(() => {
expect(result.current.state).toEqual({
expect(result.current[0]).toEqual({
status: 'detected',
height: HEIGHT,
width: WIDTH,
Expand All @@ -67,7 +67,7 @@ describe('Element size can be detected when: ', () => {

it('updates state if listening native HTML element', async () => {
const ComponentFixture = () => {
const { ref, state } = useElementSize<HTMLDivElement>();
const [state, ref] = useElementSize<HTMLDivElement>();
return (
<div ref={ref}>
{state.status === 'detected'
Expand Down
34 changes: 18 additions & 16 deletions system/libs/figa-hooks/src/lib/use-element-size/use-element-size.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
import { useEffect, useRef, useState, useMemo } from 'react';
import { Subject, throttleTime } from 'rxjs';
import type { ElementSizeState, UseElementSizeConfig } from './defs';
import type {
ElementSizeState,
ElementSizeConfig,
ElementSizeReturn,
} from './defs';
import { useIsomorphicLayoutEffect } from '../use-isomorphic-layout-effect';

/**
* The hook responsible for detecting the height and width of
* any HTML element. By default it checks body.
*
* It returns reference and state to work with.
* @param {UseElementSizeConfig} config - Configuration object.
* @param {ElementSizeConfig} config - Configuration object.
*/
const useElementSize = <T extends HTMLElement>(
config?: UseElementSizeConfig
) => {
config?: ElementSizeConfig
): ElementSizeReturn<T> => {
const [state, setState] = useState<ElementSizeState>({
status: 'undetected',
});

const ref = useRef<T>(null);
const observerRef = useRef<ResizeObserver | null>(null);

const changed = useMemo(() => new Subject<ElementSizeState>(), []);
// eslint-disable-next-line react-hooks/exhaustive-deps
const changed$ = useMemo(() => changed.asObservable(), []);
const { changed, changed$ } = useMemo(() => {
const changed = new Subject<ElementSizeState>();
const changed$ = changed.asObservable();
return { changed, changed$ };
}, []);

useEffect(() => {
const sub = changed$.pipe(throttleTime(config?.delay ?? 150)).subscribe({
Expand All @@ -33,10 +40,9 @@ const useElementSize = <T extends HTMLElement>(
return () => {
sub.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [changed$, config?.delay]);

useEffect(() => {
useIsomorphicLayoutEffect(() => {
const observeElement = () => {
if (!ref?.current && !document.body) {
changed.next({ status: 'unsupported' });
Expand All @@ -61,13 +67,9 @@ const useElementSize = <T extends HTMLElement>(
return () => {
observerRef.current?.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [changed]);

return {
state,
ref,
};
return [state, ref];
};

export { useElementSize };
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ const CreatorLayout = ({
...props
}: CreatorLayoutProps) => {
const [view, setView] = useState<CreatorLayoutView>('undetected');
const { state: size } = useElementSize({ delay: 20 });
const [size] = useElementSize({ delay: 20 });

const [Code, Preview] = children;

Expand Down
15 changes: 15 additions & 0 deletions system/libs/figa-ui/src/lib/timeline/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions system/libs/figa-ui/src/lib/timeline/defs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interface IEvent {
date: string;
title: string;
description: string;
isReached: boolean;
}

export type { IEvent };
2 changes: 2 additions & 0 deletions system/libs/figa-ui/src/lib/timeline/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './defs';
export * from './timeline';
32 changes: 32 additions & 0 deletions system/libs/figa-ui/src/lib/timeline/mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const mocks = [
{
date: 'Jan 20',
title: 'Start of Evaluation',
description: 'Preparation',
},
{
date: 'Feb 14',
title: 'Initial Scoping',
description: 'Data checking',
},
{
date: 'March 8',
title: 'Validation',
description: 'Completed proof of concept',
},
{
date: 'Apr 30',
title: 'Contracting',
description: 'Signed contract',
},
{
date: 'June 7',
title: 'Migration',
description: 'All information is migrated',
},
{
date: 'Sep 16',
title: 'Global Launch',
description: 'In Asia, Australia, Latin America',
},
];
22 changes: 22 additions & 0 deletions system/libs/figa-ui/src/lib/timeline/timeline.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Story, Meta } from '@storybook/react';

import { Timeline } from './timeline';
import { Box } from '../box';

export default {
component: Timeline,
title: 'Timeline',
} as Meta;

const Template: Story = () => {
return (
<Box style={{ height: '100vh' }}>
<Box margin="auto">
<Timeline />
</Box>
</Box>
);
};

export const Default = Template.bind({});
Default.args = {};
175 changes: 175 additions & 0 deletions system/libs/figa-ui/src/lib/timeline/timeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';

import { Font } from '../font';
import { mocks } from './mocks';

const StyledTimeline = styled.div`
width: 900px;
height: 20px;
background-color: #ffffff33;
border-radius: 50px;
display: flex;
position: relative;
`;

const EventContainer = styled.div`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
& > div {
background-color: black;
width: 50px;
height: 50px;
border-radius: 50%;
border: 3px solid;
position: relative;
z-index: 1;
}
`;

const ProgressBar = styled.div`
position: absolute;
height: 20px;
width: ${({ width }: { width: string }) => `${width}%`};
left: 0;
top: 0;
background-color: #35b78b;
border-radius: 50px;
`;

const MoveProgressBar = styled('input')`
position: absolute;
width: 500px;
transform: rotate(-90deg);
transform-origin: center;
left: -200px;
`;

const Timeline = () => {
const [progressBar, setProgressBar] = useState<string>('30');
const [reachedEventIndex, setReachedEventIndex] = useState(0);

const eventRefs = useRef<HTMLDivElement[]>([]);
const progressBarRef = useRef<HTMLDivElement | null>(null);

useEffect(() => {
const positions = eventRefs.current.map((ref) => {
return ref.getBoundingClientRect().x;
});

const checkIfEventIsReached = () => {
const ref = progressBarRef.current;
if (ref === null) throw Error('ProgressBar ref is null');
const progressBarPosition = ref.getBoundingClientRect().right;
const reachedEventsPositions = positions.filter(
(pos) => pos <= progressBarPosition
);

setReachedEventIndex(reachedEventsPositions.length - 1);
};
checkIfEventIsReached();
}, [progressBar]);

return (
<>
<StyledTimeline>
<EventContainer>
{mocks.map((event, index) => (
<div
key={event.title}
ref={(ref) => {
if (ref === null) return;
eventRefs.current[index] = ref;
}}
style={{
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderColor:
index === 0 || index <= reachedEventIndex
? '#35b78b'
: '#ffffff33',
transition: '0.2s',
}}
>
{index <= reachedEventIndex && index !== mocks.length - 1 && (
<svg
fill="#35b78b"
version="1.1"
id="Capa_1"
xmlns="http://www.w3.org/2000/svg"
width="20px"
height="20px"
viewBox="0 0 352.62 352.62"
>
<g>
<path
d="M337.222,22.952c-15.912-8.568-33.66,7.956-44.064,17.748c-23.867,23.256-44.063,50.184-66.708,74.664
c-25.092,26.928-48.348,53.856-74.052,80.173c-14.688,14.688-30.6,30.6-40.392,48.96c-22.032-21.421-41.004-44.677-65.484-63.648
c-17.748-13.464-47.124-23.256-46.512,9.18c1.224,42.229,38.556,87.517,66.096,116.28c11.628,12.24,26.928,25.092,44.676,25.704
c21.42,1.224,43.452-24.48,56.304-38.556c22.645-24.48,41.005-52.021,61.812-77.112c26.928-33.048,54.468-65.485,80.784-99.145
C326.206,96.392,378.226,44.983,337.222,22.952z M26.937,187.581c-0.612,0-1.224,0-2.448,0.611
c-2.448-0.611-4.284-1.224-6.732-2.448l0,0C19.593,184.52,22.653,185.132,26.937,187.581z"
/>
</g>
</svg>
)}
{index === mocks.length - 1 && (
<Font
variant="b2"
style={{ fontSize: '14px', color: '#35b78b' }}
>
{`${progressBar}`}
<span style={{ fontSize: '12px', color: '#35b78b' }}>%</span>
</Font>
)}
<div
style={{
position: 'absolute',
bottom: '-120%',
left: '50%',
transform: 'translateX(-50%)',
}}
>
<Font
variant="h3"
style={{
fontSize: '10px',
whiteSpace: 'nowrap',
textAlign: 'center',
color: index <= reachedEventIndex ? 'white' : '#555',
}}
>
{event.title}
</Font>
<Font
variant="b1"
style={{
fontSize: '7px',
whiteSpace: 'nowrap',
textAlign: 'center',
color: index <= reachedEventIndex ? 'white' : '#555',
}}
>
{event.description}
</Font>
</div>
</div>
))}
</EventContainer>
<ProgressBar width={progressBar} ref={progressBarRef} />
</StyledTimeline>
<MoveProgressBar
type="range"
value={progressBar}
onChange={(e) => setProgressBar(e.target.value)}
/>
</>
);
};

export { Timeline };