Skip to content

Commit

Permalink
Merge pull request #1138 from FlowFuse/1119-layout-tabs
Browse files Browse the repository at this point in the history
Layout: Tabs
  • Loading branch information
joepavitt authored Jul 31, 2024
2 parents 19cca2b + 4f3e768 commit 1429fb5
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 2 deletions.
3 changes: 2 additions & 1 deletion docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ export default ({ mode }) => {
items: [
{ text: 'Grid', link: '/layouts/types/grid' },
{ text: 'Fixed', link: '/layouts/types/fixed' },
{ text: 'Notebook', link: '/layouts/types/notebook' }
{ text: 'Notebook', link: '/layouts/types/notebook' },
{ text: 'Tabs', link: '/layouts/types/tabs' }
]
}
]
Expand Down
Binary file added docs/assets/images/layout-tabs.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/layouts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ We currently offer three different layout options:
- [Grid](./types/grid.md)
- [Fixed](./types/fixed.md)
- [Notebook](./types/notebook.md)
- [Tabs](./types/tabs.md)

## Sizing Groups & Widgets

Expand Down
48 changes: 48 additions & 0 deletions docs/layouts/types/tabs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
description:Render each group as a standalone Tab in a tabbed view for the page
---
<script setup>
import AddedIn from '../../components/AddedIn.vue'
</script>

# Layout: Tabs <AddedIn version="1.15.0" />

Rather than rendering each group side-by-side (as per Fixed and Grid layouts) or above and below each other (as per Notebook layout), the Tabs layout will render each group as a standalone Tab in a tabbed view for the page. You can then switch between the tabs to view the different groups.

![Tabs Layout](../../assets/images/layout-tabs.gif){data-zoomable}
*An example UI rendered using the "Tabs" Layout, showing each "Group" as a Tab.*

Note that it's not currently possible to navigate to a page with a a particular tab open. The page will always open with the first tab selected.

## Controlling Width & Columns

Each tab will always render the full width of the screen. The "width" of each group then defines the number of columns that will be available within the tab.

For example, if you have a group with a width of 6, and two charts, each with a width of 3, they will render side-by-side within the tab, at 50% of the screen's width. If you then changed the group's width to be 12, the two charts would instead only take up 25% of the screen width each.

## Breakpoints

Depending on the screen size, the number of default columns rendered will change. Here you can see examples of the columns rendered at three breakpoints:

![Guidelines demonstrating the columns rendered in the "Grid" Layout](../../assets/images/layout-grid-columns.png){data-zoomable}
_Guidelines demonstrating the columns rendered in the "Grid" Layout at different screen sizes_

### Desktop

- **Breakpoint:** 1024px
- **Columns:** 12

### Tablet

- **Breakpoint:** 768px
- **Columns:** 9

### Medium

- **Breakpoint:** > 576px
- **Columns:** 6

### Mobile

- **Breakpoint:** < 576px
- **Columns:** 3
2 changes: 1 addition & 1 deletion nodes/config/locales/en-US/ui_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ <h3>Properties</h3>
</dd>
<dt>
Layout
<span class="property-type">Grid | Fixed | Notebook</span>
<span class="property-type">Grid | Fixed | Notebook | Tabs</span>
</dt>
<dd>
Choose form one of the available Dashboard Layouts.
Expand Down
1 change: 1 addition & 0 deletions nodes/config/locales/en-US/ui_page.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"layout": "Layout",
"grid": "Grid",
"fixed": "Fixed",
"tabs": "Tabs",
"notebook": "Notebook",
"class": "Class",
"classNamePlaceholder": "Optional CSS class name(s) for page",
Expand Down
1 change: 1 addition & 0 deletions nodes/config/ui_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
options: [
{ value: 'grid', label: RED._('@flowfuse/node-red-dashboard/ui-page:ui-page.label.grid') },
{ value: 'flex', label: RED._('@flowfuse/node-red-dashboard/ui-page:ui-page.label.fixed') },
{ value: 'tabs', label: RED._('@flowfuse/node-red-dashboard/ui-page:ui-page.label.tabs') },
{ value: 'notebook', label: RED._('@flowfuse/node-red-dashboard/ui-page:ui-page.label.notebook') }
]
}],
Expand Down
148 changes: 148 additions & 0 deletions ui/src/layouts/Tabs.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<template>
<BaselineLayout :page-title="$route.meta.title">
<div :id="'nrdb-page-' + $route.meta.id" class="nrdb-layout--tabs nrdb-ui-page" :class="page?.className">
<div>
<!-- Render any widgets with a 'page' scope -->
<component
:is="widget.component"
v-for="widget in pageWidgets"
:id="widget.id"
:key="widget.id"
:props="widget.props"
:state="widget.state"
/>
</div>

<v-tabs v-if="orderedGroups" v-model="tab" show-arrows>
<v-tab v-for="t in orderedGroups" :key="t.id" :value="t.id">{{ t.name }}</v-tab>
</v-tabs>

<v-tabs-window v-if="orderedGroups" v-model="tab">
<v-tabs-window-item v-for="t in orderedGroups" :key="t.id" :value="t.id">
<div
class="nrdb-ui-group" :class="getGroupClass(t)"
:disabled="t.disabled === true ? 'disabled' : null"
:style="`grid-column-end: span min(${ t.width }, var(--layout-columns)`"
>
<v-card variant="outlined" class="bg-group-background">
<template #text>
<widget-group :group="t" :widgets="widgetsByGroup(t.id)" :row-height="rowHeight" />
</template>
</v-card>
</div>
</v-tabs-window-item>
</v-tabs-window>
</div>
</BaselineLayout>
</template>

<script>
// eslint-disable-next-line import/order
import BaselineLayout from './Baseline.vue'
import WidgetGroup from './Group.vue'
// eslint-disable-next-line import/order, sort-imports
import { mapState, mapGetters } from 'vuex'
export default {
name: 'LayoutTabs',
components: {
BaselineLayout,
WidgetGroup
},
data () {
return {
rowHeight: 48,
tab: 0
}
},
computed: {
...mapState('ui', ['groups', 'widgets', 'pages']),
...mapState('data', ['properties']),
...mapGetters('ui', ['groupsByPage', 'widgetsByGroup', 'widgetsByPage']),
orderedGroups: function () {
// get groups on this page
const groups = this.groupsByPage(this.$route.meta.id)
// only show hte groups that haven't had their "visible" property set to false
.filter((g) => {
if ('visible' in g) {
return g.visible
}
return true
})
.sort((a, b) => {
return a.order - b.order
})
return groups
},
pageWidgets: function () {
return this.widgetsByPage(this.$route.meta.id)
},
page: function () {
return this.pages[this.$route.meta.id]
}
},
methods: {
getWidgetClass (widget) {
const classes = []
// ensure each widget has a class for its type
classes.push(`nrdb-${widget.type}`)
if (widget.props.className) {
classes.push(widget.props.className)
}
if (widget.state.class) {
classes.push(widget.state.class)
}
return classes.join(' ')
},
getGroupClass (group) {
const classes = []
// add any class set in the group's properties
if (group.className) {
classes.push(group.className)
}
// add dynamically set class(es)
const properties = this.properties[group.id]
if (properties && properties.class) {
classes.push(properties.class)
}
return classes.join(' ')
}
}
}
</script>

<style scoped>
@import "./grid-groups.css";
.nrdb-layout--tabs {
--layout-gap: 12px;
--widget-row-height: 48px;
--layout-columns: 12;
padding: var(--page-padding);
}
.v-card {
width: 100%;
}
@media only screen and (max-width: 1024px) {
.nrdb-layout--tabs {
--layout-columns: 9;
}
}
@media only screen and (max-width: 768px) {
.nrdb-layout--tabs {
--layout-columns: 6;
}
}
@media only screen and (max-width: 576px) {
.nrdb-layout--tabs {
--layout-columns: 3;
}
}
</style>
2 changes: 2 additions & 0 deletions ui/src/layouts/index.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import Flex from './Flex.vue'
import Grid from './Grid.vue'
import Notebook from './Notebook.vue'
import Tabs from './Tabs.vue'

export default {
flex: Flex,
grid: Grid,
tabs: Tabs,
notebook: Notebook
}

0 comments on commit 1429fb5

Please sign in to comment.