Skip to content
Snippets Groups Projects
Unverified Commit 9ecd3680 authored by Anton Kulyk's avatar Anton Kulyk Committed by GitHub
Browse files

Refactor CollapseSection (#17754)

parent 9a9a987d
No related branches found
No related tags found
No related merge requests found
......@@ -19,6 +19,20 @@ export const examples = {
foo foo foo foo foo
</CollapseSection>
),
"Up-down icon variant": (
<CollapseSection
initialState="collapsed"
header="Foo"
iconVariant="up-down"
>
foo foo foo foo foo
</CollapseSection>
),
"Icon on the right": (
<CollapseSection initialState="collapsed" header="Foo" iconPosition="right">
foo foo foo foo foo
</CollapseSection>
),
"Components in header": (
<CollapseSection
header={
......
/* eslint "react/prop-types": 2 */
import React, { useState } from "react";
import React, { useCallback, useState } from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import Icon from "metabase/components/Icon";
import { HeaderContainer, Header, ToggleIcon } from "./CollapseSection.styled";
const propTypes = {
children: PropTypes.node,
className: PropTypes.string,
header: PropTypes.node,
headerClass: PropTypes.string,
bodyClass: PropTypes.string,
initialState: PropTypes.oneOf(["expanded", "collapsed"]),
iconVariant: PropTypes.oneOf(["right-down", "up-down"]),
iconPosition: PropTypes.oneOf(["left", "right"]),
};
function CollapseSection({
children,
className,
initialState = "collapsed",
iconVariant = "right-down",
iconPosition = "left",
header,
headerClass,
className,
bodyClass,
initialState = "collapsed",
children,
}) {
const [isExpanded, setIsExpanded] = useState(initialState === "expanded");
const toggle = useCallback(() => {
setIsExpanded(isExpanded => !isExpanded);
}, []);
const onKeyDown = useCallback(
e => {
if (e.key === "Enter") {
toggle();
}
},
[toggle],
);
const HeaderIcon = (
<ToggleIcon
isExpanded={isExpanded}
variant={iconVariant}
position={iconPosition}
/>
);
return (
<div
className={cx(
"collapse-section",
isExpanded && "collapse-section--expanded",
className,
)}
role="tab"
aria-expanded={isExpanded}
>
<div
role="button"
tabIndex="0"
className={cx(
"collapse-section__header cursor-pointer flex align-center",
headerClass,
)}
onClick={() => setIsExpanded(isExpanded => !isExpanded)}
onKeyDown={e =>
e.key === "Enter" && setIsExpanded(isExpanded => !isExpanded)
}
<div className={className} role="tab" aria-expanded={isExpanded}>
<HeaderContainer
className={headerClass}
onClick={toggle}
onKeyDown={onKeyDown}
>
<Icon
className="mr1"
name={isExpanded ? "chevrondown" : "chevronright"}
size={12}
/>
<span className="collapse-section__header-text flex align-center">
{header}
</span>
</div>
<div role="tabpanel" className="collapse-section__body-container">
{isExpanded && (
<div className={cx("collapse-section__body-container", bodyClass)}>
{children}
</div>
)}
{iconPosition === "left" && HeaderIcon}
<Header>{header}</Header>
{iconPosition === "right" && HeaderIcon}
</HeaderContainer>
<div role="tabpanel">
{isExpanded && <div className={bodyClass}>{children}</div>}
</div>
</div>
);
}
CollapseSection.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
header: PropTypes.node,
headerClass: PropTypes.string,
bodyClass: PropTypes.string,
initialState: PropTypes.oneOf(["expanded", "collapsed"]),
};
CollapseSection.propTypes = propTypes;
export default CollapseSection;
import styled, { css } from "styled-components";
import Icon from "metabase/components/Icon";
export const HeaderContainer = styled.div.attrs({
role: "button",
tabIndex: "0",
})`
display: flex;
align-items: center;
cursor: pointer;
`;
export const Header = styled.span`
display: flex;
align-items: center;
`;
const ICON_VARIANTS = {
"right-down": {
collapsed: "chevronright",
expanded: "chevrondown",
},
"up-down": {
collapsed: "chevrondown",
expanded: "chevronup",
},
};
export const ToggleIcon = styled(Icon).attrs({
name: ({ isExpanded, variant }) => {
const { collapsed, expanded } = ICON_VARIANTS[variant];
return isExpanded ? expanded : collapsed;
},
size: 12,
})`
${props => css`
margin-${props.position === "left" ? "right" : "left"}: 0.5rem;
`};
`;
/* eslint-disable react/display-name */
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import CollapseSection from "./CollapseSection";
function setup({
header = "Collapse Header",
initialState = "collapsed",
...props
} = {}) {
return render(
<CollapseSection header={header} initialState={initialState} {...props}>
<p>Inside Text</p>
</CollapseSection>,
);
}
describe("CollapseSection", () => {
it("hides content", () => {
setup({ initialState: "collapsed" });
expect(screen.queryByText("Inside Text")).not.toBeInTheDocument();
});
it("can render in expanded state", () => {
setup({ initialState: "expanded" });
expect(screen.queryByText("Inside Text")).toBeVisible();
});
it("shows header", () => {
setup({ header: "Header" });
expect(screen.queryByText("Header")).toBeInTheDocument();
expect(screen.queryByLabelText("chevronright icon")).toBeInTheDocument();
});
it("expands content when header is clicked", () => {
setup();
fireEvent.click(screen.getByText("Collapse Header"));
expect(screen.queryByText("Inside Text")).toBeInTheDocument();
expect(screen.queryByText("Inside Text")).toBeVisible();
});
it("expands content when icon is clicked", () => {
setup();
fireEvent.click(screen.getByLabelText("chevronright icon"));
expect(screen.queryByText("Inside Text")).toBeInTheDocument();
expect(screen.queryByText("Inside Text")).toBeVisible();
});
it("collapses content when header is clicked", () => {
setup({ initialState: "expanded" });
fireEvent.click(screen.getByText("Collapse Header"));
expect(screen.queryByText("Inside Text")).not.toBeInTheDocument();
});
it("collapses content when icon is clicked", () => {
setup({ initialState: "expanded" });
fireEvent.click(screen.getByLabelText("chevrondown icon"));
expect(screen.queryByText("Inside Text")).not.toBeInTheDocument();
});
it("renders custom header", () => {
setup({ header: <h1>Custom Header</h1> });
expect(screen.queryByText("Custom Header")).toBeInTheDocument();
});
it("uses different icons for 'up-down' icon variant", () => {
setup({ iconVariant: "up-down" });
expect(screen.queryByLabelText("chevrondown icon")).toBeInTheDocument();
fireEvent.click(screen.queryByLabelText("chevrondown icon"));
expect(screen.queryByLabelText("chevronup icon")).toBeInTheDocument();
});
});
export { default } from "./CollapseSection";
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment