Skip to content

Commit

Permalink
PD-12487 Make CollapsibleChecklists component
Browse files Browse the repository at this point in the history
  • Loading branch information
jamiek-acl committed Jul 15, 2019
1 parent 84228d6 commit 52f57fb
Show file tree
Hide file tree
Showing 14 changed files with 391 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/Collapsible/package.json
Expand Up @@ -7,7 +7,7 @@
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/acl-services/paprika/tree/master/packages/collapsible"
"url": "https://github.com/acl-services/paprika/tree/master/packages/Collapsible"
},
"publishConfig": {
"access": "public"
Expand Down
Empty file.
28 changes: 28 additions & 0 deletions packages/CollapsibleChecklists/package.json
@@ -0,0 +1,28 @@
{
"name": "@paprika/collapsible-list",
"version": "0.1.0",
"description": "CollapsibleList component",
"author": "@paprika",
"license": "MIT",
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/acl-services/paprika/tree/master/packages/CollapsibleList"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@babel/runtime-corejs2": "^7.3.1",
"@paprika/collapsible": "^0.1.0",
"@paprika/stylers": "^0.1.1",
"@paprika/tokens": "^0.1.1",
"class-names": "^1.0.0",
"prop-types": "^15.7.2"
},
"peerDependencies": {
"react": "^16.8.0",
"react-dom": "^16.8.0",
"styled-components": "^4.2.0"
}
}
37 changes: 37 additions & 0 deletions packages/CollapsibleChecklists/src/CollapsibleChecklists.js
@@ -0,0 +1,37 @@
import React from "react";
import PropTypes from "prop-types";
// import CollapsibleChecklistsStyles from "./CollapsibleChecklists.styles";
import Heading from "./components/Heading";
import Group from "./components/Group";
import Item from "./components/Item";

const propTypes = {
children: PropTypes.node,
};

const defaultProps = {};

const CollapsibleChecklists = props => {
const { children, onChange } = props;

return children.map((child, index) => {
switch (child.type.displayName) {
case Heading.displayName:
return <Heading key={`heading${index}`} {...child.props} />; // eslint-disable-line react/no-array-index-key
case Group.displayName:
return <Group key={`group${index}`} {...child.props} onChange={onChange} />; // eslint-disable-line react/no-array-index-key
default:
return child;
}
});
};

CollapsibleChecklists.displayName = "CollapsibleChecklists";
CollapsibleChecklists.propTypes = propTypes;
CollapsibleChecklists.defaultProps = defaultProps;

CollapsibleChecklists.Heading = Heading;
CollapsibleChecklists.Group = Group;
CollapsibleChecklists.Item = Item;

export default CollapsibleChecklists;
109 changes: 109 additions & 0 deletions packages/CollapsibleChecklists/src/components/Group/Group.js
@@ -0,0 +1,109 @@
import React from "react";
import PropTypes from "prop-types";
import Collapsible from "@paprika/collapsible";
import Item from "../Item";

const propTypes = {
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]), // are probably an array of "Items", but could be a Spinner or anything else
isDisabled: PropTypes.bool,
onChange: PropTypes.func,
onExpand: PropTypes.func,
title: PropTypes.node.isRequired,
};

const defaultProps = {
children: [],
isDisabled: false,
onChange: () => {},
onExpand: () => {},
};

function useIsIndeterminate(checkboxRef) {
const [, setIsIndeterminate] = React.useState(null);
React.useEffect(() => {
setIsIndeterminate(checkboxRef.current.indeterminate);
}, [checkboxRef.current.indeterminate]);
}

function Group(props) {
const { children, isDisabled, onChange, onExpand, title } = props;
const [isCollapsed, setIsCollapsed] = React.useState(true);
const checkboxRef = React.useRef({});
useIsIndeterminate(checkboxRef);
let allAreChecked = React.Children.count(props.children) > 0;
let noneAreChecked = true;

React.Children.forEach(children, child => {
if (child.props.isChecked) {
noneAreChecked = false;
} else {
allAreChecked = false;
}
});

if (allAreChecked || noneAreChecked) {
checkboxRef.current.indeterminate = false;
} else if (!allAreChecked && !noneAreChecked) {
checkboxRef.current.indeterminate = true;
}

function handleOnChange() {
const allChildrenAreChecked =
children.filter(child => child.props.isChecked || child.props.isDisabled).length === children.length;

let childrenToChange = [];
if (allChildrenAreChecked) {
childrenToChange = children.filter(child => !child.props.isDisabled);
} else {
childrenToChange = children.filter(child => !child.props.isChecked && !child.props.isDisabled);
}

onChange(childrenToChange);
}

const label = (
<React.Fragment>
<input
ref={checkboxRef}
checked={allAreChecked}
type="checkbox"
disabled={isDisabled}
onChange={handleOnChange}
/>
{title}
</React.Fragment>
);

const modifiedChildren = React.Children.map(children, child => {
const newProps =
child.type.displayName === Item.displayName
? {
onChange: () => onChange([child]),
}
: null;
return React.cloneElement(child, newProps);
});

return (
<Collapsible
a11yText="umm..."
hasOnlyIconToggle
isCollapsed={isCollapsed}
isDisabled={isDisabled}
label={label}
onClick={() => {
if (isCollapsed && onExpand) {
onExpand();
}
setIsCollapsed(!isCollapsed);
}}
>
{modifiedChildren}
</Collapsible>
);
}

Group.displayName = "Group";
Group.propTypes = propTypes;
Group.defaultProps = defaultProps;
export default Group;
@@ -0,0 +1 @@
export { default } from "./Group";
19 changes: 19 additions & 0 deletions packages/CollapsibleChecklists/src/components/Heading/Heading.js
@@ -0,0 +1,19 @@
import React from "react";
import PropTypes from "prop-types";
import headingStyles from "./Heading.styles";

const propTypes = {
children: PropTypes.node.isRequired,
};

const defaultProps = {};

const Heading = ({ children }) => {
return <div css={headingStyles}>{children}</div>;
};

Heading.displayName = "Heading";
Heading.propTypes = propTypes;
Heading.defaultProps = defaultProps;

export default Heading;
@@ -0,0 +1,9 @@
import tokens from "@paprika/tokens";
// import stylers from "@paprika/stylers";

const headingStyles = () => `
background-color: ${tokens.color.blackLighten70};
font-weight: bold;
`;

export default headingStyles;
@@ -0,0 +1 @@
export { default } from "./Heading";
31 changes: 31 additions & 0 deletions packages/CollapsibleChecklists/src/components/Item/Item.js
@@ -0,0 +1,31 @@
import React from "react";
import PropTypes from "prop-types";

const propTypes = {
children: PropTypes.node.isRequired,
isChecked: PropTypes.bool,
isDisabled: PropTypes.bool,
onChange: PropTypes.func,
};

const defaultProps = {
isChecked: false,
isDisabled: false,
onChange: () => {},
};

function Item(props) {
const { children, isChecked, isDisabled, onChange } = props;

return (
<div style={{ marginLeft: "16px" }}>
<input type="checkbox" checked={isChecked} disabled={isDisabled} onChange={onChange} /> {children}
</div>
);
}

Item.displayName = "Item";
Item.propTypes = propTypes;
Item.defaultProps = defaultProps;

export default Item;
@@ -0,0 +1 @@
export { default } from "./Item";
3 changes: 3 additions & 0 deletions packages/CollapsibleChecklists/src/index.js
@@ -0,0 +1,3 @@
import CollapsibleChecklists from "./CollapsibleChecklists";

export { CollapsibleChecklists as default };
@@ -0,0 +1,9 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import { withKnobs } from "@storybook/addon-knobs";

import BasicExample from "./examples/Basic";

storiesOf("CollapsibleChecklists", module)
.addDecorator(withKnobs)
.add("Basic", () => <BasicExample />);

0 comments on commit 52f57fb

Please sign in to comment.