Skip to content

Commit

Permalink
Updated TabPanel to the latest react features
Browse files Browse the repository at this point in the history
  • Loading branch information
viktor-podzigun committed Aug 20, 2019
1 parent f424f13 commit 0209efd
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 116 deletions.
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
package scommons.client.showcase.demo

import io.github.shogowada.scalajs.reactjs.React
import io.github.shogowada.scalajs.reactjs.VirtualDOM._
import io.github.shogowada.scalajs.reactjs.classes.ReactClass
import scommons.client.ui.Buttons
import scommons.client.ui.tab._
import scommons.react._
import scommons.react.hooks._

object TabPanelDemo {

def apply(): ReactClass = reactClass

private lazy val reactClass = React.createClass[Unit, Unit] { _ =>
val nestedItems = List(
TabItemData("Nested Tab 1", image = Some(Buttons.ADD.image), render = Some { _ =>
<.div()("Content for nested tab 1")
}),
TabItemData("Nested Tab 2", image = Some(Buttons.REMOVE.image), render = Some { _ =>
<.div()("Content for nested tab 2")
})
)

val nestedTabs = React.createClass[Unit, Unit] { _ =>
<(TabPanel())(^.wrapped := TabPanelProps(nestedItems))()
}
object TabPanelDemo extends FunctionComponent[Unit] {

private case class TabPanelDemoState(index1: Int = 0,
index2: Int = 1,
index3: Int = 2)

protected def render(props: Props): ReactElement = {
val (state, setState) = useStateUpdater(() => TabPanelDemoState())

val items = List(
TabItemData("First Tab", render = Some { _ =>
<.div()("Content for first tab")
}),
TabItemData("Second Tab", image = Some(Buttons.FIND.image), component = Some(nestedTabs)),
TabItemData("Second Tab", image = Some(Buttons.FIND.image), component = Some(nestedTabs())),
TabItemData("Third Tab", render = Some { _ =>
<.div()("Content for third tab")
})
Expand All @@ -38,16 +28,59 @@ object TabPanelDemo {
<.h2()("TabPanel"),
<.p()("Demonstrates tabs functionality."),
<.p()(
<(TabPanel())(^.wrapped := TabPanelProps(items))()
<(TabPanel())(^.wrapped := TabPanelProps(
items = items,
selectedIndex = state.index1,
onSelect = { (_, index) =>
setState(s => s.copy(index1 = index))
}
))()
),
<.h3()("Tabs on the left"),
<.p()(
<(TabPanel())(^.wrapped := TabPanelProps(items, selectedIndex = 1, direction = TabDirection.Left))()
<(TabPanel())(^.wrapped := TabPanelProps(
items = items,
selectedIndex = state.index2,
onSelect = { (_, index) =>
setState(s => s.copy(index2 = index))
},
direction = TabDirection.Left
))()
),
<.h3()("Tabs at the bottom"),
<.p()(
<(TabPanel())(^.wrapped := TabPanelProps(items, selectedIndex = 2, direction = TabDirection.Bottom))()
<(TabPanel())(^.wrapped := TabPanelProps(
items = items,
selectedIndex = state.index3,
onSelect = { (_, index) =>
setState(s => s.copy(index3 = index))
},
direction = TabDirection.Bottom
))()
)
)
}

private lazy val nestedItems = List(
TabItemData("Nested Tab 1", image = Some(Buttons.ADD.image), render = Some { _ =>
<.div()("Content for nested tab 1")
}),
TabItemData("Nested Tab 2", image = Some(Buttons.REMOVE.image), render = Some { _ =>
<.div()("Content for nested tab 2")
})
)

private lazy val nestedTabs = new FunctionComponent[Unit] {
protected def render(props: Props): ReactElement = {
val (selectedIndex, setSelectedIndex) = useState(0)

<(TabPanel())(^.wrapped := TabPanelProps(
items = nestedItems,
selectedIndex = selectedIndex,
onSelect = { (_, index) =>
setSelectedIndex(index)
}
))()
}
}
}
107 changes: 46 additions & 61 deletions ui/src/main/scala/scommons/client/ui/tab/TabPanel.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package scommons.client.ui.tab

import io.github.shogowada.scalajs.reactjs.React
import io.github.shogowada.scalajs.reactjs.VirtualDOM._
import io.github.shogowada.scalajs.reactjs.classes.ReactClass
import io.github.shogowada.scalajs.reactjs.events.MouseSyntheticEvent
import scommons.client.ui.ImageLabelWrapper
import scommons.react.UiComponent
import scommons.react._

case class TabPanelProps(items: List[TabItemData],
selectedIndex: Int = 0,
Expand All @@ -16,69 +13,57 @@ case class TabPanelProps(items: List[TabItemData],
s"selectedIndex($selectedIndex) should be within items indices")
}

object TabPanel extends UiComponent[TabPanelProps] {
object TabPanel extends FunctionComponent[TabPanelProps] {

private case class TabPanelState(selectedIndex: Int)
protected def render(compProps: Props): ReactElement = {
val props = compProps.wrapped
val itemWithIndexList = props.items.zipWithIndex

protected def create(): ReactClass = React.createClass[PropsType, TabPanelState](
getInitialState = { self =>
TabPanelState(self.props.wrapped.selectedIndex)
},
componentWillReceiveProps = { (self, nextProps) =>
val props = nextProps.wrapped
if (self.props.wrapped.selectedIndex != props.selectedIndex) {
self.setState(_.copy(selectedIndex = props.selectedIndex))
}
},
render = { self =>
val props = self.props.wrapped
val itemWithIndexList = props.items.zipWithIndex

val tabs = <.ul(^.className := "nav nav-tabs")(itemWithIndexList.map { case (item, index) =>
<.li(
^.key := index.toString,
if (index == self.state.selectedIndex) Some(^.className := "active")
else None
)(
<.a(
^.href := "",
^.onClick := { e: MouseSyntheticEvent =>
e.preventDefault()
self.setState(_.copy(selectedIndex = index))
val tabs = <.ul(^.className := "nav nav-tabs")(itemWithIndexList.map { case (item, index) =>
<.li(
^.key := index.toString,
if (index == props.selectedIndex) Some(^.className := "active")
else None
)(
<.a(
^.href := "",
^.onClick := { e: MouseSyntheticEvent =>
e.preventDefault()
if (index != props.selectedIndex) {
props.onSelect(item, index)
}
)(item.image match {
case None => item.title
case Some(image) => ImageLabelWrapper(image, Some(item.title), alignText = false)
})
)
})
}
)(item.image match {
case None => item.title
case Some(image) => ImageLabelWrapper(image, Some(item.title), alignText = false)
})
)
})

val content = <.div(^.className := "tab-content")(itemWithIndexList.map { case (item, index) =>
val activeClass =
if (index == self.state.selectedIndex) "active"
else ""
val content = <.div(^.className := "tab-content")(itemWithIndexList.map { case (item, index) =>
val activeClass =
if (index == props.selectedIndex) "active"
else ""

<.div(
^.key := index.toString,
^.className := s"tab-pane $activeClass"
)(
item.component.map { comp =>
<(comp)()()
}.getOrElse(
item.render.map { render =>
render(self.props)
}.getOrElse {
<.div()()
}
)
<.div(
^.key := index.toString,
^.className := s"tab-pane $activeClass"
)(
item.component.map { comp =>
<(comp)()()
}.getOrElse(
item.render.map { render =>
render(compProps)
}.getOrElse {
<.div()()
}
)
})

<.div(^.className := props.direction.style)(
if (props.direction == TabDirection.Bottom) List(content, tabs)
else List(tabs, content)
)
}
)
})

<.div(^.className := props.direction.style)(
if (props.direction == TabDirection.Bottom) List(content, tabs)
else List(tabs, content)
)
}
}
71 changes: 40 additions & 31 deletions ui/src/test/scala/scommons/client/ui/tab/TabPanelSpec.scala
Original file line number Diff line number Diff line change
@@ -1,59 +1,59 @@
package scommons.client.ui.tab

import io.github.shogowada.scalajs.reactjs.React
import io.github.shogowada.statictags.Element
import org.scalajs.dom
import org.scalatest.Assertion
import scommons.client.ui.Buttons
import scommons.react._
import scommons.react.test.TestSpec
import scommons.react.test.dom.raw.ReactTestUtils
import scommons.react.test.dom.raw.ReactTestUtils._
import scommons.react.test.dom.util.TestDOMUtils
import scommons.react.test.raw.ShallowInstance
import scommons.react.test.util.ShallowRendererUtils

class TabPanelSpec extends TestSpec with ShallowRendererUtils {
class TabPanelSpec extends TestSpec
with ShallowRendererUtils
with TestDOMUtils {

it should "call onSelect when select tab" in {
it should "call onSelect when select new tab" in {
//given
val onSelect = mockFunction[TabItemData, Int, Unit]
val items = List(
TabItemData("Tab 1"),
TabItemData("Tab 2")
)
val props = TabPanelProps(items, onSelect = onSelect)
val comp = renderIntoDocument(<(TabPanel())(^.wrapped := props)())
val buttons = scryRenderedDOMComponentsWithTag(comp, "a")
domRender(<(TabPanel())(^.wrapped := props)())
val buttons = domContainer.querySelectorAll("a")
buttons.length shouldBe items.size
val tabs = scryRenderedDOMComponentsWithTag(comp, "li")
val tabs = domContainer.querySelectorAll("li")
tabs.length shouldBe items.size
tabs(props.selectedIndex).className shouldBe "active"
val panels = scryRenderedDOMComponentsWithClass(comp, "tab-pane")
tabs(props.selectedIndex).asInstanceOf[dom.Element].getAttribute("class") shouldBe "active"
val panels = domContainer.querySelectorAll(".tab-pane")
panels.length shouldBe items.size
panels(props.selectedIndex).className shouldBe "tab-pane active"
panels(props.selectedIndex).asInstanceOf[dom.Element].getAttribute("class") shouldBe "tab-pane active"
val nextSelectIndex = 1

//then
onSelect.expects(items(nextSelectIndex), nextSelectIndex)

//when
ReactTestUtils.Simulate.click(buttons(nextSelectIndex))

//then
tabs(props.selectedIndex).className shouldBe ""
tabs(nextSelectIndex).className shouldBe "active"
panels(props.selectedIndex).className shouldBe "tab-pane "
panels(nextSelectIndex).className shouldBe "tab-pane active"
fireDomEvent(Simulate.click(buttons(nextSelectIndex)))
fireDomEvent(Simulate.click(buttons(props.selectedIndex)))
}

it should "reset selectedIndex when componentWillReceiveProps" in {
it should "select tab with selectedIndex when update" in {
//given
val content1 = <.div()()
val content2 = <.div()("Test second tab content")
val content3 = <.div()("Test third tab content")
val wrapper = new FunctionComponent[Unit] {
protected def render(props: Props): ReactElement = {
content2
}
}
val items = List(
TabItemData("Tab 1", image = Some(Buttons.ADD.image)),
TabItemData("Tab 2", component = Some(React.createClass[Unit, Unit] { _ =>
content2
})),
TabItemData("Tab 2", component = Some(wrapper())),
TabItemData("Tab 3", render = Some(_ => content3))
)
val prevProps = TabPanelProps(items)
Expand All @@ -77,11 +77,14 @@ class TabPanelSpec extends TestSpec with ShallowRendererUtils {
val content1 = <.div()()
val content2 = <.div()("Test second tab content")
val content3 = <.div()("Test third tab content")
val wrapper = new FunctionComponent[Unit] {
protected def render(props: Props): ReactElement = {
content2
}
}
val items = List(
TabItemData("Tab 1", image = Some(Buttons.ADD.image)),
TabItemData("Tab 2", component = Some(React.createClass[Unit, Unit] { _ =>
content2
})),
TabItemData("Tab 2", component = Some(wrapper())),
TabItemData("Tab 3", render = Some(_ => content3))
)
val props = TabPanelProps(items)
Expand All @@ -99,11 +102,14 @@ class TabPanelSpec extends TestSpec with ShallowRendererUtils {
val content1 = <.div()()
val content2 = <.div()("Test second tab content")
val content3 = <.div()("Test third tab content")
val wrapper = new FunctionComponent[Unit] {
protected def render(props: Props): ReactElement = {
content2
}
}
val items = List(
TabItemData("Tab 1", image = Some(Buttons.ADD.image)),
TabItemData("Tab 2", component = Some(React.createClass[Unit, Unit] { _ =>
content2
})),
TabItemData("Tab 2", component = Some(wrapper())),
TabItemData("Tab 3", render = Some(_ => content3))
)
val props = TabPanelProps(items, selectedIndex = 1)
Expand All @@ -121,11 +127,14 @@ class TabPanelSpec extends TestSpec with ShallowRendererUtils {
val content1 = <.div()()
val content2 = <.div()("Test second tab content")
val content3 = <.div()("Test third tab content")
val wrapper = new FunctionComponent[Unit] {
protected def render(props: Props): ReactElement = {
content2
}
}
val items = List(
TabItemData("Tab 1", image = Some(Buttons.ADD.image)),
TabItemData("Tab 2", component = Some(React.createClass[Unit, Unit] { _ =>
content2
})),
TabItemData("Tab 2", component = Some(wrapper())),
TabItemData("Tab 3", render = Some(_ => content3))
)
val props = TabPanelProps(items, direction = TabDirection.Bottom)
Expand Down

0 comments on commit 0209efd

Please sign in to comment.