Skip to content

Commit

Permalink
feat(drawer): basic drawer component
Browse files Browse the repository at this point in the history
Add basic drawer component with supporting component `DrawerItems`
  • Loading branch information
biswarup35 committed Jun 12, 2022
1 parent dc5b58a commit e74ce8a
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/material-you/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from "./src/Backdrop";
export * from "./src/List";
export * from "./src/Card";
export { Menu } from "./src/Menu";
export { Drawer } from "./src/Drawer";
30 changes: 30 additions & 0 deletions packages/material-you/src/Drawer/Drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from "react";
import type { DrawerOwnProps } from "./types";
import type { PolymorphicWithRef } from "../types";
import DrawerBase from "./DrawerBase";
import { DrawerItems } from "../DrawerItems";

type DrawerProps<T extends React.ElementType> = PolymorphicWithRef<
T,
DrawerOwnProps
>;

type DrawerElement = <E extends React.ElementType = "nav">(
props: DrawerProps<E>
) => React.ReactElement<DrawerProps<E>>;

const Drawer: DrawerElement = React.forwardRef(
<T extends React.ElementType>(
props: DrawerProps<T>,
innerRef: typeof props.ref
) => {
const { component = "nav", ...rest } = props;
return (
<DrawerBase as={component as typeof component} ref={innerRef} {...rest} />
);
}
);

export default Object.assign(Drawer, {
Items: DrawerItems,
});
29 changes: 29 additions & 0 deletions packages/material-you/src/Drawer/DrawerBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { DrawerOwnProps } from "./types";
import styled, { CSSObject } from "@emotion/styled";
import { useTheme } from "../theme";
import { useMemo } from "react";

const DrawerBase = styled("nav", {
label: "Drawer",
})<Partial<DrawerOwnProps>>(() => {
const { theme, type } = useTheme();

const _background = useMemo(() => new Map<typeof type, CSSObject>(), [type])
.set("light", {
background: theme.colorStyles.sys.light.surface,
})
.set("dark", {
background: theme.colorStyles.sys.dark.surface,
})
.get(type) as CSSObject;
return Object.assign(
{
width: "100%",
height: "100%",
maxWidth: theme.pxToRem(360),
},
_background
);
});

export default DrawerBase;
1 change: 1 addition & 0 deletions packages/material-you/src/Drawer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Drawer } from "./Drawer";
3 changes: 3 additions & 0 deletions packages/material-you/src/Drawer/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type DrawerOwnProps = {
variant?: "standard" | "modal";
};
82 changes: 82 additions & 0 deletions packages/material-you/src/DrawerItem/BaseDrawerItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type { DrawerItemOwnProps } from "./types";
import styled, { CSSObject } from "@emotion/styled";
import { useMemo } from "react";
import { useTheme } from "../theme";

const BaseDrawerItem = styled("li", {
label: "DrawerItem",
})<Partial<DrawerItemOwnProps>>(({ dense, active = false }) => {
const { theme, type } = useTheme();
const _dense = useMemo(() => new Map<typeof dense, CSSObject>(), []);
_dense.set(true, {
minHeight: theme.pxToRem(48),
});
const _active = useMemo(() => new Map<typeof active, CSSObject>(), []);
_active.set(true, {
backgroundColor: new Map<typeof type, string>()
.set("light", theme.colorStyles.sys.light.secondaryContainer)
.set("dark", theme.colorStyles.sys.dark.secondaryContainer)
.get(type),
});
const _rippleBG = useMemo(() => new Map<typeof type, string>(), []);
_rippleBG
.set("dark", theme.colorStyles.readOnly.dark.onSecondaryContainer.opacity12)
.set(
"light",
theme.colorStyles.readOnly.light.onSecondaryContainer.opacity12
);
const _hover = useMemo(() => new Map<typeof type, CSSObject>(), []);
_hover
.set("light", {
[`&: after`]: {
background:
theme.colorStyles.readOnly.light.onSecondaryContainer.opacity08,
},
})
.set("dark", {
[`&: after`]: {
backgroundColor:
theme.colorStyles.readOnly.dark.onSecondaryContainer.opacity08,
},
});

return Object.assign(
{
display: "flex",
alignItems: "center",
minHeight: theme.pxToRem(56),
paddingInline: theme.pxToRem(28),
position: "relative",
overflow: "hidden",
boxSizing: "content-box",
borderRadius: theme.pxToRem(28),

[`&: hover`]: {
..._hover.get(type),
backgroundClip: "border-box",
},
[`> [data-label="drawer"]`]: {
flexGrow: 1,
paddingRight: theme.pxToRem(12),
},
[`> [data-icon="drawer"]`]: {
paddingRight: theme.pxToRem(12),
},
[`> [data-ripple]`]: {
background: _rippleBG.get(type),
},
[`&: after`]: {
content: "''",
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
},
},
_dense.get(dense),
_active.get(active)
);
});

export default BaseDrawerItem;
46 changes: 46 additions & 0 deletions packages/material-you/src/DrawerItem/DrawerItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from "react";
import type { DrawerItemOwnProps } from "./types";
import type { PolymorphicWithRef } from "../types";
import BaseDrawerItem from "./BaseDrawerItem";
import { LabelTypography } from "../LabelTypography";
import { Icon } from "../Icon";
import { useEventListener } from "../hooks";
import rippleEffect from "../utils/events/rippleEffect";

type LabelProps = React.ComponentProps<typeof LabelTypography> & {
labelText?: string;
};

type DrawerItemProps<T extends React.ElementType = "li"> = PolymorphicWithRef<
T,
DrawerItemOwnProps
>;
type DrawerItemElement = <T extends React.ElementType = "li">(
props: DrawerItemProps<T>
) => React.ReactElement<DrawerItemProps<T>>;

const DrawerItem: DrawerItemElement = React.forwardRef(
<T extends React.ElementType = "li">(
props: DrawerItemProps<T>,
innerRef: typeof props.ref
) => {
const { component = "li", ...rest } = props;
const itemRef = React.useRef(null);
// forwarding the ref using imperative API
React.useImperativeHandle(innerRef, () => itemRef.current);

// useEventListener to add ripple effect
useEventListener(
"mousedown",
(event) => rippleEffect(event, itemRef),
itemRef
);
return <BaseDrawerItem as={component} ref={itemRef} {...rest} />;
}
);

export default Object.assign(DrawerItem, {
Label: (props: LabelProps) =>
React.cloneElement(<LabelTypography />, props, [props.labelText]),
Icon: Icon,
});
1 change: 1 addition & 0 deletions packages/material-you/src/DrawerItem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as DrawerItem } from "./DrawerItem";
4 changes: 4 additions & 0 deletions packages/material-you/src/DrawerItem/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type DrawerItemOwnProps = {
dense?: true;
active?: boolean;
};
9 changes: 9 additions & 0 deletions packages/material-you/src/DrawerItems/BaseDrawerItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import styled from "@emotion/styled";

const BaseDrawerItems = styled("ul", {
label: "DrawerItems",
})(() => {
return {};
});

export default BaseDrawerItems;
49 changes: 49 additions & 0 deletions packages/material-you/src/DrawerItems/DrawerItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as React from "react";
import type { PolymorphicWithRef } from "../types";
import BaseDrawerItems from "./BaseDrawerItems";
import { DrawerItem } from "../DrawerItem";

type Props = {
label: React.ComponentProps<typeof DrawerItem.Label> & {
[`data-label`]?: string;
};
icon: React.ComponentProps<typeof DrawerItem.Icon> & {
[`data-icon`]?: string;
};
};
type DrawerItemsProps<T extends React.ElementType> = PolymorphicWithRef<
T,
{
children: (Item: typeof DrawerItem, props: Props) => React.ReactNode;
}
>;
type DrawerItemsElement = <E extends React.ElementType = "ul">(
props: DrawerItemsProps<E>
) => React.ReactElement<DrawerItemsProps<E>>;

const DrawerItems: DrawerItemsElement = React.forwardRef(
<T extends React.ElementType>(
props: DrawerItemsProps<T>,
innerRef: typeof props.ref
) => {
const { component = "ul", children, ...rest } = props;
return (
<BaseDrawerItems
as={component as typeof component}
ref={innerRef}
{...rest}
>
{children(DrawerItem, {
label: {
component: "span",
size: "large",
["data-label"]: "drawer",
},
icon: { ["data-icon"]: "drawer" },
})}
</BaseDrawerItems>
);
}
);

export default DrawerItems;
1 change: 1 addition & 0 deletions packages/material-you/src/DrawerItems/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as DrawerItems } from "./DrawerItems";

0 comments on commit e74ce8a

Please sign in to comment.