Skip to content

HorizontalAppBar Component Code

Dependencies

This component requires:

  • Vue 3 with Composition API

Full Component Code

vue
<script setup>
import { ref, computed } from 'vue';

const props = defineProps({
  logo: {
    type: String,
    default:
      'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiB2aWV3Qm94PSIwIDAgMjAwIDIwMCI+PHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiIGZpbGw9IiNlZWUiLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjQ4IiBmaWxsPSIjNjY2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+TE9HTzwvdGV4dD48L3N2Zz4=',
  },
  roundLogo: {
    type: Boolean,
    default: true,
  },
  title: {
    type: String,
    default: '',
  },
  backgroundColor: {
    type: String,
    default: '#ffffff',
  },
  textColor: {
    type: String,
    default: '#444444',
  },
  height: {
    type: String,
    default: '4rem',
  },
  elevation: {
    type: Boolean,
    default: true,
  },
  position: {
    type: String,
    default: 'fixed', // static | relative | absolute | fixed | sticky
  },
  top: {
    type: [String, Number],
    default: '0rem',
  },
  zIndex: {
    type: [String, Number],
    default: 1000,
  },
});

const logo = ref(props.logo);

const topWithUnit = computed(() => (typeof props.top === 'number' ? `${props.top}rem` : props.top));

const positionClass = computed(() => {
  switch (props.position) {
    case 'fixed':
      return 'position-fixed';
    case 'sticky':
      return 'position-sticky';
    case 'absolute':
      return 'position-absolute';
    case 'relative':
      return 'position-relative';
    default:
      return 'position-static';
  }
});
</script>
<template>
  <div
    class="horizontal-app-bar"
    :class="positionClass"
    :style="{
      backgroundColor: backgroundColor,
      height: height,
      color: textColor,
      top: topWithUnit,
      zIndex: zIndex,
      boxShadow: elevation ? '0 0.125rem 0.25rem rgba(0,0,0,0.1)' : 'none',
    }"
    role="banner"
  >
    <slot name="left">
      <div class="left-container">
        <slot name="logo">
          <div
            v-if="logo"
            class="logo-container"
            :class="{ 'round-logo': roundLogo }"
            role="img"
            aria-label="Application logo"
          >
            <img :src="logo" alt="Logo" class="logo" />
          </div>
        </slot>
        <slot name="title">
          <div v-if="title" class="title">
            {{ title }}
          </div>
        </slot>
        <slot name="left-content"></slot>
      </div>
    </slot>

    <slot name="center">
      <div class="center-container">
        <slot name="center-content"></slot>
      </div>
    </slot>

    <slot name="right">
      <div class="right-container">
        <slot name="right-content"></slot>
      </div>
    </slot>

  </div>
</template>

<style scoped lang="scss">
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.horizontal-app-bar {
  display: grid;
  grid-template-columns: max-content 1fr max-content;
  align-items: center;
  padding: 0 1rem;
  width: 100%;
  transition: all 0.3s ease;

  .left-container {
    display: flex;
    align-items: center;
    gap: 1rem;
    min-width: 0;

    .logo-container {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 2.5rem;
      height: 2.5rem;
      cursor: pointer;
      overflow: hidden;
      transition: transform 0.2s ease;

      &:hover {
        transform: scale(1.05);
      }

      &.round-logo {
        border-radius: 50%;
      }

      img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        transition: transform 0.3s ease;
      }
    }

    .title {
      font-size: 1.25rem;
      font-weight: 600;
      letter-spacing: 0.0313rem;
      line-height: 1.4;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      max-width: 18.75rem;
      transition: all 0.2s ease;
      cursor: pointer;

      &:hover {
        opacity: 0.8;
      }
    }
  }

  .center-container {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 1rem;
  }

  .right-container {
    display: flex;
    align-items: center;
    gap: 1rem;
  }
}

/* Position Utility Classes */
.position-fixed {
  position: fixed;
  left: 0;
  right: 0;
}
.position-sticky {
  position: sticky;
  left: 0;
  right: 0;
}
.position-absolute {
  position: absolute;
  left: 0;
  right: 0;
}
.position-relative {
  position: relative;
}
.position-static {
  position: static;
}
</style>