diff --git a/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx b/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx index 16647aa80b..09437c1a31 100644 --- a/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx +++ b/docs/src/components/ComponentDoc/ComponentExample/ComponentExample.tsx @@ -6,7 +6,7 @@ import { renderToStaticMarkup } from 'react-dom/server' import { html } from 'js-beautify' import copyToClipboard from 'copy-to-clipboard' import { Divider, Form, Grid, Menu, Segment, Visibility } from 'semantic-ui-react' -import { pxToRem } from 'src/lib' +import { pxToRem, createSpy, ActiveVariablesTracker } from 'src/lib' import evalTypeScript from 'docs/src/utils/evalTypeScript' import { Provider } from 'stardust' @@ -39,6 +39,18 @@ const controlsWrapperStyle = { minHeight: pxToRem(30), } +const variablesPanelStyle = { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-between', + maxHeight: pxToRem(250), + overflowY: 'auto', +} + +const variableInputStyle = { + paddingBottom: pxToRem(10), +} + /** * Renders a `component` and the raw `code` that produced it. * Allows toggling the the raw `code` code block. @@ -51,6 +63,7 @@ class ComponentExample extends PureComponent { KnobsComponent: any ghBugHref: any ghEditHref: any + variablesTracker = new ActiveVariablesTracker() static contextTypes = { onPassed: PropTypes.func, @@ -297,8 +310,21 @@ class ComponentExample extends PureComponent { getComponentName = () => this.props.examplePath.split('/')[1] renderWithProvider(ExampleComponent) { + const variableSpies = createSpy({ + componentDisplayName: this.getComponentName(), + onVariableTouched: variableName => this.variablesTracker.registerAsActive(variableName), + whenApplied: () => { + this.variablesTracker.isEnabled = true + }, + }) + + this.variablesTracker.resetAndDisable() return ( - + ) @@ -439,9 +465,13 @@ class ComponentExample extends PureComponent { return (
- + {_.map(defaultVariables, (val, key) => ( { - const { - backgroundColor, - backgroundColorHover, - circularRadius, - circularWidth, - typePrimaryColor, - typePrimaryBackgroundColor, - typePrimaryBackgroundColorHover, - typePrimaryBorderColor, - typeSecondaryColor, - typeSecondaryBackgroundColor, - typeSecondaryBackgroundColorHover, - typeSecondaryBorderColor, - }: IButtonVariables = variables + root: ({ props, theme, trackVariables }) => { + const v = trackVariables() return { - backgroundColor, + backgroundColor: v.backgroundColor, display: 'inline-block', verticalAlign: 'middle', cursor: 'pointer', borderWidth: 0, ':hover': { - backgroundColor: backgroundColorHover, + backgroundColor: v.backgroundColorHover, }, - ...(props.circular && { borderRadius: circularRadius, width: circularWidth }), + ...(props.circular && { borderRadius: v.circularRadius, width: v.circularWidth }), ...(props.type === 'primary' && { - color: typePrimaryColor, - backgroundColor: typePrimaryBackgroundColor, - borderColor: typePrimaryBorderColor, + color: v.typePrimaryColor, + backgroundColor: v.typePrimaryBackgroundColor, + borderColor: v.typePrimaryBorderColor, ':hover': { - backgroundColor: typePrimaryBackgroundColorHover, + backgroundColor: v.typePrimaryBackgroundColorHover, }, }), ...(props.type === 'secondary' && { - color: typeSecondaryColor, - backgroundColor: typeSecondaryBackgroundColor, - borderColor: typeSecondaryBorderColor, + color: v.typeSecondaryColor, + backgroundColor: v.typeSecondaryBackgroundColor, + borderColor: v.typeSecondaryBorderColor, borderWidth: '2px', ':hover': { borderColor: 'transparent', - backgroundColor: typeSecondaryBackgroundColorHover, + backgroundColor: v.typeSecondaryBackgroundColorHover, }, }), } diff --git a/src/components/Button/buttonVariables.ts b/src/components/Button/buttonVariables.ts index 39c76404bf..9b89f9b8a3 100644 --- a/src/components/Button/buttonVariables.ts +++ b/src/components/Button/buttonVariables.ts @@ -20,7 +20,7 @@ export default (siteVars: any): IButtonVariables => { backgroundColor: siteVars.gray08, backgroundColorHover: siteVars.gray06, circularRadius: pxToRem(999), - circularWidth: '32px', + circularWidth: pxToRem(32), typePrimaryColor: siteVars.white, typePrimaryBackgroundColor: siteVars.brand, typePrimaryBackgroundColorHover: siteVars.brand04, diff --git a/src/components/Provider/Provider.tsx b/src/components/Provider/Provider.tsx index 7085a968e2..a6dba58535 100644 --- a/src/components/Provider/Provider.tsx +++ b/src/components/Provider/Provider.tsx @@ -90,10 +90,8 @@ class Provider extends Component { } render() { - const { componentVariables, siteVariables, children } = this.props + const { componentVariables, siteVariables, children, variableSpies } = this.props - // ensure we don't assign `undefined` values to the theme context - // they will override values down stream const theme: any = {} if (siteVariables) { theme.siteVariables = siteVariables @@ -101,6 +99,9 @@ class Provider extends Component { if (componentVariables) { theme.componentVariables = componentVariables } + if (variableSpies) { + theme.spies = variableSpies + } return ( diff --git a/src/lib/getClasses.tsx b/src/lib/getClasses.tsx index c4a78d2811..fd5d1a625c 100644 --- a/src/lib/getClasses.tsx +++ b/src/lib/getClasses.tsx @@ -1,22 +1,27 @@ import renderer from './felaRenderer' +import { connect } from './variablesTracking' export interface IClasses { [key: string]: string } /** + * @param displayName * @param rules * @param props * @param variables * @param theme * @returns {{}} */ -const getClasses = (props, rules, variables: any = () => {}, theme: any = {}): IClasses => { +const getClasses = (displayName: string, props, rules, getVariables: any = () => {}, theme: any = {}): IClasses => { const { renderRule } = renderer + const variables = getVariables(theme.siteVariables) + const ruleProps = { props, theme, - variables: variables(theme.siteVariables), + variables: variables, + trackVariables: connect(theme.spies || {}, displayName, variables) } return Object.keys(rules).reduce((acc, ruleName) => { diff --git a/src/lib/index.ts b/src/lib/index.ts index 2af176621c..7119e64874 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -36,3 +36,10 @@ export { default as leven } from './leven' export { pxToRem, setHTMLFontSize } from './fontSizeUtility' export { customPropTypes, SUI } + +export { + createSpy, + connect, + ActiveVariablesTracker, + WithTrackedVariables, +} from './variablesTracking' diff --git a/src/lib/renderComponent.tsx b/src/lib/renderComponent.tsx index aa98caf579..e1c11812a8 100644 --- a/src/lib/renderComponent.tsx +++ b/src/lib/renderComponent.tsx @@ -45,7 +45,7 @@ const renderComponent =

( const mergedVariables = () => Object.assign({}, variablesFromFile, variablesFromTheme, variablesFromProp) - const classes = getClasses(props, rules, mergedVariables, theme) + const classes = getClasses(displayName, props, rules, mergedVariables, theme) classes.root = cx(className, classes.root, props.className) const config: IRenderResultConfig

= { ElementType, rest, classes } diff --git a/src/lib/variablesTracking.ts b/src/lib/variablesTracking.ts new file mode 100644 index 0000000000..3453f19af8 --- /dev/null +++ b/src/lib/variablesTracking.ts @@ -0,0 +1,81 @@ +export type WithTrackedVariables = TProps extends { variables: infer TVariables } + ? TProps & { trackVariables: () => TVariables } + : never + +type VariableTouchedCallback = (variableName: string | number | symbol) => void + +interface VariableSpy { + onVariableTouched: VariableTouchedCallback + whenApplied: () => void +} + +interface PerComponentVariableSpies { + [componentDisplayName: string]: VariableSpy +} + +interface VariableSpySpec { + componentDisplayName: string + onVariableTouched: VariableTouchedCallback + whenApplied?: () => void +} + +const DoNothing = () => {} + +export function createSpy(spec: VariableSpySpec): PerComponentVariableSpies { + const { componentDisplayName, onVariableTouched, whenApplied } = spec + + return { + [componentDisplayName]: { + onVariableTouched, + whenApplied: whenApplied || DoNothing, + }, + } +} + +function createTrackedVariablesProvider( + spy: VariableSpy, + variables: TVariables, +): () => TVariables { + return () => { + spy.whenApplied() + + return new Proxy(variables, { + get: (it, variableName) => { + spy.onVariableTouched(variableName) + return it[variableName] + }, + }) + } +} + +export function connect( + spies: PerComponentVariableSpies, + componentDisplayName: string, + variables: TVariables, +): () => TVariables { + if (!spies || !spies[componentDisplayName]) { + return () => variables + } + + return createTrackedVariablesProvider(spies[componentDisplayName], variables) +} + +export class ActiveVariablesTracker { + isEnabled = false + activeVariables: (string | number | symbol)[] = [] + + registerAsActive(variableName: string | number | symbol): void { + if (!this.activeVariables.some(v => v === variableName)) { + this.activeVariables.push(variableName) + } + } + + isActive(variableName: string | number | symbol): boolean { + return this.activeVariables.some(v => v === variableName) + } + + resetAndDisable(): void { + this.isEnabled = false + this.activeVariables = [] + } +}