Last active
March 22, 2017 02:53
-
-
Save kaw2k/2ad5bf8e601efc6f94815a0ff97633ca to your computer and use it in GitHub Desktop.
This file contains hidden or 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
import * as React from 'react'; | |
import * as cx from 'classnames'; | |
import * as Router from 'react-router'; | |
import Icon, { IconOptions } from './icon'; | |
/* | |
We have a few things that are `clickable`: | |
- buttons | |
- internal links | |
- external links | |
Any `clickable` can apear like a button or a link, and all have to have optional | |
icons. Before I had button as the root component and links would extend the props of button | |
and re-create the functionality in its own component. This turned out to be a bad idea. | |
Instead, I want to unify them with a heavier base clasee, clickable, and have lighter | |
wrappers for `Button` and `Link`. | |
*/ | |
// ============================= | |
// CLICKABLE | |
// ============================= | |
interface IClickableProps { | |
// Functional props | |
// I tried making this prop required, but since TS doesn't have subtraction types | |
// there was no simple way to signify that this prop was fulfilled by the parent | |
component?: React.ReactChild; // IE: 'a' or 'button' or ReactRouter.Link | |
label?: React.ReactNode; // The text of the clickable element | |
iconName?: IconOptions; | |
iconPosition?: 'before' | 'after'; | |
// Style props | |
theme?: 'primary' | 'secondary' | 'green' | 'yellow' | 'orange' | 'white'; | |
flat?: boolean; | |
inline?: boolean; | |
flow?: 'horizontal' | 'vertical'; | |
// HTML props | |
onClick?: (e: React.MouseEvent<HTMLElement>) => void; | |
style?: React.CSSProperties; | |
tabIndex?: number; | |
// ...:sob:... | |
// I tried several routes to remove this. We need some way to pass additional | |
// props to this component. Since we accept `component` as a prop, we can conceivably | |
// have any number of additional props this component needs. I tried generics, it | |
// wouldn't work. I also tried union types, same deal. | |
[key: string]: any; | |
} | |
const Clickable: React.SFC<IClickableProps> = ({ | |
component = 'button', | |
theme, | |
iconName, | |
iconPosition = 'after', | |
flat = false, | |
inline = false, | |
className = '', | |
flow = 'horizontal', | |
label, | |
children, | |
...props | |
}) => { | |
const icon = iconName && <Icon iconName={iconName} className={iconPosition} />; | |
return React.createElement(component as any, { | |
...props, | |
className: cx(`clickable clickable-theme--${theme} clickable-flow--${flow}`, className, { | |
flat, | |
inline, | |
'icon-only': !label && !children, | |
}), | |
}, [ | |
<div className="clickable-body"> | |
{iconPosition === 'before' && icon} | |
{label && <span className="clickable-label">{label}</span>} | |
{children} | |
{iconPosition === 'after' && icon} | |
</div> | |
]); | |
}; | |
// ============================= | |
// BUTTON | |
// ============================= | |
interface IButtonProps extends IClickableProps { | |
type?: 'submit' | 'button'; | |
disabled?: boolean; | |
} | |
const Button: React.SFC<IButtonProps> = ({ className='', ...props }) => | |
<Clickable {...props} component="button" className={cx('button', className)} />; | |
// ============================= | |
// LINK | |
// ============================= | |
interface ILinkProps extends IClickableProps { | |
external?: boolean; | |
to: string; | |
target?: string; | |
activeClassName?: string; | |
title?: string; | |
} | |
const Link: React.SFC<ILinkProps> = ({ | |
external, | |
to, | |
className = '', | |
activeClassName, | |
...props | |
}) => | |
external | |
? <Clickable target="_blank" | |
{...props} | |
component="a" | |
className={cx('link', className)} | |
href={to} /> | |
: <Clickable target="_blank" | |
{...props} | |
component={Router.Link as any} | |
activeClassName={activeClassName} | |
className={cx('link', className)} | |
to={to} />; | |
Yea... consistent design is another fight all together 😂. Each of the theme
props were one-offs until we standardized them. I can dig dropping label
and favoring children. I had it that way before and sort of liked how label
worked, but really don't care. children
seem like the way to go.
inline
and flow
are both props I just thought of when I was making up the interface. We will see if they are useful in practice.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I think this is a good approach if you want to keep your button/links consistent throughout the app. Of course, this comes at the cost of adding potential nastyness when something breaks conventions in the design.
I feel like
IClickableProps
'slabel
property should be dropped in favorchildren
. Seems more natural to just pass that along as the text. It also allows for any special styling as needed.Some things confuse me like the
inline
andflow
props. Doesn't seem to follow my line of thinking and I feel like they should be in combined?