Skip to content

Instantly share code, notes, and snippets.

@romannurik
Created August 23, 2018 15:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save romannurik/f98fdbfbae64e856120635df1bf4ac3f to your computer and use it in GitHub Desktop.
Save romannurik/f98fdbfbae64e856120635df1bf4ac3f to your computer and use it in GitHub Desktop.
A proof-of-concept Tab widget for Framer X
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