{}
+
+ export default TabSet
+}
diff --git a/src/index.mjs b/src/index.mjs
new file mode 100644
index 00000000..9ebc0ee4
--- /dev/null
+++ b/src/index.mjs
@@ -0,0 +1 @@
+export { default } from './tab-set/index.cjs'
diff --git a/src/index.ts b/src/index.ts
deleted file mode 100644
index 217d2284..00000000
--- a/src/index.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import isTabGroup from './tab-set/is-tab-group'
-import isTab from './tab-set/tab-group/is-tab'
-import isTabPanel from './tab-set/is-tab-panel'
-
-import TabGroup from './tab-set/tab-group'
-import Tab from './tab-set/tab-group/tab'
-import TabPanel from './tab-set/tab-panel'
-
-import TabSet from './tab-set'
-
-export {
- isTabGroup,
- isTab,
- isTabPanel
-}
-
-export {
- TabGroup,
- Tab,
- TabPanel
-}
-
-export default TabSet
diff --git a/src/tab-set/index.cjs b/src/tab-set/index.cjs
new file mode 100644
index 00000000..d3d98c54
--- /dev/null
+++ b/src/tab-set/index.cjs
@@ -0,0 +1,11 @@
+require('@babel/register')({
+ ignore: [
+ /node_modules\/(?!react-tab-set)/
+ ]
+})
+
+const {
+ default: component
+} = require('./index.jsx')
+
+module.exports = component
diff --git a/src/tab-set/index.jsx b/src/tab-set/index.jsx
new file mode 100644
index 00000000..7c95c92f
--- /dev/null
+++ b/src/tab-set/index.jsx
@@ -0,0 +1,121 @@
+import React, {
+ Children,
+ cloneElement,
+ useState,
+ useEffect
+} from 'react'
+import PropTypes from 'prop-types'
+import {
+ v4
+} from 'uuid'
+import debug from 'debug'
+
+import isTabPanel from './is-tab-panel.mjs'
+import isTabGroup from './is-tab-group.mjs'
+
+const error = debug('react-tab-set/tab-set')
+
+function mapChildren (
+ children,
+ selectedTab,
+ onTabSelect
+) {
+ return Children.map(children, (child) => {
+ const { type } = child
+
+ if (type) { // eslint-disable-line @typescript-eslint/strict-boolean-expressions
+ const { props } = child
+
+ if (isTabGroup(type)) {
+ return cloneElement(
+ child,
+ {
+ ...props,
+ selectedTab,
+ onTabSelect
+ }
+ )
+ }
+
+ if (isTabPanel(type)) {
+ return cloneElement(
+ child,
+ {
+ ...props,
+ selectedTab
+ }
+ )
+ }
+
+ const {
+ children
+ } = props
+
+ if (children) { // eslint-disable-line @typescript-eslint/strict-boolean-expressions
+ return cloneElement(
+ child,
+ {
+ ...props,
+ children: mapChildren(children, selectedTab, onTabSelect)
+ }
+ )
+ }
+ }
+
+ return child
+ })
+}
+
+function DEFAULT_CHANGE () {
+ //
+}
+
+export default function TabSet (props) {
+ const {
+ children,
+ selectedTab: tab = v4()
+ } = props
+
+ const [
+ selectedTab,
+ setSelectedTab
+ ] = useState(tab)
+
+ useEffect(() => { setSelectedTab(tab) }, [
+ tab
+ ])
+
+ useEffect(() => {
+ const {
+ onChange = DEFAULT_CHANGE
+ } = props
+
+ try {
+ onChange(selectedTab)
+ } catch {
+ error('Error `onChange`')
+ }
+ }, [
+ selectedTab
+ ])
+
+ return (
+
+ {mapChildren(children, selectedTab, setSelectedTab)}
+
+ )
+}
+
+TabSet.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.node,
+ PropTypes.arrayOf(PropTypes.node)
+ ]).isRequired,
+ selectedTab: PropTypes.string,
+ onChange: PropTypes.func
+}
+
+TabSet.defaultProps = {
+ selectedTab: v4(),
+ onChange () { }
+}
diff --git a/src/tab-set/index.tsx b/src/tab-set/index.tsx
deleted file mode 100644
index 3a8cbbb1..00000000
--- a/src/tab-set/index.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import React, { Component, Children, cloneElement } from 'react'
-import {
- v4
-} from 'uuid'
-
-import isTabPanel from './is-tab-panel'
-import isTabGroup from './is-tab-group'
-
-export interface TabSetProps {
- onChange: (selectedTab: string) => void
- selectedTab: string
- children: JSX.Element | JSX.Element[]
-}
-
-export default class TabSet extends Component {
- /*
- * The selected tab default does not have to be a uuid, but a uuid
- * reduces the likelihood that this default has the same value as
- * an implemented tab
- */
- static defaultProps = {
- onChange: () => {},
- selectedTab: v4(),
- children: []
- }
-
- shouldComponentUpdate (props: TabSetProps): boolean {
- return (
- props.children !== this.props.children ||
- props.selectedTab !== this.props.selectedTab
- )
- }
-
- handleTabSelect = (selectedTab: string): void => {
- if (selectedTab !== this.props.selectedTab) {
- const { onChange } = this.props
-
- this.setState({ selectedTab })
-
- onChange(selectedTab)
- }
- }
-
- mapChildren (children: JSX.Element | JSX.Element[], selectedTab: string): JSX.Element[] {
- return Children.map(children, (child) => {
- const { type } = child
-
- if (type) {
- const { props } = child
-
- if (isTabGroup(type)) {
- return cloneElement(
- child,
- {
- ...props,
- selectedTab,
- onTabSelect: this.handleTabSelect
- }
- )
- }
-
- if (isTabPanel(type)) {
- return cloneElement(
- child,
- {
- ...props,
- selectedTab
- }
- )
- }
-
- const {
- children
- } = props
-
- if (children) {
- return cloneElement(
- child,
- {
- ...props,
- children: this.mapChildren(children, selectedTab)
- }
- )
- }
- }
-
- return child
- })
- }
-
- getChildren (): JSX.Element[] {
- const {
- children,
- selectedTab
- } = this.props
-
- return this.mapChildren(children, selectedTab)
- }
-
- render (): JSX.Element {
- return (
-
- {this.getChildren()}
-
- )
- }
-}
diff --git a/src/tab-set/is-tab-group.mjs b/src/tab-set/is-tab-group.mjs
new file mode 100644
index 00000000..7062b409
--- /dev/null
+++ b/src/tab-set/is-tab-group.mjs
@@ -0,0 +1,5 @@
+import TabGroup from './tab-group/index.cjs'
+
+export default function isTabGroup (component) {
+ return component === TabGroup
+}
diff --git a/src/tab-set/is-tab-group.ts b/src/tab-set/is-tab-group.ts
deleted file mode 100644
index c9e7113c..00000000
--- a/src/tab-set/is-tab-group.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import TabGroup from './tab-group'
-
-const isTabGroup = (c: any): c is TabGroup => c === TabGroup
-
-export default isTabGroup
diff --git a/src/tab-set/is-tab-panel.mjs b/src/tab-set/is-tab-panel.mjs
new file mode 100644
index 00000000..e5e5ea40
--- /dev/null
+++ b/src/tab-set/is-tab-panel.mjs
@@ -0,0 +1,5 @@
+import TabPanel from './tab-panel/index.cjs'
+
+export default function isTabPanel (component) {
+ return component === TabPanel
+}
diff --git a/src/tab-set/is-tab-panel.ts b/src/tab-set/is-tab-panel.ts
deleted file mode 100644
index edec984d..00000000
--- a/src/tab-set/is-tab-panel.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import TabPanel from './tab-panel'
-
-const isTabPanel = (c: any): c is TabPanel => c === TabPanel
-
-export default isTabPanel
diff --git a/src/tab-set/tab-group/index.cjs b/src/tab-set/tab-group/index.cjs
new file mode 100644
index 00000000..d3d98c54
--- /dev/null
+++ b/src/tab-set/tab-group/index.cjs
@@ -0,0 +1,11 @@
+require('@babel/register')({
+ ignore: [
+ /node_modules\/(?!react-tab-set)/
+ ]
+})
+
+const {
+ default: component
+} = require('./index.jsx')
+
+module.exports = component
diff --git a/src/tab-set/tab-group/index.jsx b/src/tab-set/tab-group/index.jsx
new file mode 100644
index 00000000..5ae22a6f
--- /dev/null
+++ b/src/tab-set/tab-group/index.jsx
@@ -0,0 +1,78 @@
+import React, {
+ Children,
+ cloneElement
+} from 'react'
+import PropTypes from 'prop-types'
+import {
+ v4
+} from 'uuid'
+
+import isTab from './is-tab.mjs'
+
+function mapChildren (
+ children,
+ selectedTab,
+ onTabSelect
+) {
+ return Children.map(children, (child) => {
+ const { type } = child
+
+ if (type) { // eslint-disable-line @typescript-eslint/strict-boolean-expressions
+ const { props } = child
+
+ if (isTab(type)) {
+ return cloneElement(
+ child,
+ {
+ ...props,
+ selectedTab,
+ onTabSelect
+ }
+ )
+ }
+
+ const {
+ children
+ } = props
+
+ if (children) { // eslint-disable-line @typescript-eslint/strict-boolean-expressions
+ return cloneElement(
+ child,
+ {
+ ...props,
+ children: mapChildren(children, selectedTab, onTabSelect)
+ }
+ )
+ }
+ }
+
+ return child
+ })
+}
+
+function DEFAULT_SELECT () {
+ //
+}
+
+export default function TabGroup (props) {
+ const {
+ children,
+ selectedTab = v4(),
+ onTabSelect = DEFAULT_SELECT
+ } = props
+
+ return (
+
+ {mapChildren(children, selectedTab, onTabSelect)}
+
+ )
+}
+
+TabGroup.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.node,
+ PropTypes.arrayOf(PropTypes.node)
+ ]).isRequired,
+ selectedTab: PropTypes.string,
+ onTabSelect: PropTypes.func
+}
diff --git a/src/tab-set/tab-group/index.tsx b/src/tab-set/tab-group/index.tsx
deleted file mode 100644
index 043c9fff..00000000
--- a/src/tab-set/tab-group/index.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import React, { Component, Children, cloneElement } from 'react'
-import {
- v4
-} from 'uuid'
-
-import isTab from './is-tab'
-
-interface TabGroupProps {
- onTabSelect: (selectedTab: string) => void
- children: JSX.Element | JSX.Element[]
- selectedTab: string
-}
-
-export default class TabGroup extends Component {
- /*
- * The selected tab default does not have to be a uuid, but a uuid
- * reduces the likelihood that this default has the same value as
- * an implemented tab
- */
- static defaultProps = {
- onTabSelect: () => {},
- selectedTab: v4(),
- children: []
- }
-
- shouldComponentUpdate (props: TabGroupProps): boolean {
- return (
- props.children !== this.props.children ||
- props.selectedTab !== this.props.selectedTab
- )
- }
-
- handleTabClick = (tab: string): void => {
- const { onTabSelect } = this.props
-
- onTabSelect(tab)
- }
-
- mapChildren (children: JSX.Element | JSX.Element[], selectedTab: string): JSX.Element[] {
- return Children.map(children, (child) => {
- const { type } = child
-
- if (type) {
- const { props } = child
-
- if (isTab(type)) {
- return cloneElement(
- child,
- {
- ...props,
- selectedTab,
- onTabClick: this.handleTabClick
- }
- )
- }
-
- const {
- children
- } = props
-
- if (children) {
- return cloneElement(
- child,
- {
- ...props,
- children: this.mapChildren(children, selectedTab)
- }
- )
- }
- }
-
- return child
- })
- }
-
- getChildren (): JSX.Element[] {
- const {
- children,
- selectedTab
- } = this.props
-
- return this.mapChildren(children, selectedTab)
- }
-
- render (): JSX.Element {
- return (
-
- )
- }
-}
diff --git a/src/tab-set/tab-group/is-tab.mjs b/src/tab-set/tab-group/is-tab.mjs
new file mode 100644
index 00000000..2f009832
--- /dev/null
+++ b/src/tab-set/tab-group/is-tab.mjs
@@ -0,0 +1,5 @@
+import Tab from './tab/index.cjs'
+
+export default function isTab (component) {
+ return component === Tab
+}
diff --git a/src/tab-set/tab-group/is-tab.ts b/src/tab-set/tab-group/is-tab.ts
deleted file mode 100644
index c5c27e6d..00000000
--- a/src/tab-set/tab-group/is-tab.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import Tab from './tab'
-
-const isTab = (c: any): c is Tab => c === Tab
-
-export default isTab
diff --git a/src/tab-set/tab-group/tab/index.cjs b/src/tab-set/tab-group/tab/index.cjs
new file mode 100644
index 00000000..d3d98c54
--- /dev/null
+++ b/src/tab-set/tab-group/tab/index.cjs
@@ -0,0 +1,11 @@
+require('@babel/register')({
+ ignore: [
+ /node_modules\/(?!react-tab-set)/
+ ]
+})
+
+const {
+ default: component
+} = require('./index.jsx')
+
+module.exports = component
diff --git a/src/tab-set/tab-group/tab/index.jsx b/src/tab-set/tab-group/tab/index.jsx
new file mode 100644
index 00000000..87eb5283
--- /dev/null
+++ b/src/tab-set/tab-group/tab/index.jsx
@@ -0,0 +1,47 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import {
+ v4
+} from 'uuid'
+
+function DEFAULT_SELECT () {
+ //
+}
+
+export default function Tab (props) {
+ const {
+ children,
+ tab,
+ selectedTab = v4(),
+ onTabSelect = DEFAULT_SELECT
+ } = props
+
+ const className = (tab === selectedTab)
+ ? 'tab selected'
+ : 'tab'
+
+ return (
+ {
+ onTabSelect(tab)
+ }}>
+ {children}
+
+ )
+}
+
+Tab.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.node,
+ PropTypes.arrayOf(PropTypes.node)
+ ]).isRequired,
+ tab: PropTypes.string.isRequired,
+ selectedTab: PropTypes.string,
+ onTabSelect: PropTypes.func
+}
+
+Tab.defaultProps = {
+ selectedTab: v4(),
+ onTabSelect () { }
+}
diff --git a/src/tab-set/tab-group/tab/index.tsx b/src/tab-set/tab-group/tab/index.tsx
deleted file mode 100644
index fc5d4bf4..00000000
--- a/src/tab-set/tab-group/tab/index.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import React, { Component } from 'react'
-import {
- v4
-} from 'uuid'
-
-export interface TabProps {
- onTabClick: (tab: string) => void
- children: JSX.Element | JSX.Element[] | string | number | boolean
- tab: string
- selectedTab: string
-}
-
-export default class Tab extends Component {
- /*
- * The tab and selected tab defaults do not have to be a uuid, but a uuid
- * reduces the likelihood that this default has the same value as
- * an implemented tab
- */
- static defaultProps = {
- onTabClick: () => {},
- tab: v4(),
- selectedTab: v4(),
- children: []
- }
-
- shouldComponentUpdate (props: TabProps): boolean {
- return (
- props.children !== this.props.children ||
- props.tab !== this.props.tab ||
- props.selectedTab !== this.props.selectedTab
- )
- }
-
- handleClick = (): void => {
- const { tab, onTabClick } = this.props
-
- onTabClick(tab)
- }
-
- render (): JSX.Element {
- const {
- tab,
- selectedTab,
- children
- } = this.props
-
- const className = (tab === selectedTab)
- ? 'tab selected'
- : 'tab'
-
- return (
-
- {children}
-
- )
- }
-}
diff --git a/src/tab-set/tab-panel/index.cjs b/src/tab-set/tab-panel/index.cjs
new file mode 100644
index 00000000..d3d98c54
--- /dev/null
+++ b/src/tab-set/tab-panel/index.cjs
@@ -0,0 +1,11 @@
+require('@babel/register')({
+ ignore: [
+ /node_modules\/(?!react-tab-set)/
+ ]
+})
+
+const {
+ default: component
+} = require('./index.jsx')
+
+module.exports = component
diff --git a/src/tab-set/tab-panel/index.jsx b/src/tab-set/tab-panel/index.jsx
new file mode 100644
index 00000000..57aa59bf
--- /dev/null
+++ b/src/tab-set/tab-panel/index.jsx
@@ -0,0 +1,43 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import {
+ v4
+} from 'uuid'
+
+function DEFAULT_RENDER () {
+ return null
+}
+
+export default function TabPanel (props) {
+ const {
+ tab,
+ selectedTab = v4()
+ } = props
+
+ if (tab === selectedTab) {
+ const {
+ render = DEFAULT_RENDER,
+ children = render()
+ } = props
+
+ if (children) { // eslint-disable-line @typescript-eslint/strict-boolean-expressions
+ return (
+
+ {children}
+
+ )
+ }
+ }
+
+ return null
+}
+
+TabPanel.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.node,
+ PropTypes.arrayOf(PropTypes.node)
+ ]),
+ tab: PropTypes.string.isRequired,
+ selectedTab: PropTypes.string,
+ render: PropTypes.func
+}
diff --git a/src/tab-set/tab-panel/index.tsx b/src/tab-set/tab-panel/index.tsx
deleted file mode 100644
index 92de98e1..00000000
--- a/src/tab-set/tab-panel/index.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import React, { Component } from 'react'
-import {
- v4
-} from 'uuid'
-
-interface TabPanelProps {
- children: JSX.Element | JSX.Element[] | string | number | boolean | undefined | null
- tab: string
- selectedTab: string
- render?: () => JSX.Element | JSX.Element[] | string | number | boolean | undefined | null
-}
-
-export default class TabPanel extends Component {
- /*
- * The tab and selected tab defaults do not have to be a uuid, but a uuid
- * reduces the likelihood that this default has the same value as
- * an implemented tab
- */
- static defaultProps = {
- tab: v4(),
- selectedTab: v4()
- }
-
- shouldComponentUpdate (props: TabPanelProps): boolean {
- if (props.render instanceof Function) return true
-
- return (
- props.children !== this.props.children ||
- props.tab !== this.props.tab ||
- props.selectedTab !== this.props.selectedTab
- )
- }
-
- renderPanel (): JSX.Element | null {
- const {
- render = () => null,
- children = render()
- } = this.props
-
- if (children) {
- return (
-
- {children}
-
- )
- }
-
- return null
- }
-
- render (): JSX.Element | null {
- const {
- tab,
- selectedTab
- } = this.props
-
- if (tab === selectedTab) {
- return this.renderPanel()
- }
-
- return null
- }
-}
diff --git a/stories/.babelrc b/stories/.babelrc
index 5269d1bf..02324be7 100644
--- a/stories/.babelrc
+++ b/stories/.babelrc
@@ -1,6 +1,5 @@
{
"presets": [
- "@babel/typescript",
[
"@babel/env",
{
@@ -21,26 +20,6 @@
"@babel/react"
],
"plugins": [
- "@babel/transform-typescript",
- "@babel/proposal-export-default-from",
- "@babel/proposal-export-namespace-from",
- [
- "@babel/proposal-class-properties",
- {
- "loose": false
- }
- ],
- [
- "module-resolver",
- {
- "root": [
- ".."
- ],
- "cwd": "babelrc",
- "alias": {
- "react-tab-set": "../src"
- }
- }
- ]
+ "@babel/syntax-jsx"
]
}
diff --git a/stories/.eslintrc b/stories/.eslintrc
index 2fa91e74..ca5a63ff 100644
--- a/stories/.eslintrc
+++ b/stories/.eslintrc
@@ -1,23 +1,35 @@
{
"extends": [
- "standard-with-typescript",
- "plugin:react/recommended"
+ "standard",
+ "plugin:react/recommended",
+ "plugin:storybook/recommended"
],
- "parser": "@typescript-eslint/parser",
+ "parser": "@babel/eslint-parser",
"parserOptions": {
- "project": "tsconfig.json",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": [
- "@typescript-eslint/eslint-plugin",
"react"
],
"rules": {
- "no-use-before-define": "off",
- "@typescript-eslint/no-use-before-define": [
- "off"
+ "react/display-name": "off",
+ "quotes": [
+ "error",
+ "single"
+ ],
+ "jsx-quotes": [
+ "error",
+ "prefer-single"
+ ],
+ "react/jsx-indent": [
+ "error",
+ 2,
+ {
+ "checkAttributes": true,
+ "indentLogicalExpressions": true
+ }
]
},
"settings": {
diff --git a/stories/TabSet.stories.jsx b/stories/TabSet.stories.jsx
new file mode 100644
index 00000000..dc6fb194
--- /dev/null
+++ b/stories/TabSet.stories.jsx
@@ -0,0 +1,46 @@
+import React from 'react'
+import { action } from '@storybook/addon-actions'
+
+import TabSet from 'react-tab-set'
+import TabGroup from 'react-tab-set/tab-set/tab-group'
+import Tab from 'react-tab-set/tab-set/tab-group/tab'
+import TabPanel from 'react-tab-set/tab-set/tab-panel'
+
+function Component (props) {
+ return ( // eslint-disable-line react/prop-types
+
+
+
+ Tab One
+
+
+ Tab Two
+
+
+
+ Tab Panel One
+
+
+ Tab Panel Two
+
+
+ )
+}
+
+export default {
+ title: 'Components/TabSet',
+ component: Component,
+ argTypes: {
+ selectedTab: {
+ options: ['one', 'two'],
+ control: { type: 'radio' },
+ description: 'selectedTab'
+ }
+ }
+}
+
+export const ComponentStory = {
+ args: {
+ selectedTab: 'one'
+ }
+}
diff --git a/stories/TabSet.stories.tsx b/stories/TabSet.stories.tsx
deleted file mode 100644
index dc2e412d..00000000
--- a/stories/TabSet.stories.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react'
-import { action } from '@storybook/addon-actions'
-import { withKnobs, select, text } from '@storybook/addon-knobs'
-import type { Story, Meta } from '@storybook/react'
-import type { TabSetProps } from 'react-tab-set/tab-set'
-import TabSet from 'react-tab-set/tab-set'
-import {
- TabGroup,
- Tab,
- TabPanel
-} from 'react-tab-set'
-
-const Template: Story = ({ onChange }) => ( // eslint-disable-line react/prop-types
-
-
-
- {text('Tab One', 'One')}
-
-
- {text('Tab Two', 'Two')}
-
-
-
- {text('Tab Panel One', 'One')}
-
-
- {text('Tab Panel Two', 'Two')}
-
-
-)
-
-export const Component = Template.bind({})
-Component.args = {
- onChange: action('onChange')
-}
-
-export default { // eslint-disable-line @typescript-eslint/consistent-type-assertions
- title: 'TabSet/TabSet',
- component: Component,
- decorators: [withKnobs],
- template: Template
-} as Meta
diff --git a/tsconfig.json b/tsconfig.json
index 98c9e9ca..5032516c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,27 +1,15 @@
{
"compilerOptions": {
"jsx": "react",
- "module": "es2020",
- "target": "es2020",
- "moduleResolution": "node",
+ "module": "NodeNext",
+ "target": "ESNext",
+ "moduleResolution": "NodeNext",
"noEmit": true,
"strict": true,
"isolatedModules": true,
- "noImplicitAny": true,
- "allowSyntheticDefaultImports": true,
- "allowImportingTsExtensions": true,
- "baseUrl": ".",
- "paths": {
- "react-tab-set": [
- "src"
- ],
- "react-tab-set/*": [
- "src/*"
- ]
- }
+ "baseUrl": "."
},
"include": [
- "src/**/*",
- "stories/**/*"
+ "src/*"
]
}