Skip to content

Commit

Permalink
fix: track approver children
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Mar 7, 2024
1 parent 93592e0 commit cb142fc
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 21 deletions.
18 changes: 13 additions & 5 deletions src/app/common/hooks/use-register-children.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { useState } from 'react';

export function useRegisterChildren<T extends string>() {
const [children, setChildren] = useState<Record<T, number>>({} as Record<T, number>);
const [childCount, setChildCount] = useState<Record<T, number>>({} as Record<T, number>);

function registerChild(child: T) {
setChildren(children => ({ ...children, [child]: (children[child] || 0) + 1 }));
setChildCount(children => ({ ...children, [child]: (children[child] || 0) + 1 }));
}

function deregisterChild(child: T) {
setChildren(children => ({ ...children, [child]: (children[child] || 0) - 1 }));
setChildCount(children => ({ ...children, [child]: (children[child] || 0) - 1 }));
}

function hasChild(child: T) {
return children[child] > 0;
return childCount[child] > 0;
}

return { children: Object.keys(children) as T[], registerChild, deregisterChild, hasChild };
return {
childCount,
children: Object.keys(childCount) as T[],
registerChild,
deregisterChild,
hasChild,
};
}

export type ChildRegister<T extends string> = ReturnType<typeof useRegisterChildren<T>>;
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const childElementInitialAnimationState = css({
[animationSelector]: { opacity: 0, transform: 'translateY(-16px)' },
});

const staggerMenuItems = stagger(0.06, { startDelay: 0.32 });
const staggerMenuItems = stagger(0.06, { startDelay: 0.36 });

export function useApproverChildrenEntryAnimation() {
const [scope, animate] = useAnimate();
Expand All @@ -33,17 +33,14 @@ export function useApproverChildrenEntryAnimation() {
return scope;
}

const initialState = { x: -20, opacity: 0 } as const;
const animateState = { x: 0, opacity: 1, transition: { duration: 0.32 } } as const;

interface ApproverHeaderAnimationProps extends HasChildren {
delay?: number;
}
export function ApproverHeaderAnimation({ delay = 0, ...props }: ApproverHeaderAnimationProps) {
return (
<motion.div
initial={initialState}
animate={{ ...animateState, transition: { ...animateState, delay } }}
initial={{ x: -18, opacity: 0 }}
animate={{ x: 0, opacity: 1, transition: { duration: 0.4, delay, ease: 'easeOut' } }}
{...props}
/>
);
Expand All @@ -53,7 +50,7 @@ export function ApproverActionsAnimation(props: HasChildren) {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ ...animateState, transition: { ...animateState.transition, delay: 0.64 } }}
animate={{ opacity: 1, transition: { duration: 0.4, delay: 0.68, ease: 'easeOut' } }}
{...props}
/>
);
Expand Down
21 changes: 19 additions & 2 deletions src/app/features/approver/approver.context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { createContext, useContext } from 'react';

export type ApproverChildren = 'header' | 'actions' | 'advanced' | 'section' | 'subheader';
import { useOnMount } from '@app/common/hooks/use-on-mount';
import { type ChildRegister, useRegisterChildren } from '@app/common/hooks/use-register-children';

interface ApproverContext {
type ApproverChildren = 'header' | 'actions' | 'advanced' | 'section' | 'subheader';

interface ApproverContext extends ChildRegister<ApproverChildren> {
isDisplayingAdvancedView: boolean;
setIsDisplayingAdvancedView(val: boolean): void;
}
Expand All @@ -16,3 +19,17 @@ export function useApproverContext() {
if (!context) throw new Error('`useApproverContext` must be used within a `ApproverProvider`');
return context;
}

export function useRegisterApproverChildren() {
return useRegisterChildren<ApproverChildren>();
}

export function useRegisterApproverChild(child: ApproverChildren) {
const { registerChild, deregisterChild, childCount } = useApproverContext();
if (childCount.actions > 1) throw new Error('Only one `Approver.Actions` is allowed');
if (childCount.advanced > 1) throw new Error('Only one `Approver.Advanced` is allowed');
useOnMount(() => {
registerChild(child);
return () => deregisterChild(child);
});
}
7 changes: 5 additions & 2 deletions src/app/features/approver/approver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState } from 'react';

import type { HasChildren } from '@app/common/has-children';

import { ApproverProvider } from './approver.context';
import { ApproverProvider, useRegisterApproverChildren } from './approver.context';
import { ApproverActions } from './components/approver-actions';
import { ApproverAdvanced } from './components/approver-advanced';
import { ApproverContainer } from './components/approver-container';
Expand All @@ -12,9 +12,12 @@ import { ApproverSubheader } from './components/approver-subheader';

function Approver(props: HasChildren) {
const [isDisplayingAdvancedView, setIsDisplayingAdvancedView] = useState(false);
const childRegister = useRegisterApproverChildren();

return (
<ApproverProvider value={{ isDisplayingAdvancedView, setIsDisplayingAdvancedView }}>
<ApproverProvider
value={{ ...childRegister, isDisplayingAdvancedView, setIsDisplayingAdvancedView }}
>
<ApproverContainer {...props} />
</ApproverProvider>
);
Expand Down
4 changes: 3 additions & 1 deletion src/app/features/approver/components/approver-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import { Flex, styled } from 'leather-styles/jsx';

import type { HasChildren } from '@app/common/has-children';

import { ApproverActionsAnimation } from './approver-animation';
import { ApproverActionsAnimation } from '../approver-animation';
import { useRegisterApproverChild } from '../approver.context';

const stretchChildrenStyles = css({ '& > *': { flex: 1 } });

interface ApproverActionsProps extends HasChildren {
actions: React.ReactNode;
}
export function ApproverActions({ children, actions }: ApproverActionsProps) {
useRegisterApproverChild('actions');
return (
<styled.footer pos="sticky" mt="auto" bottom={0} className="skip-animation">
<ApproverActionsAnimation>
Expand Down
3 changes: 2 additions & 1 deletion src/app/features/approver/components/approver-advanced.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import { Flag } from '@app/ui/components/flag/flag';
import { ChevronDownIcon } from '@app/ui/icons';

import { AnimateChangeInHeight } from '../../../components/animate-height';
import { useApproverContext } from '../approver.context';
import { useApproverContext, useRegisterApproverChild } from '../approver.context';

const slightPauseForContentEnterAnimation = createDelay(120);

export function ApproverAdvanced({ children }: HasChildren) {
const { isDisplayingAdvancedView, setIsDisplayingAdvancedView } = useApproverContext();
useRegisterApproverChild('advanced');

const ref = useRef<HTMLButtonElement>(null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { HasChildren } from '@app/common/has-children';
import {
childElementInitialAnimationState,
useApproverChildrenEntryAnimation,
} from './approver-animation';
} from '../approver-animation';

const applyMarginsToLastApproverSection = css({
'& .approver-section:last-child': { mb: 'space.03' },
Expand Down
6 changes: 4 additions & 2 deletions src/app/features/approver/components/approver-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@ import type { ReactNode } from 'react';

import { styled } from 'leather-styles/jsx';

import { ApproverHeaderAnimation } from './approver-animation';
import { ApproverHeaderAnimation } from '../approver-animation';
import { useRegisterApproverChild } from '../approver.context';

interface ApproverHeaderProps {
title: ReactNode;
requester: ReactNode;
}
export function ApproverHeader({ title, requester }: ApproverHeaderProps) {
useRegisterApproverChild('header');
return (
<styled.header px="space.05" py="space.03" className="skip-animation">
<ApproverHeaderAnimation>
<styled.h1 textStyle="heading.03">{title}</styled.h1>
</ApproverHeaderAnimation>
<ApproverHeaderAnimation delay={0.03}>
<ApproverHeaderAnimation delay={0.04}>
<styled.p textStyle="label.02" mt="space.03">
Requested by {requester}
</styled.p>
Expand Down
3 changes: 3 additions & 0 deletions src/app/features/approver/components/approver-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { styled } from 'leather-styles/jsx';

import type { HasChildren } from '@app/common/has-children';

import { useRegisterApproverChild } from '../approver.context';

export function ApproverSection(props: HasChildren) {
useRegisterApproverChild('section');
return (
<styled.section
className="approver-section"
Expand Down
3 changes: 3 additions & 0 deletions src/app/features/approver/components/approver-subheader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { type HTMLStyledProps, styled } from 'leather-styles/jsx';

import { useRegisterApproverChild } from '../approver.context';

type ApproverSubheaderProps = HTMLStyledProps<'h2'>;

export function ApproverSubheader(props: ApproverSubheaderProps) {
useRegisterApproverChild('subheader');
return <styled.h2 textStyle="label.01" mb="space.03" {...props} />;
}

0 comments on commit cb142fc

Please sign in to comment.