Skip to content

Commit

Permalink
feat: support children as fn and remove disclosure
Browse files Browse the repository at this point in the history
  • Loading branch information
jmfrancois committed Jul 20, 2023
1 parent 560eb2e commit 7094afa
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 83 deletions.
1 change: 0 additions & 1 deletion .changeset/chatty-apes-speak.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ Breaking changes:
* HTML structure output may have changed
* Some passed props from our component to reakit and not documented as a usage as been removed. If you need a different usage let us knwow, now we own the code
* Tabs props API has been completly changed
* Tooltip do not use cloneElement and accept only children as function now.

Components changed:
* Accordion (useId)
Expand Down
61 changes: 31 additions & 30 deletions packages/design-system/src/components/Popover/Popover.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { forwardRef, Ref } from 'react';
import { action } from '@storybook/addon-actions';
import { Popover, ButtonPrimary, ButtonIcon, StackVertical, Form } from '../../';
import { DisclosureFnProps } from '../Disclosure/Disclosure';
import { Popover, ButtonPrimary, StackVertical, Form } from '../../';
import { PopoverTriggerProps } from './';

export default {
component: Popover,
Expand All @@ -10,61 +10,60 @@ export default {
const EasyPopover = () => <StackVertical gap="S">Hello hello</StackVertical>;

/* eslint-disable-next-line react/display-name */
const OpenPopover = forwardRef((props: any, ref: Ref) => {
const OpenPopover = forwardRef(({ onClick, ...props }: any, ref: Ref) => {

Check warning on line 13 in packages/design-system/src/components/Popover/Popover.stories.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/components/Popover/Popover.stories.tsx#L13

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.
return (
<ButtonPrimary onClick={action('Clicked disclosure')} {...props} ref={ref}>
<ButtonPrimary
onClick={() => {
if (onClick) {
onClick();
}
action('Clicked disclosure');
}}
{...props}
ref={ref}
>
Open popover
</ButtonPrimary>
);
});

export const DefaultStory = () => (
<div style={{ padding: '1.2rem' }}>
<Popover aria-label="Custom popover" disclosure={<OpenPopover />}>
Text Content
<Popover aria-label="Custom popover" popup="Text Content">
<OpenPopover />
</Popover>
</div>
);

export const DisclosureStory = () => (
export const ChildrenAsFunctionStory = () => (
<div style={{ padding: '1.2rem' }}>
<Popover
aria-label="Custom popover"
disclosure={
<ButtonIcon onClick={action('Clicked disclosure')} icon="question-filled">
Open popover
</ButtonIcon>
}
>
Text Content
<Popover aria-label="Custom popover" popup="Text Content">
{(props: PopoverTriggerProps) => <OpenPopover {...props} />}
</Popover>
</div>
);

export const FormDisclosureStory = () => (
export const FormFocusStory = () => (
<div style={{ padding: '1.2rem' }}>
<Popover
aria-label="Custom popover"
focusOnDisclosure
disclosure={<Form.Text name="text" label="Text enabled" />}
>
Text Content
<Popover aria-label="Custom popover" popup="Text Content">
<Form.Text name="text" label="Text enabled" />
</Popover>
</div>
);

export const WithoutPaddingStory = () => (
<div style={{ padding: '1.2rem' }}>
<Popover aria-label="Custom popover" disclosure={<OpenPopover />} isFixed hasPadding={false}>
Text Content without padding
export const FixedStory = () => (
<div>
<Popover aria-label="Custom popover" popup="Text Content" isFixed hasPadding={false}>
<OpenPopover />
</Popover>
</div>
);

export const WithFunctionAsChildren = () => (
export const WithContentAsFunction = () => (
<div style={{ padding: '1.2rem' }}>
<Popover aria-label="Custom popover" disclosure={<OpenPopover />}>
{(popover: DisclosureFnProps) => (
<Popover
aria-label="Custom popover"
popup={(popover: DisclosureFnProps) => (
<>
<StackVertical gap="S">
There is some content
Expand All @@ -73,6 +72,8 @@ export const WithFunctionAsChildren = () => (
<EasyPopover />
</>
)}
>
<OpenPopover />
</Popover>
</div>
);
78 changes: 37 additions & 41 deletions packages/design-system/src/components/Popover/Popover.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,65 @@
import { useRef } from 'react';
import type { ReactNode } from 'react';
import { useRef, Fragment } from 'react';
import type { ReactNode, MouseEvent } from 'react';
import tokens from '@talend/design-tokens';

import { Placement, FloatingArrow, FloatingPortal } from '@floating-ui/react';
import { usePopover } from './usePopover';
import { Disclosure } from '../Disclosure/Disclosure';
import theme from './Popover.module.scss';
import { renderOrClone, ChildrenOrFn } from '../../renderOrClone';

interface PopoverOptions {
disclosure?: ReactNode;
type PopoverOptions = {
popup: ReactNode | ((props: any) => ReactNode);

Check warning on line 11 in packages/design-system/src/components/Popover/Popover.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/components/Popover/Popover.tsx#L11

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.
initialOpen?: boolean;
placement?: Placement;
modal?: boolean;
open?: boolean;
isFixed?: boolean;
onOpenChange?: (open: boolean) => void;
}
};

export type PopoverProps = {
children: ChildrenOrFn;
} & PopoverOptions;

export function Popover({
children,
disclosure,
modal = true,
isFixed = false,
popup,
...restOptions
}: {
children: ReactNode;
} & PopoverOptions) {
}: PopoverProps) {
// This can accept any props as options, e.g. `placement`,
// or other positioning options.
const arrowRef = useRef<SVGSVGElement>(null);
const popover = usePopover({ modal, arrowRef, ...restOptions });

let childrenRendered = children;
if (typeof children === 'function') {
childrenRendered = children(popover);
}
let content = (
<div
ref={popover.refs.setFloating}
className={theme.popover}
style={{ ...popover.floatingStyles, display: popover.open ? 'block' : 'none' }}
aria-labelledby={popover.labelId}
aria-describedby={popover.descriptionId}
{...popover.getFloatingProps(restOptions)}
>
<FloatingArrow
ref={arrowRef}
context={popover.context}
strokeWidth={1}
stroke={tokens.coralColorIllustrationShadow}
fill={tokens.coralColorNeutralBackground}
/>
{childrenRendered}
</div>
);
if (isFixed) {
content = <FloatingPortal>{content}</FloatingPortal>;
}
const disclosureProps = popover.getReferenceProps({ onClick: e => e.stopPropagation() });
const Wrapper = isFixed ? FloatingPortal : Fragment;
const onClick = (e: MouseEvent<HTMLElement>) => {
e.preventDefault();
};
const childrenProps = popover.getReferenceProps({ onClick });
return (
<>
<Disclosure popref={popover.refs.setReference} {...disclosureProps}>
{disclosure}
</Disclosure>
{content}
{renderOrClone(children, { ...childrenProps, ref: popover.refs.setReference })}
<Wrapper>
<div
ref={popover.refs.setFloating}
className={theme.popover}
style={{ ...popover.floatingStyles, display: popover.open ? 'block' : 'none' }}
aria-labelledby={popover.labelId}
aria-describedby={popover.descriptionId}
{...popover.getFloatingProps(restOptions)}
>
<FloatingArrow
ref={arrowRef}
context={popover.context}
strokeWidth={1}
stroke={tokens.coralColorIllustrationShadow}
fill={tokens.coralColorNeutralBackground}
/>
{typeof popup === 'function' ? popup(popover.getFloatingProps()) : popup}
</div>
</Wrapper>
</>
);
}
3 changes: 2 additions & 1 deletion packages/design-system/src/components/Popover/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Popover } from './Popover';

export type { PopoverProps } from './Popover';
export type { PopoverTriggerProps } from './usePopover';
export default Popover;
28 changes: 25 additions & 3 deletions packages/design-system/src/components/Popover/usePopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,36 @@ import {
const ARROW_HEIGHT = 7;
const GAP = 2;

interface PopoverOptions {
type PopoverOptions = {
initialOpen?: boolean;
arrowRef?: MutableRefObject<SVGSVGElement | null>;
placement?: Placement;
modal?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}
};

export type PopoverTriggerProps = {
onClick?: (event: any) => void;

Check warning on line 30 in packages/design-system/src/components/Popover/usePopover.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/components/Popover/usePopover.tsx#L30

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.
ref: any;

Check warning on line 31 in packages/design-system/src/components/Popover/usePopover.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/components/Popover/usePopover.tsx#L31

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.
};

export type UsePopoverType = {
// floating-ui types
refs: any;

Check warning on line 36 in packages/design-system/src/components/Popover/usePopover.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/components/Popover/usePopover.tsx#L36

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.
getFloatingProps: (props?: any) => any;

Check warning on line 37 in packages/design-system/src/components/Popover/usePopover.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/components/Popover/usePopover.tsx#L37

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.

Check warning on line 37 in packages/design-system/src/components/Popover/usePopover.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/components/Popover/usePopover.tsx#L37

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.
floatingStyles: any;

Check warning on line 38 in packages/design-system/src/components/Popover/usePopover.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/components/Popover/usePopover.tsx#L38

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.
context: any;

Check warning on line 39 in packages/design-system/src/components/Popover/usePopover.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/components/Popover/usePopover.tsx#L39

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.
getReferenceProps: (props?: any) => any;

Check warning on line 40 in packages/design-system/src/components/Popover/usePopover.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/components/Popover/usePopover.tsx#L40

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.

Check warning on line 40 in packages/design-system/src/components/Popover/usePopover.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/components/Popover/usePopover.tsx#L40

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.
// local state
open: boolean;
setOpen: (open: boolean) => void;
modal?: boolean;
labelId?: string;
descriptionId?: string;
setLabelId: (id: string) => void;
setDescriptionId: (id: string) => void;
};

export function usePopover({
initialOpen = false,
Expand All @@ -33,7 +55,7 @@ export function usePopover({
modal,
open: controlledOpen,
onOpenChange: setControlledOpen,
}: PopoverOptions = {}) {
}: PopoverOptions = {}): UsePopoverType {
const [uncontrolledOpen, setUncontrolledOpen] = useState(initialOpen);
const [labelId, setLabelId] = useState<string | undefined>();
const [descriptionId, setDescriptionId] = useState<string | undefined>();
Expand Down
13 changes: 6 additions & 7 deletions packages/design-system/src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { useId } from '../../useId';

import styles from './Tooltip.module.scss';
import { renderOrClone } from '../../renderOrClone';

export type Placement =
| 'top-start'
Expand Down Expand Up @@ -84,13 +85,11 @@ const Tooltip = ({ id, children, title, placement = 'top', ...rest }: TooltipPro

return (
<>
{children(
{
...getReferenceProps(),
'aria-describedby': safeId,
},
floating.refs.setReference,
)}
{renderOrClone(children, {
...getReferenceProps(),
'aria-describedby': safeId,
ref: floating.refs.setReference,
})}
<FloatingPortal>
<div
{...getFloatingProps()}
Expand Down
10 changes: 10 additions & 0 deletions packages/design-system/src/renderOrClone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { ReactNode } from 'react';

export type ChildrenOrFn = ReactNode | ((props: any) => ReactNode);

Check warning on line 3 in packages/design-system/src/renderOrClone.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/renderOrClone.ts#L3

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.

export function renderOrClone(children: ChildrenOrFn, props: any = {}): ReactNode {

Check warning on line 5 in packages/design-system/src/renderOrClone.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/design-system/src/renderOrClone.ts#L5

[@typescript-eslint/no-explicit-any] Unexpected any. Specify a different type.
if (typeof children === 'function') {
return children(props);
}
return children && props ? React.cloneElement(children, props) : children;
}

0 comments on commit 7094afa

Please sign in to comment.