Skip to content
This repository has been archived by the owner on Jan 20, 2022. It is now read-only.

docs(examples): show only relevant styling variables #80

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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.
Expand All @@ -51,6 +63,7 @@ class ComponentExample extends PureComponent<any, any> {
KnobsComponent: any
ghBugHref: any
ghEditHref: any
variablesTracker = new ActiveVariablesTracker()

static contextTypes = {
onPassed: PropTypes.func,
Expand Down Expand Up @@ -297,8 +310,21 @@ class ComponentExample extends PureComponent<any, any> {
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 (
<Provider componentVariables={this.state.componentVariables} rtl={this.state.showRtl}>
<Provider
componentVariables={this.state.componentVariables}
rtl={this.state.showRtl}
variableSpies={variableSpies}
Copy link
Member

@levithomason levithomason Jul 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to not have this part of the public API at present. One thought is to first consume the theme in the tree, clone it and observe it at that point, then provide the observed version down stream.

This could be wrapped up in a new component something like this (assumes some observe() deep clone exists):

import Provider from 'src/components/Provider'

const ProviderObserver = (props) => (
  <Provider.Consumer
    render={context => {
      const observed = observe(context)
      observed.onGet(props.onGet)

      return (
        <Provider {...observed}>
          {props.children}
        </Provider>
      )
    }}
  />
)

export default ProviderObserver

Now, anywhere we'd like to observe context in the tree we can use it like so:

// ComponentExample.js

  handleContextGet(context: object, path: string[]) {
    // assuming componentVariables.Button.backgroundColor was accessed:
    // 
    // context === { siteVariables: { ... }, componentVariables: { ... }, ... }
    // path === ['componentVariables', 'Button', 'backgroundColor']
  }

  renderWithProvider() {
    const { componentVariables, showRtl } = this.state
    return (
      <Provider componentVariables={componentVariables} rtl={showRtl}>
        <ProviderObserver onGet={this.handleContextGet}>
          <ExampleComponent knobs={this.getKnobsValue()} />
        </ProviderObserver>
      </Provider>
    )
  }

Now, any rules rendered down stream of our ProviderObserver that access the variables should result in firing the onGet callback. Here, we can track those variables.

What's also neat about this is that we can now also tell any part of the theme is accessed. Including listing out any other components that may be at play in the example.

Let's see if we can get an implementation working that does not require changes to any existing:

  1. Rules file
  2. Variables file
  3. Component file

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for your feedback - agree, will look into that

>
<ExampleComponent knobs={this.getKnobsValue()} />
</Provider>
)
Expand Down Expand Up @@ -439,9 +465,13 @@ class ComponentExample extends PureComponent<any, any> {
return (
<div>
<Form>
<Form.Group inline>
<Form.Group inline style={variablesPanelStyle}>
{_.map(defaultVariables, (val, key) => (
<Form.Input
disabled={
this.variablesTracker.isEnabled && !this.variablesTracker.isActive(key)
}
style={variableInputStyle}
key={key}
label={key}
defaultValue={val}
Expand Down
39 changes: 13 additions & 26 deletions src/components/Button/buttonRules.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,38 @@
import { IButtonVariables } from './buttonVariables'

export default {
root: ({ props, theme, variables }) => {
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,
},
}),
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button/buttonVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 4 additions & 3 deletions src/components/Provider/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,18 @@ class Provider extends Component<any, any> {
}

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
}
if (componentVariables) {
theme.componentVariables = componentVariables
}
if (variableSpies) {
theme.spies = variableSpies
}

return (
<RendererProvider renderer={this.props.rtl ? felaRtlRenderer : felaLtrRenderer}>
Expand Down
9 changes: 7 additions & 2 deletions src/lib/getClasses.tsx
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down
7 changes: 7 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
2 changes: 1 addition & 1 deletion src/lib/renderComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const renderComponent = <P extends {}>(
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<P> = { ElementType, rest, classes }
Expand Down
81 changes: 81 additions & 0 deletions src/lib/variablesTracking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
export type WithTrackedVariables<TProps> = 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<TVariables extends object>(
spy: VariableSpy,
variables: TVariables,
): () => TVariables {
return () => {
spy.whenApplied()

return new Proxy(variables, {
get: (it, variableName) => {
spy.onVariableTouched(variableName)
return it[variableName]
},
})
}
}

export function connect<TVariables extends object>(
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 = []
}
}