move vertical tab switcher into helpers since it's not meant to be used elsewhere
This commit is contained in:
parent
2d0bd043cb
commit
ce04595e36
5 changed files with 3 additions and 6 deletions
207
src/components/settings_modal/helpers/vertical_tab_switcher.jsx
Normal file
207
src/components/settings_modal/helpers/vertical_tab_switcher.jsx
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
// eslint-disable-next-line no-unused
|
||||
import { h, Fragment } from 'vue'
|
||||
import { mapState } from 'pinia'
|
||||
import { throttle } from 'lodash'
|
||||
import { mapState as mapPiniaState } from 'pinia'
|
||||
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
|
||||
|
||||
import './vertical_tab_switcher.scss'
|
||||
import { useInterfaceStore } from 'src/stores/interface'
|
||||
|
||||
const findFirstUsable = (slots) => slots.findIndex(_ => _.props)
|
||||
|
||||
export default {
|
||||
name: 'VerticalTabSwitcher',
|
||||
props: {
|
||||
renderOnlyFocused: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onSwitch: {
|
||||
required: false,
|
||||
type: Function,
|
||||
default: undefined
|
||||
},
|
||||
activeTab: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
bodyScrollLock: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
parentCollapsed: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
active: findFirstUsable(this.slots()),
|
||||
resizeHandler: null,
|
||||
navSide: 'tabs'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
activeIndex () {
|
||||
// In case of controlled component
|
||||
if (this.activeTab) {
|
||||
return this.slots().findIndex(slot => slot && slot.props && this.activeTab === slot.props.key)
|
||||
} else {
|
||||
return this.active
|
||||
}
|
||||
},
|
||||
isActive () {
|
||||
return tabName => {
|
||||
const isWanted = slot => slot.props && slot.props['data-tab-name'] === tabName
|
||||
return this.$slots.default().findIndex(isWanted) === this.activeIndex
|
||||
}
|
||||
},
|
||||
...mapPiniaState(useInterfaceStore, {
|
||||
mobileLayout: store => store.layoutType === 'mobile'
|
||||
}),
|
||||
},
|
||||
beforeUpdate () {
|
||||
const currentSlot = this.slots()[this.active]
|
||||
if (!currentSlot.props) {
|
||||
this.active = findFirstUsable(this.slots())
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickTab (index) {
|
||||
return (e) => {
|
||||
e.preventDefault()
|
||||
this.setTab(index)
|
||||
}
|
||||
},
|
||||
setTab (index) {
|
||||
if (typeof this.onSwitch === 'function') {
|
||||
this.onSwitch.call(null, this.slots()[index].key)
|
||||
}
|
||||
this.active = index
|
||||
this.changeNavSide('content')
|
||||
},
|
||||
changeNavSide (side) {
|
||||
if (this.navSide !== side) {
|
||||
this.navSide = side
|
||||
}
|
||||
},
|
||||
// DO NOT put it to computed, it doesn't work (caching?)
|
||||
slots () {
|
||||
if (this.$slots.default()[0].type === Fragment) {
|
||||
return this.$slots.default()[0].children
|
||||
}
|
||||
return this.$slots.default()
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const tabs = this.slots()
|
||||
.map((slot, index) => {
|
||||
const props = slot.props
|
||||
if (!props) return
|
||||
const classesTab = ['vertical-tab', 'menu-item']
|
||||
if (this.activeIndex === index && useInterfaceStore().layoutType !== 'mobile') {
|
||||
classesTab.push('-active')
|
||||
}
|
||||
return (
|
||||
<button
|
||||
disabled={props.disabled}
|
||||
onClick={this.clickTab(index)}
|
||||
class={classesTab.join(' ')}
|
||||
type="button"
|
||||
role="tab"
|
||||
title={props.label}
|
||||
>
|
||||
{!props.icon ? '' : (<FAIcon class="tab-icon" size="1x" fixed-width icon={props.icon}/>)}
|
||||
<span class="text">
|
||||
{props.label}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
||||
const contents = this.slots().map((slot, index) => {
|
||||
const props = slot.props
|
||||
if (!props) return
|
||||
const active = this.activeIndex === index
|
||||
const classes = ['tab-content-wrapper', active ? '-active' : '-hidden' ]
|
||||
if (props.fullHeight) {
|
||||
classes.push('-full-height')
|
||||
}
|
||||
let delayRender = slot.props['delay-render']
|
||||
if (delayRender && active) {
|
||||
slot.props['delay-render'] = false
|
||||
delayRender = false
|
||||
}
|
||||
const renderSlot = (!delayRender && (!this.renderOnlyFocused || active))
|
||||
? slot
|
||||
: ''
|
||||
|
||||
const headerClasses = ['tab-content-label']
|
||||
const header = (
|
||||
<h2 class={headerClasses}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => this.changeNavSide('tabs')}
|
||||
class="button-unstyled"
|
||||
>
|
||||
<FAIcon
|
||||
size="lg"
|
||||
class="back-button-icon"
|
||||
icon="chevron-left"
|
||||
/>
|
||||
</button>
|
||||
{props.label}
|
||||
</h2>
|
||||
)
|
||||
|
||||
return (
|
||||
<div class={classes} >
|
||||
<div class="tab-mobile-header">
|
||||
{header}
|
||||
</div>
|
||||
<div class="tab-slot-wrapper">
|
||||
<div class={ ['tab-content', props['full-width'] ? '-full-width' : null].join(' ') } >
|
||||
{renderSlot}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
const rootClasses = ['vertical-tab-switcher']
|
||||
if (useInterfaceStore().layoutType === 'mobile') {
|
||||
rootClasses.push('-mobile')
|
||||
}
|
||||
|
||||
if (this.navSide === 'tabs') {
|
||||
rootClasses.push('-nav-tabs')
|
||||
} else {
|
||||
rootClasses.push('-nav-contents')
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref="root" class={ rootClasses.join(' ') }>
|
||||
<div
|
||||
class="tabs"
|
||||
role="tablist"
|
||||
ref="nav"
|
||||
>
|
||||
{tabs}
|
||||
</div>
|
||||
<div
|
||||
role="tabpanel"
|
||||
class={'contents' + (this.scrollableTabs ? ' scrollable-tabs' : '')}
|
||||
v-body-scroll-lock={this.bodyScrollLock}
|
||||
ref="contents"
|
||||
>
|
||||
{contents}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
176
src/components/settings_modal/helpers/vertical_tab_switcher.scss
Normal file
176
src/components/settings_modal/helpers/vertical_tab_switcher.scss
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
.vertical-tab-switcher {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
container-type: inline-size;
|
||||
|
||||
> .tabs {
|
||||
flex: 0 0 15em;
|
||||
flex-direction: column;
|
||||
overflow: hidden auto;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 15em;
|
||||
min-width: 15em;
|
||||
border-right: 1px solid;
|
||||
border-right-color: var(--border);
|
||||
box-sizing: border-box;
|
||||
|
||||
> .menu-item {
|
||||
padding: 0.5em 1em;
|
||||
|
||||
.tab-icon {
|
||||
vertical-align: middle;
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .contents {
|
||||
flex: 1 0 35em;
|
||||
|
||||
.tab-content {
|
||||
align-self: center;
|
||||
height: 100%;
|
||||
|
||||
&:not(.-full-width) {
|
||||
max-width: 40em;
|
||||
}
|
||||
|
||||
&.-full-width {
|
||||
align-self: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content-label {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: none;
|
||||
|
||||
button {
|
||||
box-sizing: border-box;
|
||||
padding: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-slot-wrapper {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab-content-wrapper {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
|
||||
&.-hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.-mobile {
|
||||
> .contents {
|
||||
.tab-content-label {
|
||||
display: block
|
||||
}
|
||||
}
|
||||
|
||||
&.-nav-contents {
|
||||
> .contents {
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
> .tabs {
|
||||
display: none;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.-nav-tabs {
|
||||
> .tabs {
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
> .contents {
|
||||
display: none;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@supports (container-type: inline-size) {
|
||||
&,
|
||||
&.-mobile {
|
||||
&.-nav-contents,
|
||||
&.-nav-tabs {
|
||||
/* I THINK it's a false positive and eslint doesn't understand the @-rule */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
> .contents {
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
> .tabs {
|
||||
display: block;
|
||||
flex-grow: 0;
|
||||
}
|
||||
/* stylelint-enable no-descending-specificity */
|
||||
}
|
||||
}
|
||||
|
||||
@container (width < 50em) {
|
||||
> .contents {
|
||||
.tab-content-label {
|
||||
display: block
|
||||
}
|
||||
}
|
||||
|
||||
&.-mobile {
|
||||
> .contents {
|
||||
.tab-content-label {
|
||||
display: block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&,
|
||||
&.-mobile {
|
||||
&.-nav-contents {
|
||||
> .contents {
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
> .tabs {
|
||||
display: none;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.-nav-tabs {
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
> .tabs {
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
> .contents {
|
||||
display: none;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
/* stylelint-enable no-descending-specificity */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import VerticalTabSwitcher from 'src/components/tab_switcher/vertical_tab_switcher.jsx'
|
||||
import VerticalTabSwitcher from './helpers/vertical_tab_switcher.jsx'
|
||||
|
||||
import InstanceTab from './admin_tabs/instance_tab.vue'
|
||||
import LimitsTab from './admin_tabs/limits_tab.vue'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import VerticalTabSwitcher from 'src/components/tab_switcher/vertical_tab_switcher.jsx'
|
||||
import VerticalTabSwitcher from './helpers/vertical_tab_switcher.jsx'
|
||||
|
||||
import DataImportExportTab from './tabs/data_import_export_tab.vue'
|
||||
import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import VerticalTabSwitcher from 'src/components/tab_switcher/vertical_tab_switcher.jsx'
|
||||
|
||||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import ChoiceSetting from '../helpers/choice_setting.vue'
|
||||
import IntegerSetting from '../helpers/integer_setting.vue'
|
||||
|
|
@ -88,8 +86,7 @@ const AppearanceTab = {
|
|||
UnitSetting,
|
||||
ProfileSettingIndicator,
|
||||
Preview,
|
||||
PaletteEditor,
|
||||
VerticalTabSwitcher
|
||||
PaletteEditor
|
||||
},
|
||||
mounted () {
|
||||
useInterfaceStore().getThemeData()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue