From a077c75f70f0e030cb3b3c2adce0494428bb8f99 Mon Sep 17 00:00:00 2001
From: myxvisual <myxvisual@live.com>
Date: Tue, 26 Jun 2018 16:58:34 +0800
Subject: [PATCH] feat: Add base reveal effects

---
 .../components/DocsTreeView/docTreeData.tsx   |   3 +-
 docs/src/components/Wrapper/index.tsx         |   4 -
 .../WrapperWithCategories/index.tsx           |  23 ++-
 docs/src/routes/Styles/Reveal/index.tsx       |  58 +++++++
 docs/src/routes/index.tsx                     |   7 +
 src/Reveal/RevealContainer.tsx                | 160 ++++++++++++++++++
 src/Reveal/RevealItem.tsx                     |  78 +++++++++
 src/Reveal/activeEvent.ts                     |   0
 src/Reveal/addRevealItem.tsx                  |   9 +
 src/Reveal/checkIntersects.ts                 |  30 ++++
 src/Reveal/hoverEvent.ts                      |   0
 src/Theme/index.tsx                           |  21 +++
 src/__Mock__/index.tsx                        |  12 +-
 src/styles/getTheme.ts                        |   5 +-
 typings/index.d.ts                            |   1 +
 15 files changed, 399 insertions(+), 12 deletions(-)
 create mode 100644 docs/src/routes/Styles/Reveal/index.tsx
 create mode 100644 src/Reveal/RevealContainer.tsx
 create mode 100644 src/Reveal/RevealItem.tsx
 create mode 100644 src/Reveal/activeEvent.ts
 create mode 100644 src/Reveal/addRevealItem.tsx
 create mode 100644 src/Reveal/checkIntersects.ts
 create mode 100644 src/Reveal/hoverEvent.ts

diff --git a/docs/src/components/DocsTreeView/docTreeData.tsx b/docs/src/components/DocsTreeView/docTreeData.tsx
index 7e4a986e..c048d04a 100644
--- a/docs/src/components/DocsTreeView/docTreeData.tsx
+++ b/docs/src/components/DocsTreeView/docTreeData.tsx
@@ -15,8 +15,7 @@ export default [{
   }, {
     "titleNode": "Acrylic"
   }, {
-    "titleNode": "Reveal",
-    hidden: true
+    "titleNode": "Reveal"
   }, {
     "titleNode": "Typography"
   }, {
diff --git a/docs/src/components/Wrapper/index.tsx b/docs/src/components/Wrapper/index.tsx
index fc06d586..b5e12315 100644
--- a/docs/src/components/Wrapper/index.tsx
+++ b/docs/src/components/Wrapper/index.tsx
@@ -48,10 +48,6 @@ export default class Wrapper extends React.Component<WrapperProps, WrapperState>
     headerHeight: 60
   };
 
-  componentWillMount() {
-    this.resize();
-  }
-
   componentDidMount() {
     addCSSRule("body { overflow-x: hidden; }");
     window.addEventListener("resize", this.resize);
diff --git a/docs/src/components/WrapperWithCategories/index.tsx b/docs/src/components/WrapperWithCategories/index.tsx
index 40104c3a..907a5a41 100644
--- a/docs/src/components/WrapperWithCategories/index.tsx
+++ b/docs/src/components/WrapperWithCategories/index.tsx
@@ -6,6 +6,7 @@ import getStripedBackground from "react-uwp/styles/getStripedBackground";
 import Wrapper from "../Wrapper";
 import Icon from "react-uwp/Icon";
 import DocsTreeView from "../DocsTreeView";
+import { render } from "../../../../node_modules/@types/react-dom";
 
 export interface DataProps {
   path?: string;
@@ -22,11 +23,31 @@ export interface WrapperWithCategoriesState {
 
 const HEADER_HEIGHT = 60;
 const FOOTER_HEIGHT = 260;
+const { innerWidth } = window;
+let screenType: any;
+let renderContentWidth: any;
+if (innerWidth < 960) {
+  screenType = "phone";
+  renderContentWidth = "100%";
+} else if (innerWidth >= 960 && innerWidth < 1366) {
+  screenType = "tablet";
+  renderContentWidth = 960;
+} else if (innerWidth >= 1366 && innerWidth < 1920) {
+  screenType = "laptop";
+  renderContentWidth = 1280;
+} else {
+  screenType = "pc";
+  renderContentWidth = 1600;
+}
+
 export default class WrapperWithCategories extends React.Component<WrapperWithCategoriesProps, WrapperWithCategoriesState> {
   static defaultProps: WrapperWithCategoriesProps = {
     path: "/"
   };
-  state: WrapperWithCategoriesState = {};
+  state: WrapperWithCategoriesState = {
+    renderContentWidth,
+    screenType
+  };
 
   static contextTypes = { theme: PropTypes.object };
   context: { theme: ReactUWP.ThemeType };
diff --git a/docs/src/routes/Styles/Reveal/index.tsx b/docs/src/routes/Styles/Reveal/index.tsx
new file mode 100644
index 00000000..6c415354
--- /dev/null
+++ b/docs/src/routes/Styles/Reveal/index.tsx
@@ -0,0 +1,58 @@
+import * as React from "react";
+import * as PropTypes from "prop-types";
+import RevealItem from "react-uwp/Reveal/RevealItem";
+import RevealContainer from "react-uwp/Reveal/RevealContainer";
+
+export interface DataProps {}
+
+export interface RevealProps extends DataProps, React.HTMLAttributes<HTMLDivElement> {}
+
+export interface RevealState {}
+
+export class Reveal extends React.Component<RevealProps, RevealState> {
+  static defaultProps: RevealProps = {};
+
+  state: RevealState = {};
+
+  static contextTypes = { theme: PropTypes.object };
+  context: { theme: ReactUWP.ThemeType };
+
+  render() {
+    const { ...attributes } = this.props;
+    const { theme } = this.context;
+    const inlineStyles = getStyles(this);
+    const styles = theme.prepareStyles({
+      styles: inlineStyles,
+      className: "Reveal"
+    });
+
+    return (
+      <div
+        {...attributes}
+        className={styles.root.className}
+      >
+        <RevealContainer>
+          <RevealItem>
+            Reveal
+          </RevealItem>
+        </RevealContainer>
+      </div>
+    );
+  }
+}
+
+function getStyles(Reveal: Reveal) {
+  const {
+    context: { theme },
+    props: { style }
+  } = Reveal;
+  const { prefixStyle } = theme;
+
+  return {
+    root: prefixStyle({
+      ...style
+    })
+  };
+}
+
+export default Reveal;
diff --git a/docs/src/routes/index.tsx b/docs/src/routes/index.tsx
index 0b2f4660..39e072da 100644
--- a/docs/src/routes/index.tsx
+++ b/docs/src/routes/index.tsx
@@ -138,6 +138,13 @@ function getRoutes(path = "/") {
             cb(null, require("./Styles/Acrylic").default);
           }, "react-uwp-style-Acrylic");
         }
+      }, {
+        path: "Reveal",
+        getComponent: (location: Location, cb: RouterCallback) => {
+          require.ensure([], (require) => {
+            cb(null, require("./Styles/Reveal").default);
+          }, "react-uwp-style-Reveal");
+        }
       }, {
         path: "Animation",
         getComponent: (location: Location, cb: RouterCallback) => {
diff --git a/src/Reveal/RevealContainer.tsx b/src/Reveal/RevealContainer.tsx
new file mode 100644
index 00000000..8a794ccb
--- /dev/null
+++ b/src/Reveal/RevealContainer.tsx
@@ -0,0 +1,160 @@
+import * as React from "react";
+import * as PropTypes from "prop-types";
+import RevealItem from "./RevealItem";
+import checkIntersects from "./checkIntersects";
+
+export interface DataProps {
+  hoverSize?: number;
+  hoverColor?: string;
+  borderColor?: string;
+  glowColor?: string;
+  glowSize?: string;
+}
+
+export interface RevealContainerProps extends DataProps, React.HTMLAttributes<HTMLDivElement> {}
+
+export interface RevealContainerState {}
+
+export class RevealContainer extends React.Component<RevealContainerProps, RevealContainerState> {
+  static defaultProps: RevealContainerProps = {};
+
+  state: RevealContainerState = {};
+  config: DataProps = {};
+
+  static contextTypes = { theme: PropTypes.object };
+  context: { theme: ReactUWP.ThemeType };
+  revealEls: HTMLDivElement[] = [];
+
+  componentDidMount() {
+    let {
+      hoverSize,
+      hoverColor,
+      borderColor,
+      glowColor,
+      glowSize
+    } = this.props;
+    const { theme } = this.context;
+    const setDefaultValue = (a: any, value: any) => a = (a === void 0 ? value : a);
+    hoverSize = setDefaultValue(hoverSize, 30);
+    hoverColor = setDefaultValue(hoverColor, theme.baseMediumHigh);
+    borderColor = setDefaultValue(borderColor, theme.baseMediumHigh);
+    glowColor = setDefaultValue(glowColor, theme.baseLow);
+    glowSize = setDefaultValue(glowSize, 4);
+    this.config = {
+      hoverSize,
+      hoverColor,
+      borderColor,
+      glowColor,
+      glowSize
+    };
+    window.addEventListener("mousemove", this.handleMouseMove);
+  }
+
+  componentWillUnmount() {
+    window.removeEventListener("mousemove", this.handleMouseMove);
+  }
+
+  handleMouseMove = (e: MouseEvent) => {
+    const {
+      hoverSize,
+      hoverColor,
+      borderColor,
+      glowColor,
+      glowSize
+    } = this.config;
+    const { theme: { prefixStyle } } = this.context;
+    const { clientX, clientY } = e;
+    this.revealEls.forEach(el => {
+      const { left, top, width, height } = el.getBoundingClientRect();
+      const isIntersects = checkIntersects({
+        x: clientX,
+        y: clientY,
+        r: hoverSize
+      }, {
+        x: left,
+        y: top,
+        width,
+        height
+      });
+      if (isIntersects) {
+        Object.assign(el.style, prefixStyle({
+          border: "double 12px transparent",
+          backgroundImage: "radial-gradient(circle at top left, #f00,#3020ff)",
+          backgroundOrigin: "border-box",
+          backgroundClip: "content-box, border-box"
+        } as any));
+      } else {
+        Object.assign(el.style, prefixStyle({
+          borderImage: "",
+          borderWidth: "3px",
+          borderStyle: "solid"
+        } as any));
+      }
+    });
+  }
+
+  handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
+    console.log(this.revealEls);
+  }
+
+  addRevealEl = (el: HTMLDivElement) => {
+    this.revealEls.push(el);
+  }
+
+  removeRevealEl = (el: HTMLDivElement) => {
+    this.revealEls.splice(this.revealEls.indexOf(el), 1);
+  }
+
+  render() {
+    const {
+      hoverSize,
+      hoverColor,
+      borderColor,
+      glowColor,
+      glowSize,
+      children,
+      ...attributes
+    } = this.props;
+    const { theme } = this.context;
+    const inlineStyles = getStyles(this);
+    const styles = theme.prepareStyles({
+      styles: inlineStyles,
+      className: "RevealContainer"
+    });
+
+    return (
+      <div
+        {...attributes}
+        className={styles.root.className}
+        onMouseDown={this.handleMouseDown}
+      >
+        {React.Children.map(children, (child: any, index) => {
+          const isRevealItem = child.type && child.type === RevealItem;
+          console.log(isRevealItem);
+          return (isRevealItem ? React.cloneElement(child, {
+            haveContainer: true,
+            addEl: this.addRevealEl,
+            removeEl: this.removeRevealEl
+          }) : child);
+        })}
+      </div>
+    );
+  }
+}
+
+function getStyles(RevealContainer: RevealContainer) {
+  const {
+    context: { theme },
+    props: { style }
+  } = RevealContainer;
+  const { prefixStyle } = theme;
+
+  return {
+    root: prefixStyle({
+      display: "inline-block",
+      ...style
+    })
+  };
+}
+
+export default RevealContainer;
diff --git a/src/Reveal/RevealItem.tsx b/src/Reveal/RevealItem.tsx
new file mode 100644
index 00000000..0ec2f776
--- /dev/null
+++ b/src/Reveal/RevealItem.tsx
@@ -0,0 +1,78 @@
+import * as React from "react";
+import * as PropTypes from "prop-types";
+import { findDOMNode } from "react-dom";
+
+export interface DataProps {
+  haveContainer?: boolean;
+  addEl?: (el: HTMLElement) => void;
+  removeEl?: (el: HTMLElement) => void;
+}
+
+export interface RevealItemProps extends DataProps, React.HTMLAttributes<HTMLDivElement> {}
+
+export interface RevealItemState {}
+
+export class RevealItem extends React.Component<RevealItemProps, RevealItemState> {
+  static defaultProps: RevealItemProps = {};
+
+  state: RevealItemState = {};
+  rootEl: HTMLDivElement;
+
+  static contextTypes = { theme: PropTypes.object };
+  context: { theme: ReactUWP.ThemeType };
+  componentDidMount() {
+    const { haveContainer, addEl } = this.props;
+    if (haveContainer && addEl) {
+      addEl((findDOMNode(this) as HTMLDivElement));
+    }
+  }
+
+  componentWillUnmount() {
+    const { haveContainer, removeEl } = this.props;
+    if (haveContainer && removeEl) {
+      removeEl((findDOMNode(this) as HTMLDivElement));
+    }
+  }
+
+  render() {
+    const {
+      children,
+      haveContainer,
+      addEl,
+      removeEl,
+      ...attributes } = this.props;
+    const { theme } = this.context;
+    const inlineStyles = getStyles(this);
+    const styles = theme.prepareStyles({
+      styles: inlineStyles,
+      className: "RevealItem"
+    });
+
+    return (
+      <div
+        {...attributes}
+        className={styles.root.className}
+        ref={rootEl => this.rootEl}
+      >
+        {children}
+      </div>
+    );
+  }
+}
+
+function getStyles(RevealItem: RevealItem) {
+  const {
+    context: { theme },
+    props: { style }
+  } = RevealItem;
+  const { prefixStyle } = theme;
+
+  return {
+    root: prefixStyle({
+      display: "inline-block",
+      ...style
+    })
+  };
+}
+
+export default RevealItem;
diff --git a/src/Reveal/activeEvent.ts b/src/Reveal/activeEvent.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/Reveal/addRevealItem.tsx b/src/Reveal/addRevealItem.tsx
new file mode 100644
index 00000000..8e9aa68f
--- /dev/null
+++ b/src/Reveal/addRevealItem.tsx
@@ -0,0 +1,9 @@
+import * as React from "react";
+import RevealItem from "./RevealItem";
+
+export default function addRevealItem<T>(Cpt: T): T {
+    let AnyCpt: any = Cpt;
+    return (
+        (props: any) => <RevealItem><AnyCpt {...props} /></RevealItem>
+    ) as any;
+}
diff --git a/src/Reveal/checkIntersects.ts b/src/Reveal/checkIntersects.ts
new file mode 100644
index 00000000..3d5c5ffc
--- /dev/null
+++ b/src/Reveal/checkIntersects.ts
@@ -0,0 +1,30 @@
+export interface CircleType {
+    x: number;
+    y: number;
+    r: number;
+}
+
+export interface ReactType {
+    x: number;
+    y: number;
+    width: number;
+    height: number;
+}
+
+export default function checkIntersects(circle: CircleType, rect: ReactType) {
+    const circleDistanceX = Math.abs(circle.x - rect.x);
+    const circleDistanceY = Math.abs(circle.y - rect.y);
+
+    if (circleDistanceX > (rect.width / 2 + circle.r) || circleDistanceY > (rect.height / 2 + circle.r)) {
+        return false;
+    }
+
+    if (circleDistanceX <= (rect.width / 2) || circleDistanceY <= (rect.height / 2)) {
+        return true;
+    }
+
+    const cornerDistanceSQ = (circleDistanceX - rect.width / 2) ^ 2 +
+        (circleDistanceY - rect.height / 2) ^ 2;
+
+    return (cornerDistanceSQ <= (circle.r ^ 2));
+}
diff --git a/src/Reveal/hoverEvent.ts b/src/Reveal/hoverEvent.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/Theme/index.tsx b/src/Theme/index.tsx
index 28a65155..49a29753 100644
--- a/src/Theme/index.tsx
+++ b/src/Theme/index.tsx
@@ -359,6 +359,25 @@ export class Theme extends React.Component<ThemeProps, ThemeState> {
     localStorage.setItem(customLocalStorageName, "");
   }
 
+  themeMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
+    // const { currTheme } = this.state;
+    // const { onMouseMove } = this.props;
+
+    // console.log(e.clientX, e.clientY);
+    // if (onMouseMove) onMouseMove(e);
+    // if (currTheme.useRevealEffect) {
+    // }
+  }
+
+  themeMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
+    const { currTheme } = this.state;
+    const { onMouseDown } = this.props;
+    if (onMouseDown) onMouseDown(e);
+    if (currTheme.useRevealEffect) {
+      console.log(e.currentTarget);
+    }
+  }
+
   render() {
     const {
       autoSaveTheme,
@@ -415,6 +434,8 @@ export class Theme extends React.Component<ThemeProps, ThemeState> {
           className: "theme-root",
           extendsClassName: className ? `${this.themeClassName} ${className}` : this.themeClassName
         })}
+        onMouseMove={this.themeMouseMove}
+        onMouseDown={this.themeMouseDown}
       >
         <RenderToBody
           ref={backgroundEl => this.backgroundEl = backgroundEl}
diff --git a/src/__Mock__/index.tsx b/src/__Mock__/index.tsx
index 8714ab67..2b49c0c3 100644
--- a/src/__Mock__/index.tsx
+++ b/src/__Mock__/index.tsx
@@ -18,12 +18,16 @@ export class Mock extends React.Component<MockProps, MockState> {
   render() {
     const { ...attributes } = this.props;
     const { theme } = this.context;
-    const styles = getStyles(this);
+    const inlineStyles = getStyles(this);
+    const styles = theme.prepareStyles({
+      styles: inlineStyles,
+      className: "Mock"
+    });
 
     return (
       <div
         {...attributes}
-        style={styles.root}
+        className={styles.root.className}
       >
         Mock
       </div>
@@ -31,11 +35,11 @@ export class Mock extends React.Component<MockProps, MockState> {
   }
 }
 
-function getStyles(mock: Mock) {
+function getStyles(Mock: Mock) {
   const {
     context: { theme },
     props: { style }
-  } = mock;
+  } = Mock;
   const { prefixStyle } = theme;
 
   return {
diff --git a/src/styles/getTheme.ts b/src/styles/getTheme.ts
index 28cadf66..b019da3a 100644
--- a/src/styles/getTheme.ts
+++ b/src/styles/getTheme.ts
@@ -28,6 +28,7 @@ export interface ThemeConfig {
   desktopBackgroundImage?: string;
   userAgent?: string;
   useInlineStyle?: boolean;
+  useRevealEffect?: boolean;
 }
 
 export default function getTheme(themeConfig?: ThemeConfig): ReactUWP.ThemeType {
@@ -39,12 +40,14 @@ export default function getTheme(themeConfig?: ThemeConfig): ReactUWP.ThemeType
     useFluentDesign,
     desktopBackgroundImage,
     userAgent,
-    useInlineStyle
+    useInlineStyle,
+    useRevealEffect
   } = themeConfig;
 
   themeName = themeName || "dark";
   accent = accent || "#0078D7";
   useFluentDesign = useFluentDesign === void 0 ? false : useFluentDesign;
+  useRevealEffect = useRevealEffect === void 0 ? true : false;
 
   const isDark = themeName === "dark";
   const baseHigh = isDark ? "#fff" : "#000";
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 1e4b1be4..6c4d8905 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -121,6 +121,7 @@ export interface ThemeType {
   desktopBackgroundImage?: string;
   desktopBackground?: string;
   useInlineStyle?: boolean;
+  useRevealEffect?: boolean;
 
   haveAcrylicTextures?: boolean;
   acrylicTexture40?: AcrylicTexture;
