{"id":1486,"date":"2023-05-23T10:30:50","date_gmt":"2023-05-23T07:30:50","guid":{"rendered":"https:\/\/notes.profidev.online\/?p=1486"},"modified":"2023-05-22T10:36:48","modified_gmt":"2023-05-22T07:36:48","slug":"react-multi-tab-component","status":"publish","type":"post","link":"https:\/\/notes.profidev.online\/ru\/reactjs\/1486\/","title":{"rendered":"React | Multi Tab Component on class"},"content":{"rendered":"\n<div class=\"wp-block-media-text alignwide is-stacked-on-mobile\"><figure class=\"wp-block-media-text__media\"><img fetchpriority=\"high\" decoding=\"async\" width=\"663\" height=\"374\" src=\"https:\/\/notes.profidev.online\/wp-content\/uploads\/2023\/05\/peek-2023-05-22-09-31.gif\" alt=\"React | Multi Tab Component (Animated)\" class=\"wp-image-1487 size-full\"\/><\/figure><div class=\"wp-block-media-text__content\">\n<p class=\"wp-block-paragraph\"><\/p>\n<\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text\/html&quot;,&quot;theme&quot;:&quot;cobalt&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:true,&quot;readOnly&quot;:false,&quot;fileName&quot;:&quot;&quot;,&quot;TrpContentRestriction&quot;:{&quot;restriction_type&quot;:&quot;exclude&quot;,&quot;selected_languages&quot;:[],&quot;panel_open&quot;:true},&quot;language&quot;:&quot;HTML&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;html&quot;}\"> &lt;link rel=&quot;stylesheet&quot; href=&quot;https:\/\/use.fontawesome.com\/releases\/v5.0.6\/css\/all.css&quot;&gt;\n &lt;link rel=&quot;stylesheet&quot; href=&quot;https:\/\/codepen.io\/TheVVaFFle\/pen\/JpWrWw.css&quot;&gt;\n &lt;link href='https:\/\/fonts.googleapis.com\/css?family=Roboto:100,300,400,700' rel='stylesheet' type='text\/css'&gt;\n\n\n  &lt;div id=&quot;root&quot;&gt;&lt;\/div&gt;\n\n\n  &lt;script src=&quot;https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/react\/16.2.0\/umd\/react.development.js&quot;   &gt;&lt;\/script&gt; \n  &lt;script src=&quot;https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/react-dom\/16.2.0\/umd\/react-dom.development.js&quot;&gt;&lt;\/script&gt;<\/pre><\/div>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;css&quot;,&quot;mime&quot;:&quot;text\/css&quot;,&quot;theme&quot;:&quot;cobalt&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:true,&quot;readOnly&quot;:false,&quot;fileName&quot;:&quot;&quot;,&quot;TrpContentRestriction&quot;:{&quot;restriction_type&quot;:&quot;exclude&quot;,&quot;selected_languages&quot;:[],&quot;panel_open&quot;:true},&quot;language&quot;:&quot;CSS&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;css&quot;}\">html, body, #root, #app {\n\theight: 100%;\n\tmargin: 0px;\n\tpadding: 0px;\n\twidth: 100%;\n  }\n  \n  body {\n\toverflow-x: hidden;\n  }\n  \n  #app div, #app h1, #app h2, #app h3, #app h4, #app h5, #app h6, #app input, #app button, #app li {\n\tfont-family: &quot;Roboto&quot;, sans-serif;\n  }\n  #app #app-content {\n\tleft: 50%;\n\tposition: absolute;\n\ttop: 50%;\n\ttransform: translateX(-50%) translateY(-50%);\n  }\n  #app #app-content .my-tab-content {\n\theight: 300px;\n  }\n  #app #app-content .my-tab-content .tab-icon {\n\tleft: 50%;\n\tposition: absolute;\n\ttop: 50%;\n\ttransform: translateX(-50%) translateY(-50%);\n  }\n  #app #app-content .my-tab-content .tab-icon i {\n\tcolor: #f0f0f0;\n\tfont-size: 10em;\n  }\n  #app #app-content .my-tab-content .tab-icon h1 {\n\tcolor: #c8c8c8;\n\tfont-size: 0.9em;\n\tfont-weight: 100;\n\tmargin: 0px;\n\tmargin-top: 10px;\n\ttext-align: center;\n  }<\/pre><\/div>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;javascript&quot;,&quot;mime&quot;:&quot;text\/javascript&quot;,&quot;theme&quot;:&quot;cobalt&quot;,&quot;lineNumbers&quot;:true,&quot;styleActiveLine&quot;:true,&quot;lineWrapping&quot;:true,&quot;readOnly&quot;:false,&quot;fileName&quot;:&quot;&quot;,&quot;TrpContentRestriction&quot;:{&quot;restriction_type&quot;:&quot;exclude&quot;,&quot;selected_languages&quot;:[],&quot;panel_open&quot;:true},&quot;language&quot;:&quot;JavaScript&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;js&quot;}\">class Tabs extends React.PureComponent {\n  constructor(props) {\n    super(props);\n    this.getTabSize = this.getTabSize.bind(this);\n    this.getColors = this.getColors.bind(this);\n    this.getTabs = this.getTabs.bind(this);\n    this.getTabOptions = this.getTabOptions.bind(this);\n    this.setSelectedTab = this.setSelectedTab.bind(this);\n    this.createTabOptions = this.createTabOptions.bind(this);\n    this.updateTabBarStyles = this.updateTabBarStyles.bind(this);\n    this.state = {\n      selectedTab: 0,\n      transitionDuration: 400,\n      isTransitioning: false,\n      tabBarStyles: {} };\n  }\n  componentDidMount() {\n    const { selectedTab, transitionDuration } = this.state;\n    const tabWidth = this.getTabSize().width,\n    options = this.getTabOptions(),\n    tabOptionWidth = tabWidth \/ options.length,\n    left = `${tabOptionWidth * selectedTab}px`,\n    right = `${tabWidth - tabOptionWidth * (selectedTab + 1)}px`,\n    transition = `left ${transitionDuration}ms, right ${transitionDuration}ms, width ${transitionDuration \/ 2}ms`,\n    accentColor = this.getColors().accent;\n    this.updateTabBarStyles(left, right, transition, accentColor);\n\n    \/\/Added later for initial animation purposes\n    if (this.props.initialTab) {\n      setTimeout(() =&gt; this.setSelectedTab(this.props.initialTab), 250);\n    }\n  }\n  componentDidUpdate(prevProps, prevState) {\n    if (prevState.selectedTab !== this.state.selectedTab) {\n      const { selectedTab, transitionDuration, tabBarStyles } = this.state;\n      const tabWidth = this.getTabSize().width,\n      options = this.getTabOptions(),\n      tabOptionWidth = tabWidth \/ options.length,\n      direction = prevState.selectedTab &lt; selectedTab ? 1 : -1,\n      left = direction === -1 ? `${tabOptionWidth * selectedTab + tabOptionWidth \/ 2}px` : null,\n      right = direction === 1 ? `${tabWidth - tabOptionWidth * (selectedTab + 1) + tabOptionWidth \/ 2}px` : null,\n      nextLeft = `${tabOptionWidth * selectedTab}px`,\n      nextRight = `${tabWidth - tabOptionWidth * (selectedTab + 1)}px`;\n      this.setState({ isTransitioning: true });\n      this.updateTabBarStyles(left, right, null, null);\n      setTimeout(() =&gt; this.updateTabBarStyles(nextLeft, nextRight, null, null), transitionDuration \/ 2);\n      setTimeout(() =&gt; this.setState({ isTransitioning: false }), transitionDuration);\n    }\n  }\n  getTabSize() {\n    const { tabHeight, tabWidth } = this.props;\n    return {\n      height: tabHeight || 300,\n      width: tabWidth || 450 };\n\n  }\n  getColors() {\n    const { accentColor, backgroundColor } = this.props;\n    return {\n      accent: accentColor || '#03a9f4',\n      background: backgroundColor || 'white' };\n\n  }\n  setSelectedTab(index) {\n    index = Math.max(0, index);\n    index = Math.min(index, this.props.children.length - 1);\n    this.setState({ selectedTab: index });\n  }\n  getTabs() {\n    const { children } = this.props;\n    const { transitionDuration } = this.state;\n    const tabSize = this.getTabSize(),\n    moreProps = {\n      width: `${tabSize.width}px`,\n      transition: `opacity ${transitionDuration * 1.5}ms, transform ${transitionDuration \/ 2}ms` };\n\n    let tabs = !children ? new Array( \/*#__PURE__*\/React.createElement(Tab, { key: 0, label: 'Default' })) :\n    children.length ? children : new Array(children);\n    return tabs.map(tab =&gt; ({ ...tab, props: { ...tab.props, ...moreProps } }));\n  }\n  getTabOptions() {\n    const { children } = this.props;\n    let tabOptions = null;\n    if (!children) {\n      tabOptions = new Array( \/*#__PURE__*\/React.createElement(Tab, { key: 0, label: 'Default' }));\n    } else\n    {\n      tabOptions = children.length ? children : new Array(children);\n    }\n    return tabOptions.map(tab =&gt; tab.props.label);\n  }\n  createTabOptions() {\n    const { selectedTab } = this.state;\n    const tabWidth = this.getTabSize().width,\n    options = this.getTabOptions(),\n    tabOptionWidth = tabWidth \/ options.length;\n    return options.map((tab, index) =&gt; {\n      const selected = selectedTab === index,\n      textStyle = selected ? { color: this.getColors().accent } : {},\n      classes = `tab-option ${selected ? 'selected' : ''}`;\n      return \/*#__PURE__*\/(\n        React.createElement(&quot;div&quot;, {\n          key: index,\n          className: classes,\n          style: { width: `${tabOptionWidth}px` },\n          onClick: () =&gt; this.setSelectedTab(index) }, \/*#__PURE__*\/\n\n        React.createElement(&quot;h1&quot;, { style: textStyle }, tab)));\n    });\n  }\n  updateTabBarStyles(left, right, transition, backgroundColor) {\n    const { tabBarStyles } = this.state;\n    this.setState({\n      tabBarStyles: {\n        left: left || tabBarStyles.left,\n        right: right || tabBarStyles.right,\n        transition: transition || tabBarStyles.transition,\n        backgroundColor: backgroundColor || tabBarStyles.backgroundColor } });\n  }\n  render() {\n    const { customClass } = this.props;\n    const { tabBarStyles, selectedTab, transitionDuration, isTransitioning } = this.state;\n    const tabSize = this.getTabSize(),\n    colors = this.getColors(),\n    options = this.getTabOptions(),\n    tabWrapperClasses = `tab-wrapper ${isTransitioning ? 'transitioning' : ''} ${customClass ? customClass : ''}`,\n    tabWrapperStyles = { backgroundColor: colors.background, width: `${tabSize.width}px` },\n    tabsStyles = {\n      height: `${tabSize.height}px`,\n      width: `${tabSize.width * options.length}px`,\n      transform: `translateX(${-1 * selectedTab * tabSize.width}px)`,\n      transition: `transform ${transitionDuration}ms` },\n\n    tabs = this.getTabs();\n\n    return \/*#__PURE__*\/(\n      React.createElement(&quot;div&quot;, { className: tabWrapperClasses, style: tabWrapperStyles }, \/*#__PURE__*\/\n      React.createElement(&quot;div&quot;, { className: &quot;tab-selection&quot; }, \/*#__PURE__*\/\n      React.createElement(&quot;div&quot;, { className: &quot;tab-options&quot; },\n      this.createTabOptions()), \/*#__PURE__*\/\n\n      React.createElement(&quot;div&quot;, { className: &quot;tab-selection-bar-track&quot; }, \/*#__PURE__*\/\n      React.createElement(&quot;div&quot;, {\n        className: &quot;tab-selection-bar&quot;,\n        style: tabBarStyles }))), \/*#__PURE__*\/\n\t\tReact.createElement(&quot;div&quot;, { className: &quot;tabs&quot;, style: tabsStyles },\n      tabs)));\n}}\n\nclass Tab extends React.Component {\n  render() {\n    const { children, width, transition, customClass } = this.props;\n    const tabClasses = `tab ${customClass ? customClass : ''}`,\n    tabStyles = { width, transition },\n    tabContentStyles = { transition };\n    return \/*#__PURE__*\/(\n      React.createElement(&quot;div&quot;, { className: tabClasses, style: tabStyles }, \/*#__PURE__*\/\n      React.createElement(&quot;div&quot;, { className: &quot;tab-contents&quot;, style: tabContentStyles },\n      children)));\n}}\n\nclass App extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      accentColor: '#03a9f4',\n      backgroundColor: '#fff' };\n\n  }\n  getTabs() {\n    return [\n    { label: 'Home', icon: 'fas fa-home' },\n    { label: 'Trending', icon: 'fas fa-fire' },\n    { label: 'Inbox', icon: 'fas fa-inbox' },\n    { label: 'Profile', icon: 'far fa-user-circle' }].\n    map((tab, index) =&gt; \/*#__PURE__*\/\n    React.createElement(Tab, { key: index, customClass: 'my-custom-tab-class', label: tab.label }, \/*#__PURE__*\/\n    React.createElement(&quot;div&quot;, { className: &quot;my-tab-content&quot; }, \/*#__PURE__*\/\n    React.createElement(&quot;div&quot;, { className: &quot;tab-icon&quot; }, \/*#__PURE__*\/\n    React.createElement(&quot;i&quot;, { className: tab.icon }), \/*#__PURE__*\/\n    React.createElement(&quot;h1&quot;, null, &quot;The &quot;, tab.label, &quot; Tab&quot;)))));\n}\n  render() {\n    const { accentColor, backgroundColor } = this.state;\n    const tabs = this.getTabs();\n\n    return \/*#__PURE__*\/(\n      React.createElement(&quot;div&quot;, { id: &quot;app&quot;, style: { backgroundColor: accentColor } }, \/*#__PURE__*\/\n      React.createElement(&quot;div&quot;, { id: &quot;app-content&quot; }, \/*#__PURE__*\/\n      React.createElement(Tabs, {\n        customClass: 'my-custom-tab-wrapper-class',\n        tabHeight: 300,\n        tabWidth: 500,\n        backgroundColor: backgroundColor,\n        accentColor: accentColor,\n        initialTab: 2 },\n      tabs))));\n  }}\n\n\nReactDOM.render( \/*#__PURE__*\/React.createElement(App, null), document.getElementById('root'));<\/pre><\/div>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":1487,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[66],"tags":[71],"class_list":["post-1486","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-reactjs","tag-tab-bar"],"_links":{"self":[{"href":"https:\/\/notes.profidev.online\/ru\/wp-json\/wp\/v2\/posts\/1486","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/notes.profidev.online\/ru\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/notes.profidev.online\/ru\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/notes.profidev.online\/ru\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/notes.profidev.online\/ru\/wp-json\/wp\/v2\/comments?post=1486"}],"version-history":[{"count":2,"href":"https:\/\/notes.profidev.online\/ru\/wp-json\/wp\/v2\/posts\/1486\/revisions"}],"predecessor-version":[{"id":1489,"href":"https:\/\/notes.profidev.online\/ru\/wp-json\/wp\/v2\/posts\/1486\/revisions\/1489"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/notes.profidev.online\/ru\/wp-json\/wp\/v2\/media\/1487"}],"wp:attachment":[{"href":"https:\/\/notes.profidev.online\/ru\/wp-json\/wp\/v2\/media?parent=1486"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/notes.profidev.online\/ru\/wp-json\/wp\/v2\/categories?post=1486"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/notes.profidev.online\/ru\/wp-json\/wp\/v2\/tags?post=1486"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}