Skip to content
This repository has been archived by the owner on May 17, 2023. It is now read-only.

Commit

Permalink
feat(affix): added Affix component
Browse files Browse the repository at this point in the history
re #15
  • Loading branch information
fers4t committed Dec 14, 2022
1 parent 978ca09 commit 1432045
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
96 changes: 96 additions & 0 deletions src/components/affix/Affix.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { useEffect, useState } from 'react';
import { useEventListener } from '../../hooks';
import { Portal } from '../portal/Portal';

export interface AffixProps {
children?: React.ReactNode;
/**
* The y position of the affix when it is shown. Default is 30.
*/
displayPosition?: number;
position?: {
bottom?: number;
left?: number;
right?: number;
top?: number;
};
/**
* If true, on click to children, the page will scroll to top.
*/
scrollToTopOnClick?: boolean;
target?: HTMLElement;
zIndex?: number;
}

const Affix: React.FunctionComponent<AffixProps> = (props) => {
const {
children, // ✅
displayPosition, // ✅
scrollToTopOnClick, // ✅
position, // ✅
target, // ✅
zIndex, // ✅
} = props;

console.log({ target });
const [topY, setTopY] = useState<number>(0);

const _position = position || {
bottom: 20,
right: 20,
};

const _children = React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, {
// @ts-ignore
onClick: () => {
if (scrollToTopOnClick) {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
}
child.props.onClick && child.props.onClick();
},
});
}
return child;
});

useEventListener(
'scroll',
(a) => {
console.log({ a });
const offsetTop = Math.abs(
// @ts-ignore
a.target.body // @ts-ignore
? a.target.body.getBoundingClientRect().y // @ts-ignore
: a.target.scrollTop,
);
console.log({ offsetTop });
setTopY(offsetTop);
},
target!,
);

const [showAffix, setShowAffix] = useState<boolean>(false);

useEffect(() => {
if (!topY || !displayPosition) return;
if (topY > displayPosition) setShowAffix(true);
else setShowAffix(false);
}, [displayPosition, topY]);

return (showAffix && (
<Portal className="fixed" style={{ ..._position, zIndex }} target={target}>
{_children}
</Portal>
)) as any;
};

Affix.defaultProps = {
displayPosition: 30,
};

export { Affix };
28 changes: 28 additions & 0 deletions src/stories/Affix.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Affix } from '../components/affix/Affix';
import { Button } from '../components/button/Button';

// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: 'Display/Affix',
component: Affix,
parameters: {
docs: {
description: {
component: 'Scroll `30`px to see the magic',
},
},
},
} as ComponentMeta<typeof Affix>;

// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template: ComponentStory<typeof Affix> = (args) => <Affix {...args} />;

export const Default = Template.bind({});

Default.args = {
children: <Button>Scroll to Top</Button>,
displayPosition: 30,
scrollToTopOnClick: true,
};

0 comments on commit 1432045

Please sign in to comment.