-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add StyleManager support multiple theme
- Loading branch information
Showing
4 changed files
with
453 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.