Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/formats/html/bslib/components/scss/sidebar.scss
12924 views
$bslib-sidebar-bg: rgba(var(--bs-emphasis-color-rgb, 0,0,0), 0.05) !default;
$bslib-sidebar-fg: var(--bs-emphasis-color, black) !default;
$bslib-sidebar-toggle-bg: rgba(var(--bs-emphasis-color-rgb, 0,0,0), 0.1) !default;
$bslib-sidebar-border: var(--bs-card-border-width, #{$card-border-width}) solid var(--bs-card-border-color, #{$card-border-color}) !default;
$bslib-sidebar-column-sidebar: Min(calc(100% - var(--bslib-sidebar-icon-size)), var(--bslib-sidebar-width, 250px));


.bslib-sidebar-layout {
  --bslib-sidebar-transition-duration: 500ms;
  --bslib-sidebar-transition-easing-x: cubic-bezier(0.8, 0.78, 0.22, 1.07);
  --bslib-sidebar-border: #{$bslib-sidebar-border};
  --bslib-sidebar-border-radius: var(--bs-border-radius);
  --bslib-sidebar-vert-border: #{$bslib-sidebar-border};
  --bslib-sidebar-bg: #{$bslib-sidebar-bg};
  --bslib-sidebar-fg: #{$bslib-sidebar-fg};
  --bslib-sidebar-main-fg: var(--bs-card-color, var(--bs-body-color));
  --bslib-sidebar-main-bg: var(--bs-card-bg, var(--bs-body-bg));
  --bslib-sidebar-toggle-bg: #{$bslib-sidebar-toggle-bg};
  --bslib-sidebar-padding: calc(var(--bslib-spacer) * 1.5);
  --bslib-sidebar-icon-size: var(--bslib-spacer, 1rem);
  --bslib-sidebar-icon-button-size: calc(var(--bslib-sidebar-icon-size, 1rem) * 2);
  --bslib-sidebar-padding-icon: calc(var(--bslib-sidebar-icon-button-size, 2rem) * 1.5);
  // We intentionally don't give a value here, but it could be set by someone else
  // --bslib-collapse-toggle-border: ;
  --bslib-collapse-toggle-border-radius: var(--bs-border-radius, #{$border-radius});
  --bslib-collapse-toggle-transform: 0deg;
  --bslib-sidebar-toggle-transition-easing: cubic-bezier(1, 0, 0, 1);
  --bslib-collapse-toggle-right-transform: 180deg;
  --bslib-sidebar-column-main: minmax(0, 1fr);
  
  display: grid !important;
  grid-template-columns: $bslib-sidebar-column-sidebar var(--bslib-sidebar-column-main);
  position: relative;

  @include transition(grid-template-columns ease-in-out var(--bslib-sidebar-transition-duration));

  border: var(--bslib-sidebar-border);
  border-radius: var(--bslib-sidebar-border-radius);

  &[data-bslib-sidebar-border="false"] {
    border: none;
  }
  &[data-bslib-sidebar-border-radius="false"] {
    border-radius: initial;
  }

  > .main, > .sidebar {
    grid-row: 1 / 2;
    border-radius: inherit;
    overflow: auto;
  }

  > .main {
    grid-column: 2 / 3;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    padding: var(--bslib-sidebar-padding);
    transition: padding var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration);
    color: var(--bslib-sidebar-main-fg);
    background-color: var(--bslib-sidebar-main-bg);
  }

  > .sidebar {
    grid-column: 1 / 2;
    width: 100%;
    height: 100%;
    border-right: var(--bslib-sidebar-vert-border);
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    color: var(--bslib-sidebar-fg);
    background-color: var(--bslib-sidebar-bg);
    backdrop-filter: blur(5px);

    > .sidebar-content {
      display: flex;
      flex-direction: column;
      gap: var(--bslib-spacer, 1rem);
      padding: var(--bslib-sidebar-padding);
      // Add space for the toggle button (removed if sidebar is always open)
      padding-top: var(--bslib-sidebar-padding-icon);

      > :last-child:not(.sidebar-title) {
        // Remove margin-bottom from the last item because sidebar has padding.
        // We make an exception for .sidebar-title because it might be common to
        // have a title and bare text nodes (that don't count as children).
        margin-bottom: 0;
      }

      > .accordion {
        margin-left: calc(-1 * var(--bslib-sidebar-padding));
        margin-right: calc(-1 * var(--bslib-sidebar-padding));
        &:last-child {
            margin-bottom: calc(-1 * var(--bslib-sidebar-padding));
        }
        &:not(:last-child) {
          margin-bottom: $spacer;
        }
        .accordion-body {
          display: flex;
          flex-direction: column;
        }
      }

      // Accordions in sidebars are made flush with `.accordion-flush`, which
      // removes the top and bottom border of the first or last accordion item.
      // But in our usage, the accordions might not be the first or last item in
      // the sidebar. In that case, it's better to keep the top/bottom borders.
      > .accordion:not(:first-child) .accordion-item:first-child {
        border-top: var(--#{$prefix}accordion-border-width) solid var(--#{$prefix}accordion-border-color);
      }
      > .accordion:not(:last-child) .accordion-item:last-child {
        border-bottom: var(--#{$prefix}accordion-border-width) solid var(--#{$prefix}accordion-border-color);
      }

      &.has-accordion > .sidebar-title {
        border-bottom: none;
        padding-bottom: 0;
      }
    }

    .shiny-input-container {
      width: 100%;
    }
  }

  &[data-bslib-sidebar-open="always"] {
    > .sidebar > .sidebar-content {
      // Always-open sidebars don't have a toggle & can use normal top padding
      padding-top: var(--bslib-sidebar-padding);
    }
  }

  > .collapse-toggle {
    grid-row: 1 / 2;
    grid-column: 1 / 2;
    display: inline-flex;
    align-items: center;
    position: absolute;
    right: calc(var(--bslib-sidebar-icon-size));
    top: calc(var(--bslib-sidebar-icon-size, 1rem) / 2);
    border: none;
    border-radius: var(--bslib-collapse-toggle-border-radius);
    height: var(--bslib-sidebar-icon-button-size, 2rem);
    width: var(--bslib-sidebar-icon-button-size, 2rem);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    color: var(--bslib-sidebar-fg);
    background-color: unset; // don't take `button` background color
    transition:
      color var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),
      top var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),
      right var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),
      left var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration);

    &:hover {
      background-color: var(--bslib-sidebar-toggle-bg);
    }

    > .collapse-icon {
      opacity: 0.8;
      width: var(--bslib-sidebar-icon-size);
      height: var(--bslib-sidebar-icon-size);
      transform: rotateY(var(--bslib-collapse-toggle-transform));
      // N.B. since mobile view won't trigger a transition of grid-template-columns,
      // we transition this toggle to ensure _some_ transition event always happens.
      transition: transform var(--bslib-sidebar-toggle-transition-easing) var(--bslib-sidebar-transition-duration);
    }

    &:hover > .collapse-icon {
      opacity: 1;
    }
  }

  .sidebar-title {
    font-size: $font-size-base * 1.25;
    line-height: $line-height-sm;
    margin-top: 0;
    margin-bottom: $spacer;
    padding-bottom: $spacer;
    border-bottom: var(--bslib-sidebar-border);
  }

  &.sidebar-right {
    grid-template-columns: var(--bslib-sidebar-column-main) $bslib-sidebar-column-sidebar;

    > .main {
      grid-column: 1 / 2;
      border-top-right-radius: 0;
      border-bottom-right-radius: 0;
      border-top-left-radius: inherit;
      border-bottom-left-radius: inherit;
    }

    > .sidebar {
      grid-column: 2 / 3;
      border-right: none;
      border-left: var(--bslib-sidebar-vert-border);
      border-top-left-radius: 0;
      border-bottom-left-radius: 0;
    }

    > .collapse-toggle {
      grid-column: 2 / 3;
      left: var(--bslib-sidebar-icon-size);
      right: unset;
      border: var(--bslib-collapse-toggle-border);
      > .collapse-icon {
        transform: rotateY(var(--bslib-collapse-toggle-right-transform));
      }
    }
  }

  &.sidebar-collapsed {
    --bslib-collapse-toggle-transform: 180deg;
    --bslib-collapse-toggle-right-transform: 0deg;
    --bslib-sidebar-vert-border: none;

    grid-template-columns: 0 minmax(0, 1fr);

    &.sidebar-right {
      grid-template-columns: minmax(0, 1fr) 0;
    }

    // Hide the sidebar contents after it's done transitioning so that
    // those contents don't impact the overall layout (i.e., height)
    &:not(.transitioning) {
      // Putting `display:none` on .sidebar would change the number of columns
      // in the grid, and I don't think we can transition between those states
      > .sidebar > * {
        display: none;
      }
    }

    > .main {
      border-radius: inherit;
    }

    &:not(.sidebar-right) > .main {
      padding-left: var(--bslib-sidebar-padding-icon);
    }
    &.sidebar-right > .main {
      padding-right: var(--bslib-sidebar-padding-icon);
    }

    > .collapse-toggle {
      color: var(--bslib-sidebar-main-fg);
      // The CSS variable (set via JS) is here to help avoid overlapping toggles
      top: calc(
          var(--bslib-sidebar-overlap-counter, 0) *
          calc(var(--bslib-sidebar-icon-size) +
          var(--bslib-sidebar-padding)
        ) + var(--bslib-sidebar-icon-size, 1rem) / 2);
      right: calc(-2.5 * var(--bslib-sidebar-icon-size) - var(--bs-card-border-width, 1px));
    }

    &.sidebar-right > .collapse-toggle {
      left: calc(-2.5 * var(--bslib-sidebar-icon-size) - var(--bs-card-border-width, 1px));
      right: unset;
    }
  }
}

@include media-breakpoint-up(sm) {
  // hide sidebar content while we transition the parent .sidebar on desktop
  // (on mobile the reveal happens immediately)
  .bslib-sidebar-layout.transitioning > .sidebar > .sidebar-content {
    display: none;
  }
}

@include media-breakpoint-down(sm) {
  .bslib-sidebar-layout {
    // Tell sidebar init js we're on mobile for `sidebar(open = "desktop")`
    &[data-bslib-sidebar-open="desktop"] {
      --bslib-sidebar-js-init-collapsed: true;
    }

    &, &.sidebar-right {
      // Remove sidebar borders in mobile view (except always-open, added below)
      > .sidebar { border: none; }

      // Main area takes up entire layout area to avoid layout shift when
      // sidebar is expanded as an overlay.
      > .main {
        grid-column: 1 / 3;
      }
    }

    // Always open sidebars become "flow" layouts in mobile view
    &[data-bslib-sidebar-open="always"] {
      display: block !important;
      > .sidebar {
        max-height: var(--bslib-sidebar-max-height-mobile);
        overflow-y: auto;
        border-top: var(--bslib-sidebar-vert-border);
      }
    }

    &:not([data-bslib-sidebar-open="always"]) {
      // Sidebar layer has to be lifted up to cover other (nested) sidebars
      &:not(.sidebar-collapsed) {
        > .sidebar { z-index: 1; }
        > .collapse-toggle { z-index: 1; }
      }

      // Either sidebar or main area take up entire layout depending on state
      $full-closed: 100% 0;
      $closed-full: 0 100%;
      grid-template-columns: $full-closed;
      &.sidebar-right {
        grid-template-columns: $closed-full;
      }

      &.sidebar-collapsed {
        grid-template-columns: $closed-full;
        &.sidebar-right {
          grid-template-columns: $full-closed;
        }
      }


      // Keep padding on main contents when sidebar is expanded (avoid shifts)
      &:not(.sidebar-right) > .main {
        padding-left: var(--bslib-sidebar-padding-icon);
      }
      &.sidebar-right > .main {
        padding-right: var(--bslib-sidebar-padding-icon);
      }

      // Make main contents transparent while sidebar is expanded
      > .main {
        opacity: 0;
        transition: opacity var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration);
      }
      &.sidebar-collapsed > .main {
        opacity: 1;
      }
    }
  }
}