Skip to content

Commit 0f8f9f2

Browse files
authored
feat: add color picker to MSFT dev site (#758)
* reordering slot position * wiring color picker * adding documentation * styling color pickers * cleaning up JSX * removing unused variable * fixing linting errors * renaming Themes to Theme * preventing custom theme when only changing accent color * trying to reduce code complexity * fixing tslint errors * fixing spelling mistake
1 parent 1788143 commit 0f8f9f2

File tree

5 files changed

+264
-47
lines changed

5 files changed

+264
-47
lines changed

packages/fast-components-react-msft/app/app.tsx

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
1-
import * as React from "react";
2-
import { glyphBuildingblocks } from "@microsoft/fast-glyphs-msft";
3-
import manageJss, { DesignSystemProvider } from "@microsoft/fast-jss-manager-react";
4-
import { DesignSystemDefaults, IDesignSystem } from "@microsoft/fast-components-styles-msft";
5-
import { IHypertextClassNameContract, IManagedClasses } from "@microsoft/fast-components-class-name-contracts-base";
61
import Site, {
72
componentFactory,
83
formChildFromExamplesFactory,
94
IFormChildOption,
105
ISiteProps,
116
ITheme,
7+
ShellSlot,
128
SiteCategory,
139
SiteCategoryIcon,
1410
SiteCategoryItem,
1511
SiteMenu,
1612
SiteMenuItem,
1713
SiteTitle,
18-
SiteTitleBrand,
14+
SiteTitleBrand
1915
} from "@microsoft/fast-development-site-react";
16+
import * as React from "react";
17+
import manageJss, { DesignSystemProvider } from "@microsoft/fast-jss-manager-react";
18+
import { DesignSystemDefaults, IDesignSystem } from "@microsoft/fast-components-styles-msft";
19+
import { IHypertextClassNameContract, IManagedClasses } from "@microsoft/fast-components-class-name-contracts-base";
20+
import { glyphBuildingblocks } from "@microsoft/fast-glyphs-msft";
2021
import { ComponentStyles, ICSSRules } from "@microsoft/fast-jss-manager";
2122
import { Direction } from "@microsoft/fast-application-utilities";
2223
import * as examples from "./examples";
2324
import Hypertext from "../src/hypertext";
25+
import ColorPicker, { IColorConfig } from "./color-picker";
2426

2527
/* tslint:disable-next-line */
2628
const sketchDesignKit = require("./fast-dna-msft-design-kit.sketch");
@@ -36,23 +38,33 @@ const hypertextStyles: ComponentStyles<IHypertextClassNameContract, undefined> =
3638
}
3739
};
3840

39-
export interface IAppState {
40-
direction: Direction;
41-
theme: string;
41+
enum Theme {
42+
dark = "dark",
43+
light = "light",
44+
custom = "custom"
4245
}
4346

44-
const themes: ITheme[] = [
45-
{id: "light", displayName: "light", background: DesignSystemDefaults.backgroundColor},
46-
{id: "dark", displayName: "dark", background: DesignSystemDefaults.foregroundColor}
47-
];
47+
export interface IAppState extends IColorConfig {
48+
theme: Theme;
49+
direction: Direction;
50+
}
4851

4952
export default class App extends React.Component<{}, IAppState> {
53+
private themes: ITheme[] = [
54+
{id: Theme.light, displayName: Theme.light, background: DesignSystemDefaults.backgroundColor},
55+
{id: Theme.dark, displayName: Theme.dark, background: DesignSystemDefaults.foregroundColor},
56+
{id: Theme.custom, displayName: Theme.custom}
57+
];
58+
5059
constructor(props: {}) {
5160
super(props);
5261

5362
this.state = {
5463
direction: Direction.ltr,
55-
theme: "light"
64+
foregroundColor: DesignSystemDefaults.foregroundColor,
65+
backgroundColor: DesignSystemDefaults.backgroundColor,
66+
accentColor: DesignSystemDefaults.brandColor,
67+
theme: Theme.light
5668
};
5769
}
5870

@@ -62,7 +74,8 @@ export default class App extends React.Component<{}, IAppState> {
6274
formChildOptions={formChildOptions}
6375
onUpdateDirection={this.handleUpdateDirection}
6476
onUpdateTheme={this.handleUpdateTheme}
65-
themes={themes}
77+
themes={this.themes}
78+
activeTheme={this.getThemeById(this.state.theme)}
6679
>
6780
<SiteMenu slot={"header"}>
6881
<SiteMenuItem>
@@ -85,15 +98,30 @@ export default class App extends React.Component<{}, IAppState> {
8598
<SiteCategory slot={"category"} name={"Components"}>
8699
{componentFactory(examples, {...this.generateDesignSystem()})}
87100
</SiteCategory>
101+
<div slot={ShellSlot.infoBar}>
102+
<ColorPicker
103+
foregroundColor={this.state.foregroundColor}
104+
backgroundColor={this.state.backgroundColor}
105+
accentColor={this.state.accentColor}
106+
onColorUpdate={this.handleColorUpdate}
107+
/>
108+
</div>
88109
</Site>
89110
);
90111
}
91112

113+
private getThemeById(id: Theme): ITheme {
114+
return this.themes.find((theme: ITheme): boolean => {
115+
return theme.id === id;
116+
});
117+
}
118+
92119
private generateDesignSystem(): IDesignSystem {
93120
const designSystem: Partial<IDesignSystem> = {
94121
direction: this.state.direction,
95-
foregroundColor: this.state.theme === "dark" ? DesignSystemDefaults.backgroundColor : DesignSystemDefaults.foregroundColor,
96-
backgroundColor: this.state.theme === "dark" ? DesignSystemDefaults.foregroundColor : DesignSystemDefaults.backgroundColor
122+
foregroundColor: this.state.foregroundColor,
123+
backgroundColor: this.state.backgroundColor,
124+
brandColor: this.state.accentColor
97125
};
98126

99127
return Object.assign({}, DesignSystemDefaults, designSystem);
@@ -109,9 +137,40 @@ export default class App extends React.Component<{}, IAppState> {
109137
});
110138
}
111139

112-
private handleUpdateTheme = (theme: string): void => {
113-
this.setState({
114-
theme
140+
private handleUpdateTheme = (theme: Theme): void => {
141+
if (theme !== Theme.custom) {
142+
this.setState({
143+
theme,
144+
foregroundColor: theme === Theme.dark ? DesignSystemDefaults.backgroundColor : DesignSystemDefaults.foregroundColor,
145+
backgroundColor: theme === Theme.dark ? DesignSystemDefaults.foregroundColor : DesignSystemDefaults.backgroundColor
146+
});
147+
} else {
148+
this.setCustomThemeBackground(this.state.backgroundColor);
149+
this.setState({
150+
theme
151+
});
152+
}
153+
}
154+
155+
/**
156+
* Handles any changes made by the user to the color picker inputs
157+
*/
158+
private handleColorUpdate = (config: IColorConfig): void => {
159+
this.setCustomThemeBackground(config.backgroundColor);
160+
this.setState(
161+
config.backgroundColor !== this.state.backgroundColor || config.foregroundColor !== this.state.foregroundColor
162+
? { theme: Theme.custom, ...config }
163+
: config
164+
);
165+
}
166+
167+
/**
168+
* Assign a background color to the custom theme so that it can be applied to the background of the examples view
169+
* @param value The color to assign
170+
*/
171+
private setCustomThemeBackground(value: string): void {
172+
this.themes = this.themes.map((theme: ITheme): ITheme => {
173+
return theme.id !== Theme.custom ? theme : Object.assign({}, theme, { background: value});
115174
});
116175
}
117176
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import * as React from "react";
2+
import manageJss, { ComponentStyles, IJSSManagerProps, IManagedClasses } from "@microsoft/fast-jss-manager-react";
3+
import { IDesignSystem } from "@microsoft/fast-components-styles-msft";
4+
5+
export interface IColorConfig {
6+
foregroundColor: string;
7+
backgroundColor: string;
8+
accentColor: string;
9+
}
10+
11+
export interface IColorPickerProps extends IColorConfig {
12+
onColorUpdate: (colors: IColorConfig) => void;
13+
}
14+
15+
export interface IColorPickerManagedClasses {
16+
colorPicker: string;
17+
colorPicker_label: string;
18+
}
19+
20+
const styles: ComponentStyles<IColorPickerManagedClasses, IDesignSystem> = {
21+
colorPicker: {
22+
display: "flex",
23+
height: "100%",
24+
alignItems: "center"
25+
},
26+
colorPicker_label: {
27+
margin: "0 12px"
28+
}
29+
};
30+
31+
class ColorPicker extends React.Component<IColorPickerProps & IManagedClasses<IColorPickerManagedClasses>, undefined> {
32+
/**
33+
* Ref object for foreground color input
34+
*/
35+
private foregroundRef: React.RefObject<HTMLInputElement>;
36+
37+
/**
38+
* Ref object for background color input
39+
*/
40+
private backgroundRef: React.RefObject<HTMLInputElement>;
41+
42+
/**
43+
* Ref object for accent color input
44+
*/
45+
private accentRef: React.RefObject<HTMLInputElement>;
46+
47+
constructor(props: IColorPickerProps) {
48+
super(props);
49+
50+
this.foregroundRef = React.createRef();
51+
this.backgroundRef = React.createRef();
52+
this.accentRef = React.createRef();
53+
}
54+
55+
public render(): JSX.Element {
56+
return (
57+
<div className={this.props.managedClasses.colorPicker}>
58+
{this.createColorInput("foreground", this.props.foregroundColor, "foregroundInput", this.foregroundRef)}
59+
{this.createColorInput("background", this.props.backgroundColor, "backgroundInput", this.backgroundRef)}
60+
{this.createColorInput("accent", this.props.accentColor, "accentInput", this.accentRef)}
61+
</div>
62+
);
63+
}
64+
65+
/**
66+
* Creates individual label/input elements
67+
*/
68+
private createColorInput(name: string, value: string, id: string, ref: React.RefObject<HTMLInputElement>): JSX.Element {
69+
return (
70+
<React.Fragment>
71+
<label
72+
for={id}
73+
className={this.props.managedClasses.colorPicker_label}
74+
>
75+
{name}
76+
</label>
77+
<input
78+
type="color"
79+
value={this.formatColor(value)}
80+
id={id}
81+
name={name}
82+
onChange={this.handleColorPickerChange}
83+
ref={ref}
84+
/>
85+
</React.Fragment>
86+
);
87+
}
88+
89+
/**
90+
* Event handler for all color input changes
91+
*/
92+
private handleColorPickerChange = (e: React.FormEvent<HTMLInputElement>): void => {
93+
const value: string = e.currentTarget.value;
94+
const updatedColorKey: keyof IColorConfig = e.currentTarget === this.foregroundRef.current
95+
? "foregroundColor"
96+
: e.currentTarget === this.backgroundRef.current
97+
? "backgroundColor"
98+
: "accentColor";
99+
100+
if (typeof this.props.onColorUpdate === "function") {
101+
this.props.onColorUpdate(Object.assign({}, this.props, { [updatedColorKey]: value}));
102+
}
103+
}
104+
105+
/**
106+
* Ensures that colors are properly formatted
107+
* Color input elements don't understand three digit hex values, so we need to convert them to 6
108+
*/
109+
private formatColor(color: string): string {
110+
const threeDigitHex: RegExp = /\#([a-fA-F0-9]{3})$/g;
111+
const match: string[] | null = threeDigitHex.exec(color);
112+
113+
return Array.isArray(match)
114+
? `#${match[1].split("").map((character: string) => character + character).join("")}`
115+
: color;
116+
}
117+
}
118+
119+
export default manageJss(styles)(ColorPicker);

packages/fast-components-react-msft/package-lock.json

Lines changed: 21 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)