diff --git a/jest.config.mjs b/jest.config.mjs index 25552207926f..3540d474c0f3 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -3,11 +3,12 @@ export default { testEnvironment: "node", setupFiles: ["./src/setupTests.js"], transform: { - "^.+\\.jsx?$": "babel-jest", + "^.+\\.(m|c)?jsx?$": "babel-jest", }, moduleNameMapper: { "\\.(scss|css)$": "/src/components/__mocks__/styleMock.js", "\\.svg$": "/src/components/__mocks__/svgMock.js", + "\\.(png|jpg|jpeg|ico)$": "/src/components/__mocks__/fileMock.js", }, moduleFileExtensions: [ "js", diff --git a/src/components/Configuration/components.jsx b/src/components/Configuration/components.jsx index e395168b3b1a..db2abb0fb188 100644 --- a/src/components/Configuration/components.jsx +++ b/src/components/Configuration/components.jsx @@ -1,5 +1,5 @@ import PropTypes from "prop-types"; -import { Component, isValidElement } from "react"; +import { isValidElement, useState } from "react"; import Popover from "react-tiny-popover"; const DEFAULT_CHILDREN_SIZE = 4; @@ -20,8 +20,8 @@ const addLink = (child, i, url) => const Card = ({ body }) => ( // Applied .shadow > .markdown styles: max-height and overflow
- {/* Combined .inline and .shadow pre.inline styles: - display: block, margin: 0, padding: 0 with right-padding override + {/* Combined .inline and .shadow pre.inline styles: + display: block, margin: 0, padding: 0 with right-padding override */}
       {body}
@@ -33,71 +33,53 @@ Card.propTypes = {
   body: PropTypes.node,
 };
 
-export class Details extends Component {
-  static propTypes = {
-    url: PropTypes.string,
-    myChilds: PropTypes.arrayOf(PropTypes.node),
-  };
-
-  constructor(props) {
-    super(props);
-    this.state = {
-      open: false,
-    };
-  }
-
-  clickOutsideHandler = () => {
-    this.setState({ open: false });
-  };
-
-  toggleVisibility = () => {
-    this.setState({ open: !this.state.open });
-  };
-
-  render() {
-    const { myChilds, url } = this.props;
-
-    // Find the index of 
-    const closeDefaultTagIndex = myChilds.findIndex((child) => {
-      if (isValidElement(child)) {
-        return (
-          child.props.className.includes("tag") &&
-          child.props.children.length === DEFAULT_CHILDREN_SIZE
-        );
-      }
-
-      return false;
-    });
-
-    const content = [...myChilds];
-
-    // Summary is the part of the snippet that would be shown in the code snippet,
-    // to get it we need to cut the  enclosing tags
-    const summary = content
-      .splice(2, closeDefaultTagIndex - 3)
-      .map(removeSpaces)
-      .map((child, i) => addLink(child, i, url));
-
-    content.splice(0, DEFAULT_CHILDREN_SIZE); // Remove  information
-
-    const { open } = this.state;
-    return (
-      }
+export function Details({ url, myChilds }) {
+  const [open, setOpen] = useState(false);
+
+  // Find the index of 
+  const closeDefaultTagIndex = myChilds.findIndex((child) => {
+    if (isValidElement(child)) {
+      return (
+        child.props.className.includes("tag") &&
+        child.props.children.length === DEFAULT_CHILDREN_SIZE
+      );
+    }
+
+    return false;
+  });
+
+  const content = [...myChilds];
+
+  // Summary is the part of the snippet that would be shown in the code snippet,
+  // to get it we need to cut the  enclosing tags
+  const summary = content
+    .splice(2, closeDefaultTagIndex - 3)
+    .map(removeSpaces)
+    .map((child, i) => addLink(child, i, url));
+
+  content.splice(0, DEFAULT_CHILDREN_SIZE); // Remove  information
+
+  return (
+     setOpen(false)}
+      // Replaced .shadow with Tailwind equivalents, including the custom rgba box-shadow
+      containerClassName="overflow-visible rounded shadow-[-1px_1px_10px_0_rgba(255,255,255,0.44)]"
+      content={}
+    >
+       setOpen((prev) => !prev)}
       >
-        
-          {summary}
-        
-      
-    );
-  }
+        {summary}
+      
+    
+  );
 }
+
+Details.propTypes = {
+  url: PropTypes.string,
+  myChilds: PropTypes.arrayOf(PropTypes.node),
+};
diff --git a/src/components/Cube/Cube.jsx b/src/components/Cube/Cube.jsx
index a7c323b0f78e..b2481cb91420 100644
--- a/src/components/Cube/Cube.jsx
+++ b/src/components/Cube/Cube.jsx
@@ -1,92 +1,43 @@
 // Import External Dependencies
 import PropTypes from "prop-types";
-import { Component } from "react";
-
-export default class Cube extends Component {
-  static propTypes = {
-    hover: PropTypes.bool,
-    theme: PropTypes.string,
-    depth: PropTypes.number,
-    repeatDelay: PropTypes.number,
-    className: PropTypes.string,
-    continuous: PropTypes.bool,
-  };
-
-  static defaultProps = {
-    hover: false,
-    theme: "dark",
-    depth: 30,
-    repeatDelay: 1000,
-  };
+import { useEffect, useRef, useState } from "react";
 
-  state = {
-    x: 0,
-    y: 0,
-    z: 0,
-    iteration: 0,
-  };
+export default function Cube({
+  hover = false,
+  theme = "dark",
+  depth = 30,
+  repeatDelay = 1000,
+  className = "",
+  continuous,
+}) {
+  const [state, setState] = useState({ x: 0, y: 0, z: 0, iteration: 0 });
+  const containerRef = useRef(null);
+  const timeoutRef = useRef(null);
 
-  render() {
-    const { x, y, z } = this.state;
-    const { theme, depth, className = "" } = this.props;
+  const { x, y, z, iteration } = state;
 
-    return (
-      
- (this.container = ref)} - className={`cube cube--${theme} relative block [transform-style:preserve-3d]`} - style={{ - width: `${depth}px`, - paddingBottom: `${depth * 0.5}px`, - transform: "rotateX(-35.5deg) rotateY(45deg)", - }} - > -
- {this._getFaces("outer")} -
-
- {this._getFaces("inner")} -
-
-
- ); - } - - componentDidMount() { - const { hover, continuous, repeatDelay } = this.props; + useEffect(() => { + const container = containerRef.current; if (hover) { - this.container.addEventListener("mouseenter", this._spin); - this.container.addEventListener("mouseleave", this._reset); + const spin = () => { + const axes = ["x", "y", "z", "iteration"]; + const axis = axes[Math.floor(Math.random() * axes.length)]; + const sign = Math.random() < 0.5 ? -1 : 1; + setState((prev) => ({ ...prev, [axis]: sign * 90 })); + }; + + const reset = () => { + setState((prev) => ({ ...prev, x: 0, y: 0, z: 0 })); + }; + + container.addEventListener("mouseenter", spin); + container.addEventListener("mouseleave", reset); + + return () => { + container.removeEventListener("mouseenter", spin); + container.removeEventListener("mouseleave", reset); + }; } else if (continuous) { let degrees = 0; const axis = "y"; @@ -94,10 +45,11 @@ export default class Cube extends Component { const animation = () => { const obj = {}; obj[axis] = degrees += 90; - this.setState({ + setState((prev) => ({ + ...prev, ...obj, - iteration: (this.state.iteration + 1) % 4, - }); + iteration: (prev.iteration + 1) % 4, + })); // eslint-disable-next-line no-use-before-define tick(); }; @@ -105,30 +57,13 @@ export default class Cube extends Component { const tick = () => setTimeout(() => requestAnimationFrame(animation), repeatDelay); - this._timeout = tick(); - } - } - - componentWillUnmount() { - const { hover, continuous } = this.props; + timeoutRef.current = tick(); - if (hover) { - this.container.removeEventListener("mouseenter", this._spin); - this.container.removeEventListener("mouseleave", this._reset); - } else if (continuous) { - clearTimeout(this._timeout); + return () => clearTimeout(timeoutRef.current); } - } - - /** - * Get all faces for a cube - * - * @param {'inner' | 'outer' } type - * @return {array} - An array of nodes - */ - _getFaces(type) { - const { iteration } = this.state; + }, []); // eslint-disable-line react-hooks/exhaustive-deps + const getFaces = (type) => { // Keep the thicker border on // the outside on each iteration const borderWidthMap = { @@ -201,50 +136,70 @@ export default class Cube extends Component { key={i} className={`cube__face ${baseFaceClasses} ${variantClasses}`} style={{ - transform: `${rotation} translateZ(${this.props.depth / 2}px)`, + transform: `${rotation} translateZ(${depth / 2}px)`, ...borderStyles, }} /> ); }); - } - - /** - * Get a random axis - * - * @return {string} - A random axis (i.e. x, y, or z) - */ - _getRandomAxis() { - const axes = Object.keys(this.state); - - return axes[Math.floor(Math.random() * axes.length)]; - } - - /** - * Spin the cubes in opposite directions semi-randomly - * - * @param {object} e - Native event - */ - _spin = () => { - const obj = {}; - const axis = this._getRandomAxis(); - const sign = Math.random() < 0.5 ? -1 : 1; - - obj[axis] = sign * 90; - - this.setState(obj); }; - /** - * Rotate the cubes back to their original position - * - * @param {object} e - Native event - */ - _reset = () => { - this.setState({ - x: 0, - y: 0, - z: 0, - }); - }; + return ( +
+ +
+ {getFaces("outer")} +
+
+ {getFaces("inner")} +
+
+
+ ); } + +Cube.propTypes = { + hover: PropTypes.bool, + theme: PropTypes.string, + depth: PropTypes.number, + repeatDelay: PropTypes.number, + className: PropTypes.string, + continuous: PropTypes.bool, +}; diff --git a/src/components/Dropdown/Dropdown.jsx b/src/components/Dropdown/Dropdown.jsx index e838402810c6..600e7388e5fb 100644 --- a/src/components/Dropdown/Dropdown.jsx +++ b/src/components/Dropdown/Dropdown.jsx @@ -1,127 +1,53 @@ import PropTypes from "prop-types"; -import { Component } from "react"; +import { useEffect, useRef, useState } from "react"; -export default class Dropdown extends Component { - static propTypes = { - className: PropTypes.string, - items: PropTypes.array, - }; - - state = { - active: false, - }; +export default function Dropdown({ className = "", items = [] }) { + const [active, setActive] = useState(false); + const activeRef = useRef(false); + const dropdownRef = useRef(null); + const dropdownButtonRef = useRef(null); + const linksRef = useRef([]); + const openedByClickRef = useRef(false); - componentDidMount() { - document.addEventListener("keyup", this._closeDropdownOnEsc, true); - document.addEventListener("focus", this._closeDropdownIfFocusLost, true); - document.addEventListener("click", this._closeDropdownIfFocusLost, true); - } + // Keep activeRef in sync for use inside stable event listeners + useEffect(() => { + activeRef.current = active; + }, [active]); - componentWillUnmount() { - document.removeEventListener("keyup", this._closeDropdownOnEsc, true); - document.removeEventListener("focus", this._closeDropdownIfFocusLost, true); - document.removeEventListener("click", this._closeDropdownIfFocusLost, true); - } + useEffect(() => { + const handleEsc = (event) => { + if (event.key === "Escape" && activeRef.current) { + setActive(false); + dropdownButtonRef.current.focus(); + } + }; - _closeDropdownOnEsc = (event) => { - if (event.key === "Escape" && this.state.active) { - this.setState({ active: false }, () => { - this.dropdownButton.focus(); - }); - } - }; + const handleFocusLost = (event) => { + if (activeRef.current && !dropdownRef.current.contains(event.target)) { + setActive(false); + } + }; - _closeDropdownIfFocusLost = (event) => { - if (this.state.active && !this.dropdown.contains(event.target)) { - this.setState({ active: false }); - } - }; + document.addEventListener("keyup", handleEsc, true); + document.addEventListener("focus", handleFocusLost, true); + document.addEventListener("click", handleFocusLost, true); - render() { - const { className = "", items = [] } = this.props; + return () => { + document.removeEventListener("keyup", handleEsc, true); + document.removeEventListener("focus", handleFocusLost, true); + document.removeEventListener("click", handleFocusLost, true); + }; + }, []); - return ( -