node package manager

macos-tabs

MacOS Tabs

npm version

MacOS Tabs

Installation

npm install --save macos-tabs

or

yarn add macos-tabs

Usage

API

MacOSTabs

type Props = {
    // onClick event when the user clicks on the AddTabButton on a tab header 
    onAddTabButtonClick: (e: Event) => void,
 
    // onClick event when the user clicks on the CloseTabButton on a tab header 
    onCloseTabButtonClick: (e: Event, index: number) => void,
 
    // Event when the user stops dragging a header 
    // The updated tabs contain the new ordering, you can directly update your state with 
    // these returned tabs 
    // The updated index of the active tab is also provided 
    onDragStop: (e: Event, index: number, tabs: Tabs) => void,
 
    // Event when the user clicks on a tab header 
    // The index of the clicked header is passed back 
    onTabClick: (e: Event, index: number) => void,
 
    // Event when the user's mouse enters a tab header 
    onTabMouseEnter: (e: Event, index: number) => void,
 
    // Even when the user's mouse leaves a tab header 
    onTabMouseLeave: (e: Event, index: number) => void,
 
    // Event when the activeTabIndex is updated internally 
    // This allows you to keep track of the activeTabIndex even 
    // if you are letting MacOSTabs handle the state 
    onSetActiveTab: (index: number) => void,
 
    // Specify the position of addTabButton 
    addTabPosition: 'none' | 'start' | 'end',
 
    // Declare whether or not you want to manage the activeTabIndex 
    autoActiveTab: boolean,
 
    // Can be used to set initial active tab index 
    // If autoActiveTab is false, then you should have methods 
    // to manage the activeTabIndex state 
    activeTabIndex: number,
 
    // Tab content should be wrapped in a TabBody component with the correct props 
    // Tabs order can be modified programmatically by manually changing the order 
    // of the array 
    tabs: Tabs,
 
    // Declare a div or react component to render when there are no tabs open 
    defaultContent: Object,
 
    // Flag to show or hide tab headers 
    showHeader: boolean,
 
    // Declare the header height in px (note: styles have not been tested with 
    // anything besides the default of 24px, this is a WIP) 
    headerHeight: number | string,
 
    // Declare a custom element that the body should be rendered into 
    // instead of directly below the tab headers 
    // i.e. <div id="tabBody" /> 
    customBodyElementId: string,
 
    // Set tab scroll behavior 
    scrollX: 'normal' | 'reversed' | 'disabled',
 
    // Set tab scroll behavior 
    scrollY: 'normal' | 'reversed' | 'disabled',
 
    // Apply custom styles to specified component(s) 
    styles: {
        addTabButton?: Object,
        closeTabButton?: Object,
        tab?: Object,
        tabActive?: Object,
        outerTabContainer?: Object,
        innerTabContainer?: Object
    },
 
    // Apply custom classnames to specified component(s) 
    classNames: {
        addTabButton?: string,
        closeTabButton?: string,
        tab?: string,
        tabActive?: string,
        outerTabContainer?: string,
        innerTabContainer?: string
    },
 
    // Override icons on buttons 
    icons: {
        addTabButton?: Object | string,
        closeTabButton?: Object | string
    },
 
    // Experimental/Not Completed 
    onDragOut: (e: Event, outTabIndex: number) => void,
    dragOutDistance: number
}
 
static defaultProps = {
    tabs: [],
    activeTabIndex: 0,
    scrollX: 'normal',
    scrollY: 'disabled',
    addTabPosition: 'end',
    showHeader: true,
    headerHeight: 24,
    dragOutDistance: 40,
    autoActiveTab: true,
    styles: {},
    classNames: {},
    icons: {}
}

TabBody

type Props = {
    // Label that will be displayed on the tab header 
    label: string | number,
 
    // Children components to render 
    children?: HTMLElement | Component<*, *, *>,
 
    // Unique tabId 
    tabId: string | number,
 
    // Optional styles/classnames 
 
    // Set additional TabBody styles 
    // Note: In most cases, setting styles on the child component is sufficient 
    style?: Object,
 
    // Set TabBody classname 
    className?: string
}
 
static defaultProps = {
    label: ''
}

Minimal Example

import React, { Component } from 'react'
import MacOSTabs, { TabBody } from 'macos-tabs'
 
const defaultStyles = {
    height: '100%'
}
 
export default class Home extends Component {
    constructor(props) {
        super(props)
 
        this.id = 0
 
        this.state = {
            tabs: [
                this.makeTab(this.id++),
                this.makeTab(this.id++),
                this.makeTab(this.id++)
            ]
        }
    }
 
    makeTab(id) {
        return (
            <TabBody label={ `Test Tab ${ id }` } tabId={ id } key={ id }>
                <div
                    style={{
                        height: '100%',
                        border: '1px solid red',
                        textAlign: 'center',
                        boxSizing: 'border-box',
                        paddingTop: '20%'
                    }}
                >
                    Hello { id }!
                </div>
            </TabBody>
        )
    }
 
    onDragStop(e, activeTabIndex, tabs) {
        this.setState({
            tabs
        })
    }
 
    onAddTabButtonClick(e) {
        const newTabs = this.state.tabs.slice(0)
 
        newTabs.push(this.makeTab(this.id++))
 
        this.setState({
            tabs: newTabs
        })
    }
 
    onCloseTabButtonClick(e, closedTabIndex) {
        const newTabs = this.state.tabs.slice(0)
 
        newTabs.splice(closedTabIndex, 1)
 
        this.setState({
            tabs: newTabs
        })
    }
 
    render() {
        return (
            <div style={ defaultStyles }>
                <MacOSTabs
                    tabs={ this.state.tabs }
                    onDragStop={ this.onDragStop.bind(this) }
                    onAddTabButtonClick={ this.onAddTabButtonClick.bind(this) }
                    onCloseTabButtonClick={ this.onCloseTabButtonClick.bind(this) }
                />
            </div>
        )
    }
}

Controlled Active Tab & CustomBodyElementID Example

import React, { Component } from 'react'
import MacOSTabs, { TabBody } from 'macos-tabs'
 
const defaultStyles = {
    height: '100%'
}
 
export default class Home extends Component {
    constructor(props) {
        super(props)
 
        this.id = 0
 
        this.state = {
            tabs: [
                this.makeTab(this.id++),
                this.makeTab(this.id++),
                this.makeTab(this.id++)
            ],
            activeTabIndex: 0
        }
    }
 
    makeTab(id) {
        return (
            <TabBody label={ `Test Tab ${ id }` } tabId={ id } key={ id }>
                <div
                    style={{
                        height: '100%',
                        border: '1px solid red',
                        textAlign: 'center',
                        boxSizing: 'border-box',
                        paddingTop: '20%'
                    }}
                >
                    <form>
                        <label>
                            Name { id }:
                            <input type="text" name="name" />
                        </label>
                        <input type="submit" value="Submit" />
                    </form>
                </div>
            </TabBody>
        )
    }
 
    onDragStop(e, activeTabIndex, tabs) {
        this.setState({
            tabs,
            activeTabIndex
        })
    }
 
    onAddTabButtonClick(e) {
        const newTabs = this.state.tabs.slice(0)
 
        newTabs.push(this.makeTab(this.id++))
 
        this.setState({
            tabs: newTabs,
            activeTabIndex: newTabs.length - 1
        })
    }
 
    onCloseTabButtonClick(e, closedTabIndex) {
        const newTabs = this.state.tabs.slice(0)
 
        newTabs.splice(closedTabIndex, 1)
 
        if (closedTabIndex <= this.state.activeTabIndex) {
            this.setState({
                tabs: newTabs,
                activeTabIndex: (this.state.activeTabIndex === 0) ? 0 : this.state.activeTabIndex - 1
            })
        } else {
            this.setState({
                tabs: newTabs
            })
        }
    }
 
    onSetActiveTab(index) {
        this.setState({
            activeTabIndex: index
        })
    }
 
    render() {
        return (
            <div style={ defaultStyles }>
                <MacOSTabs
                    tabs={ this.state.tabs }
                    onDragStop={ this.onDragStop.bind(this) }
                    onAddTabButtonClick={ this.onAddTabButtonClick.bind(this) }
                    onCloseTabButtonClick={ this.onCloseTabButtonClick.bind(this) }
                    onSetActiveTab={ this.onSetActiveTab.bind(this) }
                    activeTabIndex={ this.state.activeTabIndex }
                    defaultContent={ <div>Add a tab to get started!</div> }
                    customBodyElementId='custom1'
                />
                <div id='custom1' style={{ height: 'calc(100% - 24px)' }} />
            </div>
        )
    }
}