From f7bb82dd8ca195fe35aeb2816742714791730b39 Mon Sep 17 00:00:00 2001
From: Dalton <daltojohnso@users.noreply.github.com>
Date: Mon, 29 Mar 2021 11:14:39 -0700
Subject: [PATCH] Add CollapseSection UI component (#15358)

* Add CollapseSection component

* Replace header string prop with header node prop

* update nounage open/closed --> expanded/collapsed

* add className prop
---
 .../components/CollapseSection.info.js        | 63 +++++++++++++++++
 .../metabase/components/CollapseSection.jsx   | 70 +++++++++++++++++++
 2 files changed, 133 insertions(+)
 create mode 100644 frontend/src/metabase/components/CollapseSection.info.js
 create mode 100644 frontend/src/metabase/components/CollapseSection.jsx

diff --git a/frontend/src/metabase/components/CollapseSection.info.js b/frontend/src/metabase/components/CollapseSection.info.js
new file mode 100644
index 00000000000..e44df34673c
--- /dev/null
+++ b/frontend/src/metabase/components/CollapseSection.info.js
@@ -0,0 +1,63 @@
+import React from "react";
+import CollapseSection from "metabase/components/CollapseSection";
+import Icon from "metabase/components/Icon";
+
+export const component = CollapseSection;
+export const category = "layout";
+export const description = `
+A collapsible section with a clickable header.
+`;
+
+export const examples = {
+  "Collapsed by default": (
+    <CollapseSection header="Section header">
+      foo foo foo foo foo foo foo foo
+    </CollapseSection>
+  ),
+  "Settable collpased/expanded initial state": (
+    <CollapseSection initialState="expanded" header="Foo">
+      foo foo foo foo foo
+    </CollapseSection>
+  ),
+  "Components in header": (
+    <CollapseSection
+      header={
+        <div>
+          <Icon className="mr1" name="folder" size={12} />
+          Component header
+        </div>
+      }
+    >
+      foo foo foo foo foo
+    </CollapseSection>
+  ),
+  "Header and body classes": (
+    <CollapseSection
+      initialState="expanded"
+      header="Section header"
+      headerClass="text-brand flex-reverse justify-between p1 border-bottom"
+      bodyClass="p2"
+    >
+      <div>
+        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus neque
+        tellus, mattis ut felis non, tempus mollis lacus. Vivamus nulla massa,
+        accumsan non ligula eu, dapibus volutpat libero. Mauris sollicitudin
+        dolor et ipsum fringilla auctor. Praesent et diam non nisi consequat
+        ornare. Aenean et risus vel dolor maximus dapibus a id massa. Nam
+        finibus quis libero eu finibus. Sed vehicula ac enim pellentesque
+        luctus. Phasellus vehicula et ipsum porttitor mollis. Fusce blandit
+        lacus a elit pretium, vestibulum porta nisi vehicula. Aliquam vel ligula
+        enim. Orci varius natoque penatibus et magnis dis parturient montes,
+        nascetur ridiculus mus. Pellentesque eget porta mi. Duis et lectus eget
+        dolor convallis mollis. Sed commodo nec urna eget egestas.
+        <br />
+        <br />
+        Mauris in ante sit amet ipsum tempus consequat. Curabitur auctor massa
+        vitae dui auctor scelerisque. Donec in leo a libero commodo sodales.
+        Integer egestas lacinia elit, vitae cursus sem mollis ut. Proin ut
+        dapibus metus, vel accumsan justo. Pellentesque eget finibus elit, ut
+        commodo felis. Ut non lacinia metus. Maecenas eget bibendum nisl.
+      </div>
+    </CollapseSection>
+  ),
+};
diff --git a/frontend/src/metabase/components/CollapseSection.jsx b/frontend/src/metabase/components/CollapseSection.jsx
new file mode 100644
index 00000000000..f5cafd214ad
--- /dev/null
+++ b/frontend/src/metabase/components/CollapseSection.jsx
@@ -0,0 +1,70 @@
+/* eslint "react/prop-types": 2 */
+
+import React, { useState } from "react";
+import PropTypes from "prop-types";
+import cx from "classnames";
+
+import Icon from "metabase/components/Icon";
+
+function CollapseSection({
+  children,
+  className,
+  header,
+  headerClass,
+  bodyClass,
+  initialState = "collapsed",
+}) {
+  const [isExpanded, setIsExpanded] = useState(initialState === "expanded");
+
+  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)
+        }
+      >
+        <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>
+        )}
+      </div>
+    </div>
+  );
+}
+
+CollapseSection.propTypes = {
+  children: PropTypes.node,
+  className: PropTypes.string,
+  header: PropTypes.node,
+  headerClass: PropTypes.string,
+  bodyClass: PropTypes.string,
+  initialState: PropTypes.oneOf(["expanded", "collapsed"]),
+};
+
+export default CollapseSection;
-- 
GitLab