Skip to content
This repository has been archived by the owner on Jan 19, 2023. It is now read-only.

Tabs Component #2482

Merged
merged 1 commit into from
Jun 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelogs/unreleased/2482-ftovaro-lenriquez
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added tabs as components
4 changes: 3 additions & 1 deletion pkg/view/component/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const (
TypeAccordion = "accordion"
// TypeAnnotations is an annotations component.
TypeAnnotations = "annotations"
// ButtonGroup is a button group component.
// TypeButtonGroup is a button group component.
TypeButtonGroup = "buttonGroup"
// TypeCard is a card component.
TypeCard = "card"
Expand Down Expand Up @@ -96,6 +96,8 @@ const (
TypeSignpost = "signpost"
// TypeButton is a Button component.
TypeButton = "button"
// TypeTabsView is a Tab component.
TypeTabsView = "tabsView"
)

// Base is an abstract base for components..
Expand Down
2 changes: 1 addition & 1 deletion pkg/view/component/button_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (bg *ButtonGroupConfig) UnmarshalJSON(data []byte) error {

button, ok := component.(*Button)
if !ok {
return fmt.Errorf("item was not a card")
return fmt.Errorf("item was not a button group")
}

bg.Buttons = append(bg.Buttons, *button)
Expand Down
94 changes: 94 additions & 0 deletions pkg/view/component/tabs_view.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
Copyright (c) 2021 the Octant contributors. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package component

import (
"fmt"

"github.com/vmware-tanzu/octant/internal/util/json"
)

type TabsView struct {
Base
Config TabsViewConfig `json:"config"`
}

// TabsOrientation is the direction of the Tabs
type TabsOrientation string

const (
// VerticalTabs are tabs organized vertically
VerticalTabs TabsOrientation = "vertical"
// HorizontalTabs are tabs organized horizontally
HorizontalTabs TabsOrientation = "horizontal"
)

type TabsViewConfig struct {
// Tabs are an array of Tab structs
Tabs []SingleTab `json:"tabs"`
// Orientation is the direction of the tabs
Orientation TabsOrientation `json:"orientation,omitempty"`
}

func NewTabs(orientation TabsOrientation, tabs []SingleTab) *TabsView {
return &TabsView{
Base: newBase(TypeTabsView, nil),
Config: TabsViewConfig{
Tabs: tabs,
Orientation: orientation,
},
}
}

type tabsMarshal TabsView

// MarshalJSON marshals a button group.
func (t *TabsView) MarshalJSON() ([]byte, error) {
m := tabsMarshal(*t)
m.Metadata.Type = TypeTabsView
return json.Marshal(&m)
}

var _ Component = (*TabsView)(nil)

type SingleTab struct {
Name string `json:"name"`
Contents FlexLayout `json:"contents"`
}

func (t *TabsViewConfig) UnmarshalJSON(data []byte) error {
x := struct {
Orientation TabsOrientation `json:"orientation,omitempty"`
Tabs []struct {
Name string `json:"name"`
Contents TypedObject `json:"contents"`
} `json:"tabs"`
}{}

if err := json.Unmarshal(data, &x); err != nil {
return err
}

for _, tab := range x.Tabs {
c, err := tab.Contents.ToComponent()
if err != nil {
return err
}
fl, ok := c.(*FlexLayout)
if !ok {
return fmt.Errorf("item was not a FlexLayout")
}
st := SingleTab{
Name: tab.Name,
Contents: *fl,
}
t.Tabs = append(t.Tabs, st)
}

t.Orientation = x.Orientation

return nil
}
74 changes: 74 additions & 0 deletions pkg/view/component/tabs_view_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
Copyright (c) 2021 the Octant contributors. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package component

import (
"io/ioutil"
"path"
"testing"

"github.com/vmware-tanzu/octant/pkg/action"

"github.com/stretchr/testify/assert"

"github.com/stretchr/testify/require"

"github.com/vmware-tanzu/octant/internal/util/json"
)

func Test_Tabs_Marshal(t *testing.T) {
fl := NewFlexLayout("title")
button := NewButton("test", action.Payload{"foo": "bar"})
section := FlexLayoutSection{
{
Width: WidthFull,
View: button,
},
}
fl.AddSections(section)

test := []struct {
name string
input Component
expectedPath string
isErr bool
}{
{
name: "in general",
input: &TabsView{
Base: newBase(TypeTabsView, nil),
Config: TabsViewConfig{
Tabs: []SingleTab{
{
Name: "title",
Contents: *fl,
},
{
Name: "title 2",
Contents: *fl,
},
},
},
},
expectedPath: "tabs.json",
isErr: false,
},
}

for _, tc := range test {
t.Run(tc.name, func(t *testing.T) {
actual, err := json.Marshal(tc.input)
isErr := err != nil
if isErr != tc.isErr {
t.Fatalf("UnExpected error: %v", err)
}

expected, err := ioutil.ReadFile(path.Join("testdata", tc.expectedPath))
require.NoError(t, err)
assert.JSONEq(t, string(expected), string(actual))
})
}
}
34 changes: 34 additions & 0 deletions pkg/view/component/testdata/config_tabs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"tabs":
[
{
"name":"Tab 1",
"contents":{
"config":{
"sections":
[
[
{
"width": 24,
"view":{
"config":{
"name":"test",
"payload":{
"foo":"bar"
}
},
"metadata":{
"type":"button"
}
}
}
]
]
},
"metadata":{
"type":"flexlayout"
}
}
}
]
}
104 changes: 104 additions & 0 deletions pkg/view/component/testdata/tabs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{
"metadata":{"type":"tabsView"},
"config":{
"tabs":
[
{
"name":"title",
"contents":{
"config":{
"buttonGroup":{
"config":{
"buttons":null
},
"metadata":{
"type":"buttonGroup"
}
},
"sections":
[
[
{
"width": 24,
"view":{
"config":{
"name":"test",
"payload":{
"foo":"bar"
}
},
"metadata":{
"type":"button"
}
}
}
]
]
},
"metadata":{
"title":
[
{
"config":{
"value":"title"
},
"metadata":{
"type":"text"
}
}
],
"type":"flexlayout"
}
}
},
{
"name":"title 2",
"contents":{
"config":{
"buttonGroup":{
"config":{
"buttons":null
},
"metadata":{
"type":"buttonGroup"
}
},
"sections":
[
[
{
"width": 24,
"view":{
"config":{
"name":"test",
"payload":{
"foo":"bar"
}
},
"metadata":{
"type":"button"
}
}
}
]
]
},
"metadata":{
"title":
[
{
"config":{
"value":"title"
},
"metadata":{
"type":"text"
}
}
],
"type":"flexlayout"
}
}
}
]
}
}
5 changes: 5 additions & 0 deletions pkg/view/component/unmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ func unmarshal(to TypedObject) (Component, error) {
err = errors.Wrapf(json.Unmarshal(to.Config, &t.Config),
"unmarshal signpost config")
o = t
case TypeTabsView:
t := &TabsView{Base: Base{Metadata: to.Metadata}}
err = errors.Wrapf(json.Unmarshal(to.Config, &t.Config),
"unmarshal tabs config")
o = t
default:
return nil, errors.Errorf("unknown view component %q", to.Metadata.Type)
}
Expand Down
23 changes: 23 additions & 0 deletions pkg/view/component/unmarshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,29 @@ func Test_unmarshal(t *testing.T) {
Base: newBase(TypeStepper, nil),
},
},
{
name: "tabsView",
configFile: "config_tabs.json",
objectType: "tabsView",
expected: &TabsView{
Config: TabsViewConfig{
Tabs: []SingleTab{{
Name: "Tab 1",
Contents: FlexLayout{
Base: newBase(TypeFlexLayout, nil),
Config: FlexLayoutConfig{
Sections: []FlexLayoutSection{{{
Width: WidthFull,
View: NewButton("test", action.Payload{"foo": "bar"}),
}}},
ButtonGroup: nil,
},
},
}},
},
Base: newBase(TypeTabsView, nil),
},
},
{
name: "summary",
configFile: "config_summary.json",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<clr-tabs *ngIf="tabs.length > 0" clrLayout="{{ orientation }}">
<clr-tab *ngFor="let tab of tabs; trackBy: identifyTab">
<button clrTabLink class="tab-button" (click)="clickTab(tab.name)">
{{ tab.name }}
</button>
<ng-template [clrIfActive]="activeTab === tab.name">
<clr-tab-content>
<div [ngClass]="{'tabs-container': orientation === 'horizontal'}">
<app-view-container [view]="tab.contents"></app-view-container>
</div>
</clr-tab-content>
</ng-template>
</clr-tab>
</clr-tabs>
Loading