Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tooltip component #63

Merged
merged 5 commits into from
Oct 20, 2023
Merged
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
38 changes: 25 additions & 13 deletions src/components/Popup/Popup.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import React from 'react';
import { Meta, StoryFn } from '@storybook/react';
import { withDesign } from 'storybook-addon-designs';
import { Popup, PopupProps } from './Popup';
import { styled } from 'styled-components';
import { Button } from '../Button/Button';
import { height } from 'styled-system';
import { Button } from '../Button';

// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: 'Components/Popup',
component: Popup,
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
args: {
arrow: 'top',
title: 'Title',
content: 'Example Popup content',
showArrow: true,
initiallyOpen: false,
openWith: 'click',
},
parameters: {
design: [
Expand All @@ -31,31 +34,40 @@ export default {
decorators: [withDesign],
} as Meta<typeof Popup>;

const RightButton = styled(Button)<PopupProps>`
align-self: end;
`;

/*
* Example Popup story
*/
const ExamplePopup = ({ children, ...restProps }: PopupProps) => {
return (
<Popup {...restProps}>
{children}
<RightButton label='Button' />
</Popup>
<div style={{ padding: 200 }}>
<Popup {...restProps}>{children}</Popup>
</div>
);
};

// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template: StoryFn<typeof Popup> = (args) => (
<ExamplePopup {...args}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque et
odio sed est pellentesque gravida sit amet at orci.
<Button label='Click me' />
</ExamplePopup>
);

/**
* Default variant (not specified)
*/
export const DefaultVariant = Template.bind({});

/**
* Initially open
*/
export const InitiallyOpen = Template.bind({});
InitiallyOpen.args = { initiallyOpen: true };

/**
* Long text
*/
export const LongText = Template.bind({});
LongText.args = {
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque etodio sed est pellentesque gravida sit amet at orci.',
};
215 changes: 115 additions & 100 deletions src/components/Popup/Popup.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import React from 'react';
import styled, { DefaultTheme, css } from 'styled-components';
import { variant } from 'styled-system';
import React, { useRef, useState } from 'react';
import styled from 'styled-components';
import { pxToRem, ComponentBaseProps, typography } from '../../shared';

export type ArrowDirection = 'none' | 'top' | 'right' | 'bottom' | 'left';
import {
useFloating,
useInteractions,
Placement,
offset,
FloatingArrow,
arrow,
useClick,
useHover,
flip,
} from '@floating-ui/react';

/**
* Dimensions of Popup component
*/
const popupDimensions = {
arrow: {
arrowSize: 8,
arrowOffset: 146,
arrowHeight: 8,
arrowWidth: 20,
},
innerPopup: {
popupWidth: 300,
},
popupTitle: {
paddingTop: 25.5,
paddingRigth: 16,
paddingBottom: 17.5,
paddingTop: 24,
paddingRight: 16,
paddingBottom: 4,
paddingLeft: 16,
},
popupContents: {
Expand All @@ -34,94 +43,53 @@ const popupDimensions = {
*/
export interface PopupProps extends ComponentBaseProps<HTMLDivElement> {
/**
* Popup arrow direction
* Title
*/
arrow?: ArrowDirection;
title?: string;

/**
* Title
* Content
*/
title?: string;
content?: React.ReactNode;

/**
* Children
* The element that has the popup
*/
children?: React.ReactNode;

/**
* Placement of Popup component
*/
placement?: Placement;

/**
* Popup arrow direction
*/
showArrow?: boolean;

/**
* Is popup open initially
*/
initiallyOpen?: boolean;

// How to open popup
openWith?: 'hover' | 'click' | 'both';
}

/**
* Helper function to calculate arrow styles depending on side
* @param props theme props
* @returns modified css
* Arrow component
*/
const arrowStyles = (props: DefaultTheme) => {
return css`
${variant({
prop: 'arrow',
variants: {
top: {
borderLeft: `${pxToRem(
popupDimensions.arrow.arrowSize
)} solid transparent`,
borderRight: `${pxToRem(
popupDimensions.arrow.arrowSize
)} solid transparent`,
borderBottom: `${pxToRem(popupDimensions.arrow.arrowSize)} solid ${
props.theme.colors.grayScale.digitalBlack100
}`,
right: pxToRem(popupDimensions.arrow.arrowOffset),
top: pxToRem(-popupDimensions.arrow.arrowSize),
},
right: {
borderTop: `${pxToRem(
popupDimensions.arrow.arrowSize
)} solid transparent`,
borderBottom: `${pxToRem(
popupDimensions.arrow.arrowSize
)} solid transparent`,
borderLeft: `${pxToRem(popupDimensions.arrow.arrowSize)} solid ${
props.theme.colors.grayScale.digitalBlack100
}`,
top: `calc(50% - ${pxToRem(popupDimensions.arrow.arrowSize)})`,
right: pxToRem(-popupDimensions.arrow.arrowSize),
},
bottom: {
borderLeft: `${pxToRem(
popupDimensions.arrow.arrowSize
)} solid transparent`,
borderRight: `${pxToRem(
popupDimensions.arrow.arrowSize
)} solid transparent`,
borderTop: `${pxToRem(popupDimensions.arrow.arrowSize)} solid ${
props.theme.colors.grayScale.digitalBlack100
}`,
bottom: pxToRem(-popupDimensions.arrow.arrowSize),
right: pxToRem(popupDimensions.arrow.arrowOffset),
},
left: {
borderTop: `${pxToRem(
popupDimensions.arrow.arrowSize
)} solid transparent`,
borderBottom: `${pxToRem(
popupDimensions.arrow.arrowSize
)} solid transparent`,
borderRight: `${pxToRem(popupDimensions.arrow.arrowSize)} solid ${
props.theme.colors.grayScale.digitalBlack100
}`,
top: `calc(50% - ${pxToRem(popupDimensions.arrow.arrowSize)})`,
left: pxToRem(-popupDimensions.arrow.arrowSize),
},
},
})};
`;
};
const StyledArrow = styled(FloatingArrow)`
fill: ${(props) =>
(props.fill = props.theme.colors.grayScale.digitalBlack100)};
`;

/**
* Wrapper for popup component
*/
const InternalPopup = styled.div<PopupProps>`
position: relative;
width: ${pxToRem(popupDimensions.innerPopup.popupWidth)};
max-width: ${pxToRem(popupDimensions.innerPopup.popupWidth)};
background-color: ${(props) => props.theme.colors.grayScale.digitalBlack100};
`;

Expand All @@ -131,10 +99,10 @@ const InternalPopup = styled.div<PopupProps>`
const PopupTitle = styled.div<PopupProps>`
font-weight: ${typography.weight.bold};
font-size: ${typography.size.paragraph};
padding: ${pxToRem(popupDimensions.popupTitle.paddingTop)}
${pxToRem(popupDimensions.popupTitle.paddingRigth)}
${pxToRem(popupDimensions.popupTitle.paddingBottom)}
${pxToRem(popupDimensions.popupTitle.paddingLeft)};
padding-top: ${pxToRem(popupDimensions.popupTitle.paddingTop)};
padding-right: ${pxToRem(popupDimensions.popupTitle.paddingRight)};
padding-bottom: ${pxToRem(popupDimensions.popupTitle.paddingBottom)};
padding-left: ${pxToRem(popupDimensions.popupTitle.paddingLeft)};
`;

/**
Expand All @@ -149,31 +117,78 @@ const PopupContents = styled.div<PopupProps>`
flex-direction: column;
`;

/**
* Popup side arrow component
*/
const Arrow = styled.div<PopupProps>`
width: 0;
height: 0;
position: absolute;

${arrowStyles};
`;

/**
* Popup component
*/
export const Popup = ({
arrow = 'none',
showArrow = false,
children,
title,
placement = 'top',
initiallyOpen,
openWith = 'click',
content,
...restProps
}: PopupProps) => {
const [isOpen, setIsOpen] = useState(initiallyOpen);

const arrowRef = useRef(null);

const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
placement: placement,
middleware: [
offset(10),
flip(),
arrow({
element: arrowRef,
}),
],
});

const hover = useHover(context, {
enabled: openWith === 'hover',
});

const click = useClick(context, {
enabled: openWith === 'click',
});

const { getReferenceProps, getFloatingProps } = useInteractions([
click,
hover,
]);

return (
<InternalPopup {...restProps}>
<Arrow arrow={arrow} />
<PopupTitle>{title}</PopupTitle>
<PopupContents>{children}</PopupContents>
</InternalPopup>
<>
<div
style={{ display: 'inline-block' }}
ref={refs.setReference}
{...getReferenceProps()}
>
{children}
</div>
{isOpen && (
<div
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
>
{showArrow && (
<StyledArrow
ref={arrowRef}
context={context}
height={popupDimensions.arrow.arrowHeight}
width={popupDimensions.arrow.arrowWidth}
/>
)}
<InternalPopup {...restProps}>
{title && <PopupTitle>{title}</PopupTitle>}
<PopupContents>{content}</PopupContents>
</InternalPopup>
</div>
)}
</>
);
};
4 changes: 4 additions & 0 deletions src/components/Sidebar/Sidebar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const Template: StoryFn<typeof Sidebar> = (args) => (
* Default variant
*/
export const DefaultVariant = Template.bind({});

/**
* Sidebar with overlay, content and icon button in header
*/
Expand All @@ -70,20 +71,23 @@ OverlayContentIcon.args = {
sidebarContent: <ListComponent />,
headerContent: <IconButtonComponent />,
};

/**
* Sidebar with overlay
*/
export const Overlay = Template.bind({});
Overlay.args = {
overlay: true,
};

/**
* Sidebar with content
*/
export const SidebarContent = Template.bind({});
SidebarContent.args = {
sidebarContent: <ListComponent />,
};

/**
* Sidebar with icon button in header
*/
Expand Down
Loading