Skip to content

Instantly share code, notes, and snippets.

@taion
Last active July 31, 2016 09:10
Show Gist options
  • Save taion/ac60a7e54fb02e000b3a39fd3bb1e944 to your computer and use it in GitHub Desktop.
Save taion/ac60a7e54fb02e000b3a39fd3bb1e944 to your computer and use it in GitHub Desktop.
Embarrassing strawman API proposal that hopefully gets the point across
// This is _not_ supposed to be a real API. It's only intended to describe what
// I'm looking for. It's almost intentionally awful.
export const buttonHook = new OverrideHook({
properties: ['margin'],
});
export default function Button(props) {
return (
<button
{...props}
type="button"
className={classNames(
props.className,
buttonClassName,
buttonHook.className
)}
/>
);
}
import { buttonHook } from './Button';
// This is _not_ supposed to be a real API. It's only intended to describe what
// I'm looking for. It's almost intentionally awful.
const formClassName = makeClass({
border: '1 px solid grey',
[s`> ${buttonHook}`]: buttonHook({
margin: '0 1rem',
}),
});
export default function InlineForm(props) {
return (
<form
{...props}
className={classNames(props.className, formClassName)}
/>
);
}
// The idea is that you can do:
const form = (
<InlineForm>
<EmailInput />
<PasswordInput />
<LoginButton />
</InlineForm>
);
// Where <LoginButton> renders a <Button>.
import { buttonHook } from './Button';
// This is _not_ supposed to be a real API. It's only intended to describe what
// I'm looking for. It's almost intentionally awful.
const toolbarClassName = makeClass({
border: '1 px solid black',
[s`> ${buttonHook}`]: buttonHook({
margin: '0 0.5rem',
// If you try to set font-family or something here, throw.
}),
});
export default function Toolbar(props) {
return (
<div
{...props}
className={classNames(props.className, toolbarClassName)}
/>
);
}
@threepointone
Copy link

this is a quick sketch of how I'd do it in glamor. didn't even need the escape hatch!

// button.js 
let buttonStyle = { color: 'red', margin: 5 } // something

export const buttonHook = makeDecorator('margin', 'font') // could check if anything other than margin is passed down 

export class Button extends React.Component {
  static contextTypes = { 
    buttonHook: PropTypes.object // etc 
  }  
  render() {
    return <button {...merge(this.props.style, buttonStyle, this.context.buttonHook)}>
      {this.props.children}
    </button>
  }
}


// inlineform.js 
import buttonHook from './button'

let formStyle = style({ }) // no need to involve buttonhook in this 

@buttonHook({ margin: 10 })
class InlineForm extends React.Component {  
  render() {
    return <form {...formStyle}>
      {this.props.children}
    </form>
  }
}


// toolbar.js 
let toolbarStyle = {}
import buttonHook from './button'

@buttonHook({ font: 'Inconsolata' })
export class Toolbar extends React.Component {  
  render() {
    return <div {...merge(this.props.style, toolbarStyle)}>
      {this.props.children}
    </div>
  }
}

quick note -

  • this.props.style doesn't have to be a proper style object, can be an object returned from any other other calls like style, merge, hover, etc (I call them 'rules'). shareable!
  • no precedence issues, since the styles have unique [data-css-<hash>] 'scope'

@threepointone
Copy link

makeDecorator doesn't exist right now, but shouldn't be too hard to write.

@taion
Copy link
Author

taion commented Jul 31, 2016

@threepointone

Something like this came up in a different Twitter thread https://twitter.com/jimmy_jia/status/759605418389868544 😃

It works, and it's got the right semantics of where styles are defined.

I have to think about this more. It feels a lot like re-inventing CSS selectors using React context – like, written this way, it lets you write the equivalent of a .btn-toolbar .btn selector.

You could write something like a .btn-toolbar > .btn selector by mapping and cloning children and injecting e.g. buttonStyle as a prop instead of using context.

And you could use something like mapping children or like @vjeux's approach to get stuff equivalent to :nth-child selectors.

I don't know. It feels like these concepts should be more unified. Doing something like using context for .parent .child, cloning with props for .parent > .child feels less than ideal. They're similar enough concepts that IMO the code should look similar too.

@taion
Copy link
Author

taion commented Jul 31, 2016

I can see how glamor can avoid the potential precedence issues with e.g. CSS modules, though. That's pretty cool.

@taion
Copy link
Author

taion commented Jul 31, 2016

Yup – so the question is something like... this lets you write .parent .child, but you'd need select() or something from @vjeux's https://gist.github.com/vjeux/8e27dd06c64dc566bfee705e1b19cb18 to handle e.g. .parent > :last-child.

To me, that feels like a bit of a disadvantage; .parent .child, .parent > .child, and .parent > .child:last-child feel like similar enough concepts that the syntax should also look similar.

@taion
Copy link
Author

taion commented Jul 31, 2016

Trying to move discussion to styled-components/spec#5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment