Skip to content

SidebarLayout

You incorporate a sidebar in the slider and CRM entity tab embedding. Overall, it's stylish, trendy, and youthful

Usage

Layout

The component must be used as a layout.

styling

The styling process is described on the page Theme

Details
vue
<script setup lang="ts">
import { ref } from 'vue'
import type { SidebarLayoutInstance, SidebarLayoutProps } from '@bitrix24/b24ui-nuxt'
import RocketIcon from '@bitrix24/b24icons-vue/outline/RocketIcon'
import OpenIn50Icon from '@bitrix24/b24icons-vue/actions/OpenIn50Icon'
import BusinesProcessStagesIcon from '@bitrix24/b24icons-vue/outline/BusinesProcessStagesIcon'
import MoreMIcon from '@bitrix24/b24icons-vue/outline/MoreMIcon'

const checkedUseLightContent = ref(true)
const sidebarLayoutRef = ref<SidebarLayoutInstance | null>(null)

// Manage loading state
const handleAction = async () => {
  if (sidebarLayoutRef.value) {
    sidebarLayoutRef.value.setLoading(true)

    try {
      // Performing an asynchronous operation
      await new Promise(resolve => setTimeout(resolve, 2_000))
    } finally {
      sidebarLayoutRef.value.setLoading(false)
    }
  }
}

// This is for demonstration purposes only
const customUIForDemo = {
  root: 'h-[400px] min-h-[400px]',
  loadingWrapper: 'h-[400px] min-h-[400px]',
  sidebar: 'relative z-[0]',
  contentWrapper: 'lg:pl-0'
} as SidebarLayoutProps['b24ui']
</script>

<template>
  <B24SidebarLayout
    ref="sidebarLayoutRef"
    :use-light-content="checkedUseLightContent"
    :b24ui="customUIForDemo"
  >
    <template #sidebar>
      <!-- Your sidebar -->
      <B24SidebarHeader>
        <!-- Navigation header -->
        <div class="h-full flex items-center relative my-0 ps-[25px] pe-xs rtl:pe-[25px]">
          <ProseH4 class="font-medium mb-0">
            SideBar
          </ProseH4>
        </div>
      </B24SidebarHeader>
      <B24SidebarBody>
        <!-- Navigation elements -->
        <B24SidebarSection>
          <B24NavigationMenu
            :items="[{ label: 'Page 1', type: 'trigger', active: true }, { label: 'Page 2', type: 'trigger' }]"
            orientation="vertical"
          />
        </B24SidebarSection>

        <B24SidebarSpacer />
        <B24SidebarSection>
          <B24NavigationMenu
            :items="[{ label: 'Page 3', type: 'trigger' }, { label: 'Page 4', type: 'trigger' }]"
            orientation="vertical"
          />
        </B24SidebarSection>
      </B24SidebarBody>
      <B24SidebarFooter>
        <!-- Navigation footer -->
        <B24SidebarSection>
          <B24Link
            class="text-sm mb-2 flex flex-row items-center justify-between"
            to="https://bitrix24.github.io/b24ui/"
            target="_blank"
          >
            <div>@bitrix24/b24ui</div>
            <OpenIn50Icon class="size-4" />
          </B24Link>
          <B24Button
            block
            label="Use our Vue starter"
            color="air-boost"
            size="sm"
            loading-auto
            :icon="RocketIcon"
            to="https://bitrix24.github.io/b24ui/"
            target="_blank"
          />
        </B24SidebarSection>
      </B24SidebarFooter>
    </template>

    <template #navbar>
      <!-- Your navigation bar -->
      <B24NavbarSection class="hidden sm:inline-flex">
        <B24NavigationMenu
          :items="[{ label: 'Page 1', type: 'trigger', active: true }, { label: 'Page 2', type: 'trigger' }]"
          orientation="horizontal"
        />
      </B24NavbarSection>
      <B24NavbarSpacer />
      <B24NavbarSection class="flex-row items-center justify-start gap-4">
        <B24DropdownMenu
          arrow
          :items="[{ label: 'Value 1' }, { label: 'Value 2' }]"
        >
          <B24Button
            color="air-secondary-accent"
            size="xs"
            rounded
            use-dropdown
            label="Action"
          />
        </B24DropdownMenu>
        <B24Switch
          v-model="checkedUseLightContent"
          size="xs"
        />
        <B24Button
          label="Reload"
          color="air-secondary-accent"
          rounded
          size="xs"
          loading-auto
          @click="handleAction"
        />
      </B24NavbarSection>
    </template>

    <template #content-top>
      <div class="w-full flex flex-col gap-[20px]">
        <div class="backdrop-blur-sm backdrop-brightness-110 px-[15px] py-[10px] flex flex-col items-start justify-between gap-[20px]">
          <div class="w-full flex flex-row items-center justify-between gap-[20px]">
            <div class="flex-1 flex flex-row items-center justify-end gap-[12px]">
              <B24Avatar
                :icon="BusinesProcessStagesIcon"
                alt="Workflows"
                size="xl"
                :b24ui="{
                  root: 'bg-(--ui-color-primary-alt)',
                  icon: 'size-[36px] text-(--ui-color-palette-white-base)'
                }"
              />
              <div class="flex-1">
                <ProseH1 class="text-(--b24ui-typography-label-color) leading-[29px] font-(--ui-font-weight-light)">
                  Workflows
                </ProseH1>
              </div>
            </div>
            <div class="flex-1 hidden sm:flex flex-row items-center justify-end gap-[12px]">
              <B24DropdownMenu
                :items="[{ label: 'Value 1' }, { label: 'Value 2' }]"
                arrow
                :content="{ side: 'bottom', align: 'center' }"
              >
                <B24Button size="sm" :icon="MoreMIcon" color="air-secondary-accent" />
              </B24DropdownMenu>
            </div>
          </div>
          <div>
            <MockSidebarLayoutMenu orientation="horizontal" />
          </div>
        </div>
        <!-- Page Title -->
        <ProseH2 class="font-semibold mb-0">
          Some title
        </ProseH2>
      </div>
    </template>

    <template #content-actions>
      <!-- Actions on page -->
      <B24Button
        color="air-secondary-accent"
        label="Action"
        loading-auto
        @click="handleAction"
      />
    </template>

    <!-- Main content -->
    <div>
      <ProseP>Your main content goes here</ProseP>
    </div>

    <template #content-bottom>
      <!-- Bottom of page -->
      <ProseP small accent="less-more" class="px-[22px] pb-[2px]">
        Footer or additional information
      </ProseP>
    </template>
  </B24SidebarLayout>
</template>

INFO

On mobile devices the sidebar is hidden and accessible via slideover

Inner

INFO

If you need to manage loading state in child components, you should use useSidebarLayout

If the component needs to be placed inside the content, then the props isInner must be set.

You can control the content area scrollbar using the props offContentScrollbar.

Details
vue
<script setup lang="ts">
import { ref } from 'vue'
import type { SidebarLayoutInstance, SidebarLayoutProps } from '@bitrix24/b24ui-nuxt'
import NotificationIcon from '@bitrix24/b24icons-vue/outline/NotificationIcon'
import SidebarLayoutInnerAction from './SidebarLayoutInnerAction.vue'

const sidebarLayoutRef = ref<SidebarLayoutInstance | null>(null)

// Manage loading state
const handleAction = async () => {
  if (sidebarLayoutRef.value) {
    sidebarLayoutRef.value.setLoading(true)

    try {
      // Performing an asynchronous operation
      await new Promise(resolve => setTimeout(resolve, 4_000))
    } finally {
      sidebarLayoutRef.value.setLoading(false)
    }
  }
}

// This is for demonstration purposes only
const customUIForDemo = {
  root: 'h-[400px] min-h-[400px]',
  loadingWrapper: 'h-[400px] min-h-[400px]',
  sidebar: 'relative z-[0]',
  contentWrapper: 'lg:pl-0'
} as SidebarLayoutProps['b24ui']

// This is for demonstration purposes only
const customUIInnerForDemo = {
  loadingWrapper: 'h-[400px] min-h-[400px]'
} as SidebarLayoutProps['b24ui']
</script>

<template>
  <B24SidebarLayout
    ref="sidebarLayoutRef"
    :use-light-content="false"
    :b24ui="customUIForDemo"
  >
    <template #navbar>
      <!-- Your navigation bar -->
      <B24NavbarSection class="hidden sm:inline-flex">
        <B24NavigationMenu
          :items="[{ label: 'Page 1', type: 'trigger', active: true }, { label: 'Page 2', type: 'trigger' }]"
          orientation="horizontal"
        />
      </B24NavbarSection>
      <B24NavbarSpacer />
      <B24NavbarSection>
        <B24Button
          label="Reload"
          color="air-secondary-accent"
          rounded
          size="xs"
          loading-auto
          @click="handleAction"
        />
      </B24NavbarSection>
    </template>

    <!-- Main content -->
    <div class="relative h-full rounded-t-[12px] overflow-hidden">
      <div class="absolute size-full rounded-t-[12px]">
        <B24SidebarLayout
          :use-light-content="false"
          is-inner
          off-content-scrollbar
          :b24ui="{
            ...customUIInnerForDemo,
            root: [
              'edge-light',
              '[--leftmenu-bg-expanded:#eef2f4!important]',
              '[--air-theme-bg-color:#eef2f4]',
              '[--air-theme-bg-size:240px_240px]',
              '[--air-theme-bg-repeat:repeat]',
              '[--air-theme-bg-position:0_0]',
              '[--air-theme-bg-attachment:fixed]',
              '[--air-theme-bg-image:url(/bg/edge-light-v1.svg)]',
              '[--air-theme-bg-image-blurred:url(/bg/edge-light-v1-blurred.webp)]'
            ].join(' '),
            contentWrapper: [
              'bg-[url(/bg/pattern-1.png)] bg-cover bg-center bg-fixed bg-no-repeat bg-[#799fe1]/10',
              'p-0 px-0 ps-0 pe-0 lg:p-0 lg:px-0 lg:ps-0 lg:pe-0 '
            ].join(' '),
            containerWrapper: 'h-full relative',
            containerWrapperInner: 'flex flex-col items-center justify-center'
          } as SidebarLayoutProps['b24ui']"
        >
          <template #sidebar>
            <B24SidebarHeader>
              <div class="text-[#f8f7f7] h-full flex items-center relative my-0 ps-[25px] pe-xs rtl:pe-[25px]">
                <ProseH6 class="font-medium mb-0">
                  Settings
                </ProseH6>
              </div>
            </B24SidebarHeader>
            <B24SidebarBody>
              <div class="space-y-6 px-[25px]">
                <B24Switch label="Expand" class="mt-1" />
                <B24Switch label="isShowProgress" class="mt-1" />
              </div>
            </B24SidebarBody>
          </template>
          <template #navbar>
            <ProseH4 class="font-medium mb-0">
              Demo
            </ProseH4>
            <B24NavbarSpacer />
          </template>
          <template #default>
            <div
              class="text-(--ui-color-design-filled-market-content) max-w-[550px] mx-(--content-area-shift) px-[60px] py-[20px] rounded-[24px] bg-[#525c69]/20 flex flex-col items-center justify-center gap-[20px]"
            >
              <B24Avatar
                :icon="NotificationIcon"
                alt="Toast"
                size="xl"
                :b24ui="{
                  root: 'bg-transparent ring-2 ring-(--ui-color-design-filled-market-content)/50',
                  icon: 'size-[44px] text-(--ui-color-design-filled-market-content)'
                }"
              />
              <ProseH2 class="text-center text-(--ui-color-design-filled-market-content) leading-[29px] mb-0">
                Some text
              </ProseH2>
              <div class="flex flex-col sm:flex-row items-center justify-center gap-[15px]">
                <SidebarLayoutInnerAction />
              </div>
            </div>
          </template>
        </B24SidebarLayout>
      </div>
    </div>
  </B24SidebarLayout>
</template>

SidebarLayoutInnerAction

vue
<script setup lang="ts">
const contextSidebar = useSidebarLayout()

const handleActionByContext = async () => {
  if (contextSidebar) {
    contextSidebar.setLoading(true)
    try {
      // Performing an asynchronous operation
      await new Promise(resolve => setTimeout(resolve, 2_000))
    } finally {
      contextSidebar.setLoading(false)
    }
  }
}
</script>

<template>
  <B24Button label="Action" color="air-primary" @click="handleActionByContext" />
</template>

Slideover

It should be understood that the Slideover component displays data using the SidebarLayout component.

Details
vue
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
import type { SlideoverInstance } from '@bitrix24/b24ui-nuxt'
import TrendUpIcon from '@bitrix24/b24icons-vue/outline/TrendUpIcon'
import TrendDownIcon from '@bitrix24/b24icons-vue/outline/TrendDownIcon'
import BusinesProcessStagesIcon from '@bitrix24/b24icons-vue/outline/BusinesProcessStagesIcon'

const openListItem = ref(false)

// Manage loading state
const currentSlideoverRef = useTemplateRef<SlideoverInstance>('currentSlideoverRef')
const handleAction = async () => {
  if (!currentSlideoverRef.value) {
    return
  }

  try {
    currentSlideoverRef.value.setSidebarLoading(true)
    await new Promise(resolve => setTimeout(resolve, 2_000))
  } finally {
    currentSlideoverRef.value.setSidebarLoading(false)
  }
}
</script>

<template>
  <B24Slideover
    ref="currentSlideoverRef"
    title="Some list"
    description="Some description"
    :use-light-content="false"
    :b24ui="{
      content: 'sm:max-w-[970px] sm:top-[275px] sm:max-h-[calc(100%-275px)]',
      sidebarLayoutRoot: [
        'edge-dark',
        'edge-dark:[--air-theme-bg-color:#7c235b]',
        'edge-dark:[--air-theme-bg-size:cover]',
        'edge-dark:[--air-theme-bg-repeat:no-repeat]',
        'edge-dark:[--air-theme-bg-position:0_0]',
        'edge-dark:[--air-theme-bg-attachment:local]',
        'edge-dark:[--air-theme-bg-image:url(/bg/edge-dark-v2.jpg)]',
        'edge-dark:[--air-theme-bg-image-blurred:url(/bg/edge-dark-v2-blurred.webp)]'
      ].join(' '),
      sidebarLayoutLoadingIcon: 'text-(--ui-color-gray-70)'
    }"
  >
    <B24Button label="Some list" />
    <template #body>
      <div class="light px-0.5 rounded-(--ui-border-radius-md) bg-(--ui-color-background-primary)">
        <B24TableWrapper
          row-hover
          class="overflow-x-auto w-full"
        >
          <table>
            <!-- head -->
            <thead>
              <tr>
                <th>#</th>
                <th>Company</th>
                <th>Status</th>
                <th>Amount (USD)</th>
              </tr>
            </thead>
            <tbody>
              <!-- row 1 -->
              <tr>
                <th>1</th>
                <td><B24Link @click="openListItem = true">Tech Innovators Inc.</B24Link></td>
                <td><B24Badge label="Proposal Sent" use-link use-close /></td>
                <td>50,000</td>
              </tr>
              <!-- row 2 -->
              <tr>
                <th>2</th>
                <td><B24Link @click="openListItem = true">Global Solutions Ltd.</B24Link></td>
                <td><B24Badge label="Negotiation" use-link inverted use-close /></td>
                <td>120,000</td>
              </tr>
              <!-- row 3 -->
              <tr>
                <th>3</th>
                <td><B24Link @click="openListItem = true">Future Enterprises</B24Link></td>
                <td><B24Chip standalone color="air-primary-warning" text="Contract Signed" size="lg" :trailing-icon="TrendUpIcon" /></td>
                <td>200,000</td>
              </tr>
              <!-- row 4 -->
              <tr>
                <th>4</th>
                <td><B24Link @click="openListItem = true">Bright Ideas Co.</B24Link></td>
                <td>
                  <B24Chip
                    standalone
                    text="Initial Contact"
                    color="air-primary-alert"
                    size="lg"
                    inverted
                    :trailing-icon="TrendDownIcon"
                  />
                </td>
                <td>15,000</td>
              </tr>
              <!-- row 5 -->
              <tr>
                <th>5</th>
                <td><B24Link @click="openListItem = true">NextGen Technologies</B24Link></td>
                <td>
                  <B24Chip
                    standalone
                    text="Important"
                    size="lg"
                    color="air-primary-alert"
                    :b24ui="{ base: 'style-filled-boost' }"
                  />
                </td>
                <td>300,000</td>
              </tr>
            </tbody>
            <tfoot>
              <tr>
                <th colspan="3" class="text-right">
                  Total:
                </th>
                <td>
                  685,000
                </td>
              </tr>
            </tfoot>
          </table>
        </B24TableWrapper>
      </div>
    </template>

    <template #footer>
      <B24Button
        label="Reload"
        color="air-primary"
        loading-auto
        @click="handleAction"
      />
      <B24ModalDialogClose>
        <B24Button label="Cancel" color="air-tertiary" />
      </B24ModalDialogClose>
    </template>
  </B24Slideover>
  <B24Slideover
    v-model:open="openListItem"
    :close="{ label: 'Item' }"
    title="Item"
    description="Some description"
    :use-light-content="false"
    :b24ui="{
      content: 'sm:max-w-[965px] sm:top-[375px] sm:max-h-[calc(100%-375px)]',
      body: 'relative',
      sidebarLayoutRoot: [
        'edge-light',
        'edge-light:[--air-theme-bg-color:#eef2f4]',
        'edge-light:[--air-theme-bg-size:auto]',
        'edge-light:[--air-theme-bg-repeat:no-repeat]',
        'edge-light:[--air-theme-bg-position:0_0]',
        'edge-light:[--air-theme-bg-attachment:local]',
        'edge-light:[--air-theme-bg-image:url(/bg/slider-ring-blurred.webp)]',
        'edge-light:[--air-theme-bg-image-blurred:url(/bg/slider-ring-blurred.webp)]'
      ].join(' ')
    }"
  >
    <template #header>
      <div
        class="w-full pt-(--ui-space-inset-md2) pb-[calc(var(--ui-space-inset-md2)+10px)] px-(--ui-space-inset-lg) rounded-(--ui-border-radius-3xl) bg-(--ui-color-background-primary)/80 flex flex-row items-center justify-between gap-[20px]"
      >
        <B24Avatar
          :icon="BusinesProcessStagesIcon"
          alt="Workflows"
          size="2xl"
          :b24ui="{
            root: 'bg-(--ui-color-primary)',
            icon: 'size-[48px] text-(--ui-color-palette-white-base)'
          }"
        />
        <div class="flex-1">
          <ProseH1 class="text-(--ui-color-text-primary) leading-[29px] font-(--ui-font-weight-light)">
            Workflows
          </ProseH1>
          <ProseP small accent="less">
            Automate your workflows, control every stage and manage workflows from your mobile.
          </ProseP>
        </div>
      </div>
    </template>
    <template #body>
      <Placeholder class="w-full h-[300px]" />
    </template>
    <template #footer>
      <B24ModalDialogClose>
        <B24Button label="Continue" color="air-primary" />
      </B24ModalDialogClose>
      <B24ModalDialogClose>
        <B24Button label="Back" color="air-tertiary" />
      </B24ModalDialogClose>
    </template>
  </B24Slideover>
</template>

TIP

Many examples can be found on the playground and also seen in the demo version.

API

Props

Prop Default Type
as"div"any
The element or component this component should render as.
useLightContenttrueboolean
The content is placed on a light background.
isInnerfalseboolean
Set inner mode. Use in slider, modal and etc
offContentScrollbarfalseboolean
Off scrollbar control of the content area in inner mode.
b24uiPick<{ root?: ClassNameValue; sidebar?: ClassNameValue; sidebarSlideoverContainer?: ClassNameValue; sidebarSlideover?: ClassNameValue; sidebarSlideoverBtnClose?: ClassNameValue; contentWrapper?: ClassNameValue; header?: ClassNameValue; headerMenuIcon?: ClassNameValue; headerWrapper?: ClassNameValue; container?: ClassNameValue; containerWrapper?: ClassNameValue; pageTopWrapper?: ClassNameValue; pageActionsWrapper?: ClassNameValue; containerWrapperInner?: ClassNameValue; pageBottomWrapper?: ClassNameValue; loadingWrapper?: ClassNameValue; loadingIcon?: ClassNameValue; }, "loadingIcon" | "root" | "header" | "container" | "sidebar" | "sidebarSlideoverContainer" | "sidebarSlideover" | "sidebarSlideoverBtnClose" | "contentWrapper" | "headerMenuIcon" | "headerWrapper" | "containerWrapper" | "pageTopWrapper" | "pageActionsWrapper" | "containerWrapperInner" | "pageBottomWrapper" | "loadingWrapper">

Slots

Slot Type
sidebar{ handleClick: () => void; isLoading: boolean; }
Left menu.
navbar{ handleClick: () => void; isLoading: boolean; }
Top menu.
content-top{ isLoading: boolean; }
Content above the page. Used for title, filter, etc.
content-actions{ isLoading: boolean; }
Content above the page. Use for show actions.
default{ isLoading: boolean; }
The page content.
content-bottom{ isLoading: boolean; }
Content below the page.
loading{ isLoading: boolean; }
Loading state. You need to use `useSidebarLayout` to control it.

Expose

When accessing the component via a template ref, you can use the following:

NameDescription
api: SidebarLayoutApiManaging the loading state
isLoading: booleanLoading status.
setLoading(value: boolean)Sets the loading state for the current component.
setRootLoading(value: boolean)Sets the root loading state (applies to all nested components).

Released under the MIT License.