Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Banner components #317

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function getComponents() {
{ text: 'Alert', link: '/components/alert' },
{ text: 'Avatar', link: '/components/avatar' },
{ text: 'Badge', link: '/components/badge' },
{ text: 'Banner', link: '/components/banner' },
{ text: 'Breadcrumb', link: '/components/breadcrumb' },
{ text: 'Button', link: '/components/button' },
{ text: 'Button Group', link: '/components/button-group' },
Expand Down
81 changes: 81 additions & 0 deletions docs/components/banner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<script setup>
import FwbBannerDefaultExample from './banner/examples/FwbBannerDefaultExample.vue'
import FwbBannerBottomExample from './banner/examples/FwbBannerBottomExample.vue'
</script>

# Vue Banner - Flowbite

## Use the banner component to show marketing messages and CTA buttons at the top or bottom side of your website based on the utility classes from Tailwind CSS

---

:::tip
Original reference: [https://flowbite.com/docs/components/banner/](https://flowbite.com/docs/components/banner/)
:::

Get started with the sticky banner component coded with Tailwind CSS and Flowbite to show marketing, informational and CTA messages to your website visitors fixed to the top or bottom part of the page as the user scrolls down the main content area.

## Default sticky banner

Use this free example to show a text message for an announcement with a CTA link, an icon element and a close button to dismiss the banner.

<fwb-banner-default-example />


```vue
<template>
<fwb-banner closable>
<div class="bg-gray-50 p-4 dark:bg-gray-700">
<p class="flex items-center gap-2 text-sm font-normal text-gray-500 dark:text-gray-400">
<svg class="w-6 h-6 text-gray-800 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd"
d="M3.559 4.544c.355-.35.834-.544 1.33-.544H19.11c.496 0 .975.194 1.33.544.356.35.559.829.559 1.331v9.25c0 .502-.203.981-.559 1.331-.355.35-.834.544-1.33.544H15.5l-2.7 3.6a1 1 0 0 1-1.6 0L8.5 17H4.889c-.496 0-.975-.194-1.33-.544A1.868 1.868 0 0 1 3 15.125v-9.25c0-.502.203-.981.559-1.331ZM7.556 7.5a1 1 0 1 0 0 2h8a1 1 0 0 0 0-2h-8Zm0 3.5a1 1 0 1 0 0 2H12a1 1 0 1 0 0-2H7.556Z"
clip-rule="evenodd" />
</svg>
<span class="[&_p]:inline">
New brand identity has been launched for the&nbsp;
<a href="https://flowbite.com"
class="inline font-medium text-cyan-600 underline decoration-solid underline-offset-2 hover:no-underline dark:text-cyan-500">
Flowbite Library
</a>
</span>
</p>
</div>
</fwb-banner>
</template>
```

## Bottom banner position

This example can be used to position the sticky banner on the bottom side of the page instead of the top side.

<fwb-banner-bottom-example />

```vue
<template>
<fwb-banner position="bottom" closable>
<div class="bg-gray-50 p-4 dark:bg-gray-700">
<p class="flex items-center gap-2 text-sm font-normal text-gray-500 dark:text-gray-400">
<svg class="w-6 h-6 text-gray-800 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd"
d="M3.559 4.544c.355-.35.834-.544 1.33-.544H19.11c.496 0 .975.194 1.33.544.356.35.559.829.559 1.331v9.25c0 .502-.203.981-.559 1.331-.355.35-.834.544-1.33.544H15.5l-2.7 3.6a1 1 0 0 1-1.6 0L8.5 17H4.889c-.496 0-.975-.194-1.33-.544A1.868 1.868 0 0 1 3 15.125v-9.25c0-.502.203-.981.559-1.331ZM7.556 7.5a1 1 0 1 0 0 2h8a1 1 0 0 0 0-2h-8Zm0 3.5a1 1 0 1 0 0 2H12a1 1 0 1 0 0-2H7.556Z"
clip-rule="evenodd" />
</svg>
<span class="[&_p]:inline">
New brand identity has been launched for the&nbsp;
<a href="https://flowbite.com"
class="inline font-medium text-cyan-600 underline decoration-solid underline-offset-2 hover:no-underline dark:text-cyan-500">
Flowbite Library
</a>
</span>
</p>
</div>
</fwb-banner>
</template>
```

## More Examples

For more sticky banner examples you can check [banner sections](https://flowbite.com/blocks/marketing/banner/) from Flowbite Blocks.
67 changes: 67 additions & 0 deletions docs/components/banner/examples/FwbBannerBottomExample.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<template>
<div
class="vp-raw h-[25rem] overflow-y-scroll relative border border-gray-200 rounded-md dark:bg-gray-900 dark:border-gray-800">

Check warning on line 3 in docs/components/banner/examples/FwbBannerBottomExample.vue

View workflow job for this annotation

GitHub Actions / lint (18.x)

Expected 1 line break before closing bracket, but no line breaks found
<div class="px-8 py-4">
<div class="pb-4 mb-8 border-b border-gray-200 dark:border-gray-800">
<h1 id="content" class="inline-block mb-2 text-3xl font-extrabold tracking-tight text-gray-900 dark:text-white">

Check warning on line 6 in docs/components/banner/examples/FwbBannerBottomExample.vue

View workflow job for this annotation

GitHub Actions / lint (18.x)

'class' should be on a new line
Flowbite - Tailwind CSS component library
</h1>
<p class="mb-4 text-lg text-gray-600 dark:text-gray-400">
Get started with the most popular
open-source library of interactive UI components built with the utility classes from Tailwind
CSS
</p>
</div>

<h2 class="relative group text-xl font-semibold leading-8 mb-4 mt-8">
Introduction
<span id="introduction" class="absolute -top-[140px]" /> <a

Check warning on line 18 in docs/components/banner/examples/FwbBannerBottomExample.vue

View workflow job for this annotation

GitHub Actions / lint (18.x)

'class' should be on a new line
class="ml-2 text-blue-700 opacity-0 transition-opacity dark:text-blue-500 group-hover:opacity-100"
href="#introduction" aria-label="Link to this section: Introduction">#</a>

Check warning on line 20 in docs/components/banner/examples/FwbBannerBottomExample.vue

View workflow job for this annotation

GitHub Actions / lint (18.x)

'aria-label' should be on a new line

Check warning on line 20 in docs/components/banner/examples/FwbBannerBottomExample.vue

View workflow job for this annotation

GitHub Actions / lint (18.x)

Expected 1 line break before closing bracket, but no line breaks found
</h2>
<p class="text-gray-700 text-opacity-100 text-base font-normal leading-6 mb-4 dark:text-gray-400">
Flowbite is an open-source
library of UI components based on the utility-first Tailwind CSS framework
featuring dark mode support, a Figma design system, templates, and more.
</p>
<p class="text-gray-700 text-opacity-100 text-base font-normal leading-6 mb-4 dark:text-gray-400">
It includes all of the
commonly used components that a website requires, such as buttons, dropdowns,
navigation bars, modals, but also some more advanced interactive elements such as datepickers.
</p>
<p class="text-gray-700 text-opacity-100 text-base font-normal leading-6 mb-4 dark:text-gray-400">
All of the elements are built
using the utility classes from Tailwind CSS and vanilla JavaScript with
support for TypeScript.
</p>
<iframe sandbox="allow-same-origin allow-scripts" width="100%" height="500px"

Check warning on line 37 in docs/components/banner/examples/FwbBannerBottomExample.vue

View workflow job for this annotation

GitHub Actions / lint (18.x)

Expected a linebreak before this attribute

Check warning on line 37 in docs/components/banner/examples/FwbBannerBottomExample.vue

View workflow job for this annotation

GitHub Actions / lint (18.x)

'width' should be on a new line

Check warning on line 37 in docs/components/banner/examples/FwbBannerBottomExample.vue

View workflow job for this annotation

GitHub Actions / lint (18.x)

'height' should be on a new line
class="my-8 rounded-lg shadow-lg yt-video" src="https://www.youtube.com/embed/KaLxCiilHns"

Check warning on line 38 in docs/components/banner/examples/FwbBannerBottomExample.vue

View workflow job for this annotation

GitHub Actions / lint (18.x)

Expected indentation of 14 spaces but found 8 spaces

Check warning on line 38 in docs/components/banner/examples/FwbBannerBottomExample.vue

View workflow job for this annotation

GitHub Actions / lint (18.x)

'src' should be on a new line
title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen="" />
</div>
<fwb-banner class="sticky bottom-0 left-0" position="bottom" closable>
<div class="bg-gray-50 p-4 dark:bg-gray-700">
<p class="flex items-center gap-2 text-sm font-normal text-gray-500 dark:text-gray-400">
<svg class="w-6 h-6 text-gray-800 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd"
d="M3.559 4.544c.355-.35.834-.544 1.33-.544H19.11c.496 0 .975.194 1.33.544.356.35.559.829.559 1.331v9.25c0 .502-.203.981-.559 1.331-.355.35-.834.544-1.33.544H15.5l-2.7 3.6a1 1 0 0 1-1.6 0L8.5 17H4.889c-.496 0-.975-.194-1.33-.544A1.868 1.868 0 0 1 3 15.125v-9.25c0-.502.203-.981.559-1.331ZM7.556 7.5a1 1 0 1 0 0 2h8a1 1 0 0 0 0-2h-8Zm0 3.5a1 1 0 1 0 0 2H12a1 1 0 1 0 0-2H7.556Z"
clip-rule="evenodd" />
</svg>
<span class="[&_p]:inline">
New brand identity has been launched for the&nbsp;
<a href="https://flowbite.com"
class="inline font-medium text-cyan-600 underline decoration-solid underline-offset-2 hover:no-underline dark:text-cyan-500">
Flowbite Library
</a>
</span>
</p>
</div>
</fwb-banner>
</div>
</template>

<script setup>
import { FwbBanner } from '../../../../src/index'
</script>
68 changes: 68 additions & 0 deletions docs/components/banner/examples/FwbBannerDefaultExample.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<template>
<div
class="vp-raw h-[25rem] overflow-y-scroll relative border border-gray-200 rounded-md dark:bg-gray-900 dark:border-gray-800">
<fwb-banner class="sticky top-0 left-0" closable>
<div class="bg-gray-50 p-4 dark:bg-gray-700">
<p class="flex items-center gap-2 text-sm font-normal text-gray-500 dark:text-gray-400">
<svg class="w-6 h-6 text-gray-800 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd"
d="M3.559 4.544c.355-.35.834-.544 1.33-.544H19.11c.496 0 .975.194 1.33.544.356.35.559.829.559 1.331v9.25c0 .502-.203.981-.559 1.331-.355.35-.834.544-1.33.544H15.5l-2.7 3.6a1 1 0 0 1-1.6 0L8.5 17H4.889c-.496 0-.975-.194-1.33-.544A1.868 1.868 0 0 1 3 15.125v-9.25c0-.502.203-.981.559-1.331ZM7.556 7.5a1 1 0 1 0 0 2h8a1 1 0 0 0 0-2h-8Zm0 3.5a1 1 0 1 0 0 2H12a1 1 0 1 0 0-2H7.556Z"
clip-rule="evenodd" />
</svg>

<span class="[&_p]:inline">
New brand identity has been launched for the&nbsp;
<a href="https://flowbite.com"
class="inline font-medium text-cyan-600 underline decoration-solid underline-offset-2 hover:no-underline dark:text-cyan-500">
Flowbite Library
</a>
</span>
</p>
</div>
</fwb-banner>
<div class="px-8 py-4">
<div class="pb-4 mb-8 border-b border-gray-200 dark:border-gray-800">
<h1 id="content" class="inline-block mb-2 text-3xl font-extrabold tracking-tight text-gray-900 dark:text-white">
Flowbite - Tailwind CSS component library
</h1>
<p class="mb-4 text-lg text-gray-600 dark:text-gray-400">
Get started with the most popular
open-source library of interactive UI components built with the utility classes from Tailwind
CSS
</p>
</div>

<h2 class="relative group text-xl font-semibold leading-8 mb-4 mt-8">
Introduction
<span id="introduction" class="absolute -top-[140px]" /> <a
class="ml-2 text-blue-700 opacity-0 transition-opacity dark:text-blue-500 group-hover:opacity-100"
href="#introduction" aria-label="Link to this section: Introduction">#</a>
</h2>
<p class="text-gray-700 text-opacity-100 text-base font-normal leading-6 mb-4 dark:text-gray-400">
Flowbite is an open-source
library of UI components based on the utility-first Tailwind CSS framework
featuring dark mode support, a Figma design system, templates, and more.
</p>
<p class="text-gray-700 text-opacity-100 text-base font-normal leading-6 mb-4 dark:text-gray-400">
It includes all of the
commonly used components that a website requires, such as buttons, dropdowns,
navigation bars, modals, but also some more advanced interactive elements such as datepickers.
</p>
<p class="text-gray-700 text-opacity-100 text-base font-normal leading-6 mb-4 dark:text-gray-400">
All of the elements are built
using the utility classes from Tailwind CSS and vanilla JavaScript with
support for TypeScript.
</p>
<iframe sandbox="allow-same-origin allow-scripts" width="100%" height="500px"
class="my-8 rounded-lg shadow-lg yt-video" src="https://www.youtube.com/embed/KaLxCiilHns"
title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen="" />
</div>
</div>
</template>

<script setup>
import { FwbBanner } from '../../../../src/index'
</script>
53 changes: 53 additions & 0 deletions src/components/FwbBanner/FwbBanner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<template>
<div :class="bannerClasses" tabindex="-1" role="banner" aria-label="Banner">
<slot />
<button v-if="closable" :class="defaultCollapseButtonClass" aria-label="Collapse" type="button" @click="handleCollapse">
<svg class="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"
viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18 17.94 6M18 18 6.06 6" />
</svg>
</button>
Comment on lines +4 to +10
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider making the close button more accessible.

The close button uses an SVG for the icon. Ensure the button is accessible by providing a clear aria-label and considering keyboard accessibility.

- <button v-if="closable" :class="defaultCollapseButtonClass" aria-label="Collapse" type="button" @click="handleCollapse">
+ <button v-if="closable" :class="defaultCollapseButtonClass" aria-label="Close banner" type="button" @click="handleCollapse" tabindex="0">
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button v-if="closable" :class="defaultCollapseButtonClass" aria-label="Collapse" type="button" @click="handleCollapse">
<svg class="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"
viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18 17.94 6M18 18 6.06 6" />
</svg>
</button>
<button v-if="closable" :class="defaultCollapseButtonClass" aria-label="Close banner" type="button" @click="handleCollapse" tabindex="0">
<svg class="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"
viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18 17.94 6M18 18 6.06 6" />
</svg>
</button>

</div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useMergeClasses } from '@/composables/useMergeClasses'

type BannerPosition = 'top' | 'bottom'

type BannerProps = {
class?: string
position?: BannerPosition
closable?: boolean
}

const props: BannerProps = withDefaults(defineProps<BannerProps>(), {
class: '',
position: 'top',
closable: false,
})

const defaultBannerClass = 'fixed start-0 z-50 flex justify-between items-center w-full p-4 bg-gray-50 dark:bg-gray-700'
const positionDefaultClass = 'top-0 border-b border-gray-200 dark:border-gray-600'
const positionBottomClass = 'bottom-0 border-t border-gray-200 dark:border-gray-600'

const defaultCollapseButtonClass = 'flex-shrink-0 inline-flex justify-center w-7 h-7 items-center text-gray-400 border-none hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 dark:hover:bg-gray-600 dark:hover:text-white'

const bannerClasses = computed(() => useMergeClasses([
defaultBannerClass,
props.position === 'top' ? positionDefaultClass : positionBottomClass,
props.class ?? '',
]))

const handleCollapse = (e: MouseEvent) => {
const collapseButton = e.target as HTMLElement
const banner = collapseButton.closest('[role="banner"]') as HTMLElement | null

if (banner) {
banner.remove()
}
}
Comment on lines +44 to +51
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve the collapse function for better performance.

The handleCollapse function removes the banner element from the DOM. Consider emitting an event instead, allowing the parent component to handle the removal, which can be more flexible and testable.

- const handleCollapse = (e: MouseEvent) => {
-   const collapseButton = e.target as HTMLElement
-   const banner = collapseButton.closest('[role="banner"]') as HTMLElement | null
-   if (banner) {
-     banner.remove()
-   }
+ const handleCollapse = () => {
+   emit('collapse')
}

In the parent component:

<fwb-banner @collapse="handleBannerCollapse" />


</script>
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { default as FwbAvatar } from './components/FwbAvatar/FwbAvatar.vue'
export { default as FwbAvatarStack } from './components/FwbAvatar/FwbAvatarStack.vue'
export { default as FwbAvatarStackCounter } from './components/FwbAvatar/FwbAvatarStackCounter.vue'
export { default as FwbBadge } from './components/FwbBadge/FwbBadge.vue'
export { default as FwbBanner } from './components/FwbBanner/FwbBanner.vue'
export { default as FwbBreadcrumb } from './components/FwbBreadcrumb/FwbBreadcrumb.vue'
export { default as FwbBreadcrumbItem } from './components/FwbBreadcrumb/FwbBreadcrumbItem.vue'
export { default as FwbButton } from './components/FwbButton/FwbButton.vue'
Expand Down
Loading