Created
August 23, 2018 15:51
-
-
Save romannurik/f98fdbfbae64e856120635df1bf4ac3f to your computer and use it in GitHub Desktop.
A proof-of-concept Tab widget for Framer X
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 { ControlType, PropertyControls } from "framer"; | |
import * as React from "react"; | |
interface TabWidgetProps { | |
selectedTab: number; | |
accentColor: string; | |
} | |
const ZERO_STATE_STYLE: React.CSSProperties = { | |
display: 'flex', | |
alignItems: 'center', | |
textAlign: 'center', | |
justifyContent: 'center', | |
fontSize: 18, | |
fontWeight: 'bold', | |
lineHeight: '24px', | |
padding: 16, | |
backgroundColor: '#888', | |
color: '#fff', | |
}; | |
/** | |
* A component that draws a tab strip and the contents of the selected Tab. | |
*/ | |
export class TabWidget extends React.Component<TabWidgetProps> { | |
static defaultProps = { | |
selectedTab: 0, | |
accentColor: '#039be5', | |
}; | |
static propertyControls: PropertyControls = { | |
selectedTab: { | |
type: ControlType.Number, | |
title: 'Selected tab' | |
}, | |
accentColor: { | |
type: ControlType.Color, | |
title: 'Accent color' | |
}, | |
}; | |
selectTab(index) { | |
this.setState({ selectedTab: index }); | |
} | |
render() { | |
let zeroStateStyle: React.CSSProperties = { | |
...ZERO_STATE_STYLE, | |
backgroundColor: this.props.accentColor, | |
width: this.props.width, | |
height: this.props.height, | |
}; | |
if (!this.props.children.length) { | |
return <div style={zeroStateStyle}>Add a frame to get started</div>; | |
} | |
let tabs = []; | |
React.Children.forEach(this.props.children[0].props.children, (element: React.ReactChild) => { | |
let actualChild = element.props.children[0]; | |
tabs.push({ | |
title: actualChild.props.title, | |
content: actualChild, | |
}); | |
}); | |
if (!tabs.length) { | |
return <div style={zeroStateStyle}>Add a Tab component to the frame</div>; | |
} | |
let selectedTabIndex = ('selectedTab' in (this.state || {})) | |
? this.state.selectedTab | |
: this.props.selectedTab; | |
selectedTabIndex = Math.max(0, Math.min(tabs.length - 1, selectedTabIndex)); | |
let selectedTabContent = tabs[selectedTabIndex].content; | |
let selectedTabStyles: React.CSSProperties = { | |
color: this.props.accentColor, | |
boxShadow: `0 -2px 0 ${this.props.accentColor} inset`, | |
}; | |
return <div style={{ | |
display: 'flex', | |
flexDirection: 'column', | |
width: this.props.width, | |
height: this.props.height, | |
}}> | |
<div className="tab-indicators" style={{ | |
display: 'flex', | |
flexDirection: 'row', | |
flex: '0 0 auto', | |
borderBottom: '1px solid #eee', | |
}}> | |
{tabs.map(({title}, index) => | |
<div | |
onClick={ ev => this.selectTab(index) } | |
style={{ | |
padding: '0px 16px', | |
boxSizing: 'border-box', | |
display: 'flex', | |
fontFamily: 'Roboto', | |
fontSize: 14, | |
lineHeight: '20px', | |
fontWeight: 500, | |
alignItems: 'center', | |
color: 'rgba(0, 0, 0, .54)', | |
height: 40, | |
...((index == selectedTabIndex) ? selectedTabStyles : {}) | |
}}> | |
{title} | |
</div>)} | |
</div> | |
{React.cloneElement(selectedTabContent, { | |
style: { | |
position: 'relative', | |
flex: '1 1 0', | |
overflow: 'hidden', | |
} | |
})} | |
</div>; | |
} | |
} | |
interface TabProps { | |
title: string; | |
} | |
/** | |
* A single tab in a TabWidget. | |
*/ | |
export class Tab extends React.Component<TabProps> { | |
static defaultProps = { | |
title: 'Tab', | |
width: 120, | |
height: 60, | |
}; | |
static propertyControls: PropertyControls = { | |
title: { | |
type: ControlType.String, | |
title: 'Title', | |
}, | |
}; | |
render() { | |
let zeroStateStyle: React.CSSProperties = { | |
...ZERO_STATE_STYLE, | |
width: '100%', | |
height: '100%', | |
fontSize: 12, | |
lineHeight: '16px', | |
}; | |
if (!this.props.children.length) { | |
return <div style={zeroStateStyle}>Add a frame for this tab</div>; | |
} | |
return <div style={{ | |
overflow: 'hidden', | |
width: '100%', | |
height: '100%', | |
display: 'flex', | |
justifyContent: 'stretch', | |
}}>{React.Children.map(this.props.children, el => { | |
return React.cloneElement(el, { | |
style: { | |
position: 'relative', | |
flex: '1 1 auto', | |
} | |
}); | |
})}</div>; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment