Skip to content

Commit

Permalink
fix(NcAppSidebar): make sidebar a single node to allow using v-show
Browse files Browse the repository at this point in the history
Signed-off-by: Grigorii K. Shartsev <me@shgk.me>
  • Loading branch information
ShGKme committed May 21, 2024
1 parent ba47a1d commit 5a12f6b
Showing 1 changed file with 165 additions and 160 deletions.
325 changes: 165 additions & 160 deletions src/components/NcAppSidebar/NcAppSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -454,174 +454,181 @@ export default {
</docs>

<template>
<Fragment>
<!-- With vue3 we can move this inside the transition, but vue2 does not allow v-show in transition -->
<NcButton v-if="!open"
:aria-label="t('Open sidebar')"
class="app-sidebar__toggle"
:class="toggleClasses"
type="tertiary"
@click="$emit('update:open', true)">
<template #icon>
<!-- @slot Custom icon for the toggle button, defaults to the dock-right icon from MDI -->
<slot name="toggle-icon">
<IconDockRight :size="20" />
</slot>
</template>
</NcButton>
<transition appear
name="slide-right"
v-bind="$attrs"
v-on="$listeners"
@before-enter="onBeforeEnter"
@after-enter="onAfterEnter"
@before-leave="onBeforeLeave"
@after-leave="onAfterLeave">
<aside v-show="open"
id="app-sidebar-vue"
ref="sidebar"
class="app-sidebar"
:aria-labelledby="`app-sidebar-vue-${uid}__header`"
@keydown.esc="onKeydownEsc">
<header :class="{
'app-sidebar-header--with-figure': hasFigure,
'app-sidebar-header--compact': compact,
}"
class="app-sidebar-header">
<!-- container for figure and description, allows easy switching to compact mode -->
<div class="app-sidebar-header__info">
<!-- sidebar header illustration/figure -->
<div v-if="hasFigure && !empty"
:class="{
'app-sidebar-header__figure--with-action': hasFigureClickListener
}"
class="app-sidebar-header__figure"
:style="{
backgroundImage: `url(${background})`
}"
tabindex="0"
@click="onFigureClick"
@keydown.enter="onFigureClick">
<slot class="app-sidebar-header__background" name="header" />
</div>

<!-- sidebar details -->
<div v-if="!empty"
:class="{
'app-sidebar-header__desc--with-tertiary-action': canStar || $slots['tertiary-actions'],
'app-sidebar-header__desc--editable': nameEditable && !subname,
'app-sidebar-header__desc--with-subname--editable': nameEditable && subname,
'app-sidebar-header__desc--without-actions': !$slots['secondary-actions'],
}"
class="app-sidebar-header__desc">
<!-- favourite icon -->
<div v-if="canStar || $slots['tertiary-actions']" class="app-sidebar-header__tertiary-actions">
<slot name="tertiary-actions">
<NcButton v-if="canStar"
:aria-label="favoriteTranslated"
:pressed="isStarred"
class="app-sidebar-header__star"
type="secondary"
@click.prevent="toggleStarred">
<template #icon>
<NcLoadingIcon v-if="starLoading" />
<Star v-else-if="isStarred" :size="20" />
<StarOutline v-else :size="20" />
</template>
</NcButton>
</slot>
</div>
<transition appear
name="slide-right"
@before-enter="onBeforeEnter"
@after-enter="onAfterEnter"
@before-leave="onBeforeLeave"
@after-leave="onAfterLeave">
<aside v-show="open"
id="app-sidebar-vue"
ref="sidebar"
class="app-sidebar"
:aria-labelledby="`app-sidebar-vue-${uid}__header`"
@keydown.esc="onKeydownEsc">
<!--
We cannot render toggle button inside sidebar (aside#app-sidebar-vue), because it is hidden then the toggle is needed.
But we also need transition with the sidebar to be the root of this component to use it as a single UI element, allowing to use `v-show`.
So we cannot render the toggle button directly in this component.
As a simple solution - render it in the content to keep correct position.
-->
<Teleport selector="#content-vue">
<NcButton v-if="!open"
:aria-label="t('Open sidebar')"
class="app-sidebar__toggle"
:class="toggleClasses"
type="tertiary"
@click="$emit('update:open', true)">
<template #icon>
<!-- @slot Custom icon for the toggle button, defaults to the dock-right icon from MDI -->
<slot name="toggle-icon">
<IconDockRight :size="20" />
</slot>
</template>
</NcButton>
</Teleport>

<header :class="{
'app-sidebar-header--with-figure': hasFigure,
'app-sidebar-header--compact': compact,
}"
class="app-sidebar-header">
<!-- container for figure and description, allows easy switching to compact mode -->
<div class="app-sidebar-header__info">
<!-- sidebar header illustration/figure -->
<div v-if="hasFigure && !empty"
:class="{
'app-sidebar-header__figure--with-action': hasFigureClickListener
}"
class="app-sidebar-header__figure"
:style="{
backgroundImage: `url(${background})`
}"
tabindex="0"
@click="onFigureClick"
@keydown.enter="onFigureClick">
<slot class="app-sidebar-header__background" name="header" />
</div>

<!-- name -->
<div class="app-sidebar-header__name-container">
<div class="app-sidebar-header__mainname-container">
<!-- main name -->
<h2 v-show="!nameEditable"
:id="`app-sidebar-vue-${uid}__header`"
ref="header"
v-linkify="{text: name, linkify: linkifyName}"
:aria-label="title"
:title="title"
class="app-sidebar-header__mainname"
:tabindex="nameEditable ? 0 : -1"
@click.self="editName">
{{ name }}
</h2>
<template v-if="nameEditable">
<form v-click-outside="() => onSubmitName()"
class="app-sidebar-header__mainname-form"
@submit.prevent="onSubmitName">
<input ref="nameInput"
v-focus
class="app-sidebar-header__mainname-input"
type="text"
:placeholder="namePlaceholder"
:value="name"
@keydown.esc.stop="onDismissEditing"
@input="onNameInput">
<NcButton type="tertiary-no-background"
:aria-label="changeNameTranslated"
native-type="submit">
<template #icon>
<ArrowRight :size="20" />
</template>
</NcButton>
</form>
<!-- sidebar details -->
<div v-if="!empty"
:class="{
'app-sidebar-header__desc--with-tertiary-action': canStar || $slots['tertiary-actions'],
'app-sidebar-header__desc--editable': nameEditable && !subname,
'app-sidebar-header__desc--with-subname--editable': nameEditable && subname,
'app-sidebar-header__desc--without-actions': !$slots['secondary-actions'],
}"
class="app-sidebar-header__desc">
<!-- favourite icon -->
<div v-if="canStar || $slots['tertiary-actions']" class="app-sidebar-header__tertiary-actions">
<slot name="tertiary-actions">
<NcButton v-if="canStar"
:aria-label="favoriteTranslated"
:pressed="isStarred"
class="app-sidebar-header__star"
type="secondary"
@click.prevent="toggleStarred">
<template #icon>
<NcLoadingIcon v-if="starLoading" />
<Star v-else-if="isStarred" :size="20" />
<StarOutline v-else :size="20" />
</template>
<!-- header main menu -->
<NcActions v-if="$slots['secondary-actions']"
class="app-sidebar-header__menu"
:force-menu="forceMenu">
<slot name="secondary-actions" />
</NcActions>
</div>
<!-- secondary name -->
<p v-if="subname.trim() !== '' || $slots['subname']"
:title="subtitle || undefined"
class="app-sidebar-header__subname">
<!-- @slot Alternative to the `subname` prop can be used for more complex conent. It will be rendered within a `p` tag. -->
<slot name="subname">
{{ subname }}
</slot>
</p>
</NcButton>
</slot>
</div>

<!-- name -->
<div class="app-sidebar-header__name-container">
<div class="app-sidebar-header__mainname-container">
<!-- main name -->
<h2 v-show="!nameEditable"
:id="`app-sidebar-vue-${uid}__header`"
ref="header"
v-linkify="{text: name, linkify: linkifyName}"
:aria-label="title"
:title="title"
class="app-sidebar-header__mainname"
:tabindex="nameEditable ? 0 : -1"
@click.self="editName">
{{ name }}
</h2>
<template v-if="nameEditable">
<form v-click-outside="() => onSubmitName()"
class="app-sidebar-header__mainname-form"
@submit.prevent="onSubmitName">
<input ref="nameInput"
v-focus
class="app-sidebar-header__mainname-input"
type="text"
:placeholder="namePlaceholder"
:value="name"
@keydown.esc.stop="onDismissEditing"
@input="onNameInput">
<NcButton type="tertiary-no-background"
:aria-label="changeNameTranslated"
native-type="submit">
<template #icon>
<ArrowRight :size="20" />
</template>
</NcButton>
</form>
</template>
<!-- header main menu -->
<NcActions v-if="$slots['secondary-actions']"
class="app-sidebar-header__menu"
:force-menu="forceMenu">
<slot name="secondary-actions" />
</NcActions>
</div>
<!-- secondary name -->
<p v-if="subname.trim() !== '' || $slots['subname']"
:title="subtitle || undefined"
class="app-sidebar-header__subname">
<!-- @slot Alternative to the `subname` prop can be used for more complex conent. It will be rendered within a `p` tag. -->
<slot name="subname">
{{ subname }}
</slot>
</p>
</div>
</div>
</div>

<NcButton ref="closeButton"
:title="closeTranslated"
:aria-label="closeTranslated"
type="tertiary"
class="app-sidebar__close"
@click.prevent="closeSidebar">
<template #icon>
<Close :size="20" />
</template>
</NcButton>

<NcButton ref="closeButton"
:title="closeTranslated"
:aria-label="closeTranslated"
type="tertiary"
class="app-sidebar__close"
@click.prevent="closeSidebar">
<template #icon>
<Close :size="20" />
</template>
</NcButton>

<div v-if="$slots['description'] && !empty" class="app-sidebar-header__description">
<slot name="description" />
</div>
</header>
<div v-if="$slots['description'] && !empty" class="app-sidebar-header__description">
<slot name="description" />
</div>
</header>

<NcAppSidebarTabs v-show="!loading"
ref="tabs"
:active="active"
@update:active="onUpdateActive">
<slot />
</NcAppSidebarTabs>
<NcAppSidebarTabs v-show="!loading"
ref="tabs"
:active="active"
@update:active="onUpdateActive">
<slot />
</NcAppSidebarTabs>

<NcEmptyContent v-if="loading">
<template #icon>
<NcLoadingIcon :size="64" />
</template>
</NcEmptyContent>
</aside>
</transition>
</Fragment>
<NcEmptyContent v-if="loading">
<template #icon>
<NcLoadingIcon :size="64" />
</template>
</NcEmptyContent>
</aside>
</transition>
</template>

<script>
// TODO: This is built-in for vue3 just drop the import
import { Portal as Teleport } from '@linusborg/vue-simple-portal'

import NcAppSidebarTabs from './NcAppSidebarTabs.vue'
import NcActions from '../NcActions/index.js'
import NcLoadingIcon from '../NcLoadingIcon/index.js'
Expand All @@ -634,7 +641,6 @@ import GenRandomId from '../../utils/GenRandomId.js'
import { getTrapStack } from '../../utils/focusTrap.js'
import { t } from '../../l10n.js'

import { Fragment } from 'vue-frag'
import ArrowRight from 'vue-material-design-icons/ArrowRight.vue'
import Close from 'vue-material-design-icons/Close.vue'
import IconDockRight from 'vue-material-design-icons/DockRight.vue'
Expand All @@ -648,7 +654,7 @@ export default {
name: 'NcAppSidebar',

components: {
Fragment,
Teleport,
NcActions,
NcAppSidebarTabs,
ArrowRight,
Expand Down Expand Up @@ -1151,7 +1157,6 @@ $top-buttons-spacing: $app-navigation-padding; // align with app navigation
*/
.app-sidebar {
--app-sidebar-width: clamp(300px, 27vw, 500px);
--app-sidebar-padding: #{$app-navigation-padding};
width: var(--app-sidebar-width);

z-index: 1500;
Expand Down

0 comments on commit 5a12f6b

Please sign in to comment.