Skip to content

Commit

Permalink
feat: Return unstable_wrap method from props hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
diegohaz committed Apr 22, 2019
1 parent 5eff167 commit f668ae4
Show file tree
Hide file tree
Showing 15 changed files with 131 additions and 88 deletions.
8 changes: 7 additions & 1 deletion packages/reakit/src/Box/Box.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ export type BoxOptions = {
unstable_system?: unknown;
};

export type BoxProps = React.HTMLAttributes<any> & React.RefAttributes<any>;
export type BoxProps = React.HTMLAttributes<any> &
React.RefAttributes<any> & {
/**
* Function returned by hook to wrap children.
*/
unstable_wrap?: (children: React.ReactNode) => JSX.Element;
};

export function useBox(options: BoxOptions = {}, htmlProps: BoxProps = {}) {
options = unstable_useOptions("Box", options, htmlProps);
Expand Down
36 changes: 22 additions & 14 deletions packages/reakit/src/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ export type DialogOptions = HiddenOptions &
/**
* Toggles Dialog's `modal` state.
* - Non-modal: `preventBodyScroll` doesn't work and focus is free.
* - Modal: `preventBodyScroll` is automatically enabled and focus is
* trapped within the dialog.
* - Modal: `preventBodyScroll` is automatically enabled, focus is
* trapped within the dialog and the dialog is rendered within a `Portal`
* by default.
*/
modal?: boolean;
/**
Expand Down Expand Up @@ -54,6 +55,11 @@ export type DialogOptions = HiddenOptions &
* `autoFocusOnHide` disables it.
*/
unstable_finalFocusRef?: React.RefObject<HTMLElement>;
/**
* Whether or not the dialog should be rendered within `Portal`.
* It's `true` by default if `modal` is `true`.
*/
unstable_portal?: boolean;
/**
* Whether or not to move focus when the dialog shows.
* @private
Expand All @@ -76,9 +82,10 @@ export function useDialog(
preventBodyScroll = true,
unstable_autoFocusOnShow = true,
unstable_autoFocusOnHide = true,
unstable_portal: isPortal = modal,
...options
}: DialogOptions,
{ children, ...htmlProps }: DialogProps = {}
htmlProps: DialogProps = {}
) {
let _options: DialogOptions = {
modal,
Expand All @@ -87,14 +94,15 @@ export function useDialog(
preventBodyScroll,
unstable_autoFocusOnShow,
unstable_autoFocusOnHide,
unstable_portal: isPortal,
...options
};
_options = unstable_useOptions("Dialog", _options, htmlProps);

const dialog = React.useRef<HTMLElement>(null);
const portal = usePortalRef(dialog, _options);
const disclosure = useDisclosureRef(_options);
const { dialogs, wrapChildren } = useNestedDialogs(dialog, _options);
const { dialogs, wrap } = useNestedDialogs(dialog, _options);

usePreventBodyScroll(dialog, _options);
useFocusTrap(dialog, portal, _options);
Expand All @@ -111,13 +119,18 @@ export function useDialog(
"aria-modal": _options.modal,
"data-dialog": true,
style: { zIndex: 999 },
children: wrapChildren(children),
onKeyDown: event => {
if (event.key === "Escape" && _options.hide && _options.hideOnEsc) {
event.stopPropagation();
_options.hide();
}
}
},
unstable_wrap: children =>
_options.unstable_portal ? (
<Portal>{wrap(children)}</Portal>
) : (
wrap(children)
)
} as DialogProps,
// If it's not modal, it doesn't have a portal
// So we define dialog itself as portal
Expand All @@ -142,7 +155,8 @@ const keys: Keys<DialogStateReturn & DialogOptions> = [
"unstable_initialFocusRef",
"unstable_finalFocusRef",
"unstable_autoFocusOnShow",
"unstable_autoFocusOnHide"
"unstable_autoFocusOnHide",
"unstable_portal"
];

useDialog.__keys = keys;
Expand All @@ -157,12 +171,6 @@ export const Dialog = unstable_createComponent({
See https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_roles_states_props`,
"Dialog"
);

const element = unstable_useCreateElement(type, props, children);

if (props["aria-modal"]) {
return <Portal>{element}</Portal>;
}
return element;
return unstable_useCreateElement(type, props, children);
}
});
4 changes: 2 additions & 2 deletions packages/reakit/src/Dialog/__utils/useNestedDialogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ export function useNestedDialogs(
[options.visible, addDialog, removeDialog]
);

const wrapChildren = (children: React.ReactNode) => (
const wrap = (children: React.ReactNode) => (
<DialogContext.Provider value={providerValue}>
{children}
</DialogContext.Provider>
);

return { dialogs, wrapChildren };
return { dialogs, wrap };
}
35 changes: 18 additions & 17 deletions packages/reakit/src/Form/FormRadioGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { unstable_useOptions } from "../system/useOptions";
import { unstable_useProps } from "../system/useProps";
import { unstable_createComponent } from "../utils/createComponent";
import { mergeProps } from "../utils/mergeProps";
import { unstable_useCreateElement } from "../utils/useCreateElement";
import { As, PropsWithAs, Keys } from "../__utils/types";
import {
unstable_FormGroupOptions,
Expand Down Expand Up @@ -35,8 +34,24 @@ export function unstable_useFormRadioGroup<V, P extends DeepPath<V, P>>(
options: unstable_FormRadioGroupOptions<V, P>,
htmlProps: unstable_FormRadioGroupProps = {}
) {
const rover = useRoverState({ loop: true });
const providerValue = React.useMemo(() => rover, [
rover.stops,
rover.currentId,
rover.unstable_pastId
]);
options = unstable_useOptions("FormRadioGroup", options, htmlProps);
htmlProps = mergeProps({ role: "radiogroup" } as typeof htmlProps, htmlProps);
htmlProps = mergeProps(
{
role: "radiogroup",
unstable_wrap: (children: React.ReactNode) => (
<FormRadioGroupContext.Provider value={providerValue}>
{children}
</FormRadioGroupContext.Provider>
)
} as typeof htmlProps,
htmlProps
);
htmlProps = unstable_useProps("FormRadioGroup", options, htmlProps);
htmlProps = unstable_useFormGroup(options, htmlProps);
return htmlProps;
Expand All @@ -50,21 +65,7 @@ unstable_useFormRadioGroup.__keys = keys;

export const unstable_FormRadioGroup = (unstable_createComponent({
as: "fieldset",
useHook: unstable_useFormRadioGroup,
useCreateElement: (type, props, children) => {
const element = unstable_useCreateElement(type, props, children);
const rover = useRoverState({ loop: true });
const value = React.useMemo(() => rover, [
rover.stops,
rover.currentId,
rover.unstable_pastId
]);
return (
<FormRadioGroupContext.Provider value={value}>
{element}
</FormRadioGroupContext.Provider>
);
}
useHook: unstable_useFormRadioGroup
}) as unknown) as <V, P extends DeepPath<V, P>, T extends As = "fieldset">(
props: PropsWithAs<unstable_FormRadioGroupOptions<V, P>, T>
) => JSX.Element;
7 changes: 5 additions & 2 deletions packages/reakit/src/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import {
import { useMenuState, MenuStateReturn } from "./MenuState";
import { MenuContext } from "./__utils/MenuContext";

export type MenuOptions = Omit<PopoverOptions, "modal" | "hideOnEsc"> &
export type MenuOptions = Omit<
PopoverOptions,
"modal" | "unstable_portal" | "hideOnEsc"
> &
StaticMenuOptions;

export type MenuProps = PopoverProps & StaticMenuProps;
Expand Down Expand Up @@ -52,7 +55,7 @@ export function useMenu(options: MenuOptions, htmlProps: MenuProps = {}) {
htmlProps = unstable_useProps("Menu", _options, htmlProps);
htmlProps = useStaticMenu(_options, htmlProps);
htmlProps = usePopover(
{ ..._options, modal: false, hideOnEsc: false },
{ ..._options, modal: false, unstable_portal: false, hideOnEsc: false },
htmlProps
);
return htmlProps;
Expand Down
26 changes: 3 additions & 23 deletions packages/reakit/src/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,12 @@ export type MenuItemProps = RoverProps;

export function useMenuItem(
options: MenuItemOptions,
{ children, onKeyDown, ...htmlProps }: MenuItemProps = {}
{ onKeyDown, ...htmlProps }: MenuItemProps = {}
) {
const parent = React.useContext(MenuContext);
const { parent } = React.useContext(MenuContext) || ({} as MenuContextType);
const ref = React.useRef<HTMLElement>(null);
options = unstable_useOptions("MenuItem", options, htmlProps);

const providerValue = React.useMemo(
() => ({
orientation: options.orientation,
next: options.next,
previous: options.previous,
parent
}),
[options.orientation, options.next, options.previous, parent]
);

htmlProps = mergeProps(
{
ref,
Expand Down Expand Up @@ -100,17 +90,7 @@ export function useMenuItem(
htmlProps = unstable_useProps("MenuItem", options, htmlProps);
htmlProps = useRover(options, htmlProps);

return {
...htmlProps,
children:
typeof children === "function"
? (props: typeof htmlProps) => (
<MenuContext.Provider value={providerValue}>
{children(props)}
</MenuContext.Provider>
)
: children
};
return htmlProps;
}

const keys: Keys<MenuStateReturn & MenuItemOptions> = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,33 @@ import { unstable_useProps } from "../system/useProps";
import { BoxOptions, BoxProps, useBox } from "../Box/Box";
import { Keys } from "../__utils/types";
import { useShortcuts } from "./__utils/useShortcuts";
import { MenuContext } from "./__utils/MenuContext";
import { MenuStateReturn, useMenuState } from "./MenuState";

export type StaticMenuOptions = BoxOptions &
Pick<Partial<MenuStateReturn>, "orientation"> &
Pick<MenuStateReturn, "stops" | "move">;
Pick<MenuStateReturn, "stops" | "move" | "next" | "previous">;

export type StaticMenuProps = BoxProps;

export function useStaticMenu(
options: StaticMenuOptions,
htmlProps: StaticMenuProps = {}
) {
const parent = React.useContext(MenuContext);
const ref = React.useRef<HTMLElement>(null);
options = unstable_useOptions("StaticMenu", options, htmlProps);

const providerValue = React.useMemo(
() => ({
orientation: options.orientation,
next: options.next,
previous: options.previous,
parent
}),
[options.orientation, options.next, options.previous, parent]
);

const onKeyDown = useShortcuts(options);

htmlProps = mergeProps(
Expand All @@ -37,7 +49,12 @@ export function useStaticMenu(
target.focus();
}
},
onKeyDown
onKeyDown,
unstable_wrap: (children: React.ReactNode) => (
<MenuContext.Provider value={providerValue}>
{children}
</MenuContext.Provider>
)
} as typeof htmlProps,
htmlProps
);
Expand Down
2 changes: 2 additions & 0 deletions packages/reakit/src/Menu/__tests__/Menu-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const props: Parameters<typeof Menu>[0] = {
unstable_hiddenId: "hidden",
stops: [{ id: "a", ref: createRef("a") }, { id: "b", ref: createRef("b") }],
move: jest.fn(),
next: jest.fn(),
previous: jest.fn(),
"aria-label": "menu"
};

Expand Down
2 changes: 2 additions & 0 deletions packages/reakit/src/Menu/__tests__/StaticMenu-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { StaticMenu } from "../StaticMenu";
const props: Parameters<typeof StaticMenu>[0] = {
stops: [],
move: jest.fn(),
next: jest.fn(),
previous: jest.fn(),
"aria-label": "menu"
};

Expand Down
8 changes: 2 additions & 6 deletions packages/reakit/src/Menu/__tests__/index-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1077,12 +1077,8 @@ test("arrow right/left in a sub-submenu moves focus between disclosures in menub
});
return (
<StaticMenu aria-label="menu" {...menu}>
<MenuItem {...menu}>
{props => <Submenu {...props} index={1} />}
</MenuItem>
<MenuItem {...menu}>
{props => <Submenu {...props} index={2} />}
</MenuItem>
<MenuItem {...menu} as={Submenu} index={1} />
<MenuItem {...menu} as={Submenu} index={2} />
</StaticMenu>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import * as React from "react";
import { warning } from "../__utils/warning";
import { mergeProps } from "../utils/mergeProps";
import { unstable_createComponent } from "../utils/createComponent";
import { unstable_useCreateElement } from "../utils/useCreateElement";
import { unstable_useOptions } from "../system/useOptions";
import { unstable_useProps } from "../system/useProps";
import { Portal } from "../Portal/Portal";
import { DialogOptions, DialogProps, useDialog } from "../Dialog/Dialog";
import { Keys } from "../__utils/types";
import { PopoverStateReturn, usePopoverState } from "./PopoverState";
Expand Down Expand Up @@ -59,11 +57,6 @@ See https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_roles_states_props`,
"Popover"
);

const element = unstable_useCreateElement(type, props, children);

if (props["aria-modal"]) {
return <Portal>{element}</Portal>;
}
return element;
return unstable_useCreateElement(type, props, children);
}
});
10 changes: 3 additions & 7 deletions packages/reakit/src/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from "react";
import { mergeProps } from "../utils/mergeProps";
import { unstable_createComponent } from "../utils/createComponent";
import { unstable_useCreateElement } from "../utils/useCreateElement";
import { unstable_useOptions } from "../system/useOptions";
import { unstable_useProps } from "../system/useProps";
import { Portal } from "../Portal/Portal";
Expand Down Expand Up @@ -29,7 +28,8 @@ export function useTooltip(
style: {
...options.unstable_popoverStyles,
pointerEvents: "none"
}
},
unstable_wrap: children => <Portal>{children}</Portal>
} as typeof htmlProps,
htmlProps
);
Expand All @@ -47,9 +47,5 @@ useTooltip.__keys = keys;

export const Tooltip = unstable_createComponent({
as: "div",
useHook: useTooltip,
useCreateElement: (type, props, children) => {
const element = unstable_useCreateElement(type, props, children);
return <Portal>{element}</Portal>;
}
useHook: useTooltip
});

0 comments on commit f668ae4

Please sign in to comment.