Skip to content

Commit

Permalink
feat: Add StyleManager support multiple theme
Browse files Browse the repository at this point in the history
  • Loading branch information
myxvisual committed Aug 8, 2017
1 parent 74c15fb commit 867a3af
Show file tree
Hide file tree
Showing 4 changed files with 453 additions and 1 deletion.
153 changes: 153 additions & 0 deletions src/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import * as React from "react";
import * as PropTypes from "prop-types";

import ElementState from "../ElementState";
import Icon from "../Icon";
import Tooltip from "../Tooltip";
import { setStylesToManager, setStyleToManager } from "../styles/setStylesToManager";

export interface DataProps {
/**
* Control `Button` border size.
*/
borderSize?: string;
/**
* Is onMouseEnter Inline Style will assign to default `hoverStyle`.
*/
hoverStyle?: React.CSSProperties;
/**
* Is onMouseDown Inline Style will assign to default `hoverStyle`.
*/
activeStyle?: React.CSSProperties;
/**
* icon use the Iconfont like `\uE00A` or iconName `HeartLegacy`.
*/
icon?: string;
/**
* This will assign to default `iconStyle`.
*/
iconStyle?: React.CSSProperties;
/**
* will change to icon position, default is `left`.
*/
iconPosition?: "left" | "right";
/**
* if `true`, will become `Disabled Button`.
*/
disabled?: boolean;
/**
* `tooltip` is any type, you can passe a `React.Element` or `string`.
*/
tooltip?: React.ReactElement<any> | string;
/**
* Set custom Button `background`.
*/
background?: string;
}

export interface ButtonProps extends DataProps, React.HTMLAttributes<HTMLButtonElement> {}

export class Button extends React.Component<ButtonProps> {
static defaultProps: ButtonProps = {
borderSize: "2px",
iconPosition: "left"
};

static contextTypes = { theme: PropTypes.object };
context: { theme: ReactUWP.ThemeType };

refs: { container: HTMLButtonElement };

render() {
const {
borderSize,
style,
hoverStyle,
children,
icon,
iconStyle,
iconPosition,
disabled,
tooltip,
background,
activeStyle,
...attributes
} = this.props;
const { theme } = this.context;

const currIconStyle: React.CSSProperties = {
padding: "0 4px",
display: "inline",
...theme.prepareStyles(iconStyle)
};

const styles = setStylesToManager({
className: "button",
theme,
styles: {
root: {
display: "inline-block",
verticalAlign: "middle",
cursor: "pointer",
color: theme.baseHigh,
outline: "none",
padding: "4px 16px",
transition: "all .25s",
border: `${borderSize} solid transparent`,
background: background || theme.baseLow,
...theme.prepareStyles(style),
"&:hover": disabled ? void 0 : {
border: `2px solid ${theme.baseMediumLow}`
},
"&:active": disabled ? void 0 : {
background: theme.baseMediumLow
},
"&:disabled": {
background: theme.baseMedium,
cursor: "not-allowed",
color: theme.baseMedium
}
},
icon: {
padding: "0 4px",
display: "inline-block",
...theme.prepareStyles(iconStyle)
}
}
});

const normalRender = (
icon ? (iconPosition === "right" ? (
<button {...attributes} disabled={disabled} {...styles.root}>
<span style={{ verticalAlign: "middle" }}>
{children}
</span>
<Icon style={currIconStyle}>
{icon}
</Icon>
</button>
) : (
<button {...attributes} disabled={disabled} {...styles.root}>
<Icon {...styles.icon}>
{icon}
</Icon>
<span style={{ verticalAlign: "middle" }}>
{children}
</span>
</button>
)) : (
<button {...attributes as any} disabled={disabled} {...styles.root}>
{children}
</button>
)
);

return tooltip ? (
<Tooltip contentNode={tooltip}>
{normalRender}
</Tooltip>
) : normalRender;
}
}

export default Button;
3 changes: 2 additions & 1 deletion src/Theme/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from "react";
import * as PropTypes from "prop-types";

import StyleManager from "../styles/StyleManager";
import darkTheme from "../styles/darkTheme";
import getTheme from "../styles/getTheme";
import RenderToBody from "../RenderToBody";
Expand Down Expand Up @@ -193,10 +194,10 @@ export class Theme extends React.Component<ThemeProps, ThemeState> {
generateAcrylicTextures: this.generateAcrylicTextures,
forceUpdateTheme: this.forceUpdateTheme
} as ReactUWP.ThemeType);
theme.styleManager = new StyleManager(theme);
}

handleNewTheme = (theme: ReactUWP.ThemeType) => {
this.bindNewThemeMethods(theme);
this.props.themeWillUpdate(theme);
}

Expand Down
101 changes: 101 additions & 0 deletions src/styles/StyleManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as createHash from "murmurhash-js/murmurhash3_gc";
import isUnitlessNumber from "../common/react/isUnitlessNumber";

const replace2Dashes = (key: string) => key.replace(/[A-Z]/g, $1 => `-${$1.toLowerCase()}`);
const getStyleValue = (key: string, value: string) => ((typeof value === "number" && !(isUnitlessNumber as any)[key]) ? `${value}px` : value);

export interface CustomCSSProperties extends React.CSSProperties {
"&:hover"?: React.CSSProperties;
"&:active"?: React.CSSProperties;
"&:focus"?: React.CSSProperties;
"&:disabled"?: React.CSSProperties;
}

const extendsStyleKeys: any = {
"&:hover": true,
"&:active": true,
"&:focus": true,
"&:disabled": true
};

export class StyleManager {
globalClassName: string;
theme: ReactUWP.ThemeType;
themeId = 0;
styleElement: HTMLStyleElement = null;
sheets: any = {};

constructor(theme: ReactUWP.ThemeType, globalClassName?: string) {
this.globalClassName = globalClassName ? `${globalClassName}-` : "";
this.setupTheme(theme);
}

setupTheme = (theme: ReactUWP.ThemeType) => {
this.theme = theme;
this.themeId = createHash([theme.accent, theme.themeName, theme.useFluentDesign].join(", "));
}

style2CSSText = (style: React.CSSProperties) => style ? Object.keys(style).map(key => (
` ${replace2Dashes(key)}: ${getStyleValue(key, style[key])};`
)).join("\n") : void 0

renderSheets = () => {};

sheetsToString = () => `\n${Object.keys(this.sheets).map(id => this.sheets[id].CSSText).join("")}`;

updateTheme = () => {};

addSheet = (style: CustomCSSProperties, className = "", callback = () => {}) => {
const id = createHash(`${this.themeId}: ${JSON.stringify(style)}`);
const classNameWithHash = `${this.globalClassName}${className}-${id}`;
const styleKeys = Object.keys(style);
let CSSText = "";
let contentCSSText = "";
let extendsCSSText = "";

for (const styleKey of styleKeys) {
if (extendsStyleKeys[styleKey]) {
const extendsStyle = style[styleKey];
if (extendsStyle) {
extendsCSSText += `.${classNameWithHash}${styleKey.slice(1)} {\n${this.style2CSSText(extendsStyle)}\n}\n`;
}
} else {
contentCSSText += ` ${replace2Dashes(styleKey)}: ${getStyleValue(styleKey, style[styleKey])};\n`;
}
}

CSSText += `.${classNameWithHash} {\n${contentCSSText}\n}\n`;
CSSText += extendsCSSText;

this.sheets[id] = { CSSText, classNameWithHash, id, className };
callback();
return this.sheets[id];
}

addSheetWithUpdate = (style: CustomCSSProperties, className = "") => {
return this.addSheet(style, className, this.updateSheetsToDOM);
}

updateSheetByID = () => {};

updateAllSheets = () => {};

removeSheetByID = () => {};

updateSheetsToDOM = () => {
const name = `data-uwp-jss-${this.themeId}`;
this.styleElement = document.querySelector(`[${name}]`) as HTMLStyleElement;
const textContent = this.sheetsToString();

if (!this.styleElement) {
this.styleElement = document.createElement("style");
this.styleElement.setAttribute(name, "");
this.styleElement.textContent = textContent;
document.head.appendChild(this.styleElement);
} else {
this.styleElement.textContent = textContent;
}
}
}

export default StyleManager;
Loading

0 comments on commit 867a3af

Please sign in to comment.