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

Add banner #511

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/docs/constants/docs.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ export const COMPONENT_ROUTES: ComponentRoute[] = [

description: "A main banner for landing pages.",
},
{
path: "/components/banner",
title: "Banner",

description: "Inside page banner",
},

{
path: "/components/popover",
Expand Down
1 change: 1 addition & 0 deletions src/lib/components.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as Back } from "./components/Back.svelte";
export { default as Backdrop } from "./components/Backdrop.svelte";
export { default as Banner } from "./components/Banner.svelte";
export { default as BottomSheet } from "./components/BottomSheet.svelte";
export { default as BusyScreen } from "./components/BusyScreen.svelte";
export { default as Card } from "./components/Card.svelte";
Expand Down
84 changes: 84 additions & 0 deletions src/lib/components/Banner.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script lang="ts">
export let testId: string | undefined = undefined;
import { DEFAULT_ICON_SIZE } from "$lib/constants/constants";
import IconClose from "$lib/icons/IconClose.svelte";
import { createEventDispatcher } from "svelte";

export let visible: boolean = true;

const dispatch = createEventDispatcher();

function setVisibilityFalse() {
visible = false;
dispatch("nnsClose");
}
</script>

{#if visible}
<div
class="banner"
data-tid={testId}
style="--default-icon-size: {DEFAULT_ICON_SIZE}px;"
>
<div class="icon-background">
<div class="icon-wrapper">
<slot name="icon" />
</div>
</div>
<div class="text-content">
<slot name="title" />
<slot name="description" />
</div>
<div class="action-wrapper">
<slot name="action" />
</div>
<button class="close-button icon-only" on:click={setVisibilityFalse}>
<IconClose />
</button>
</div>
{/if}

<style lang="scss">
@use "../styles/mixins/fonts";

.banner {
display: flex;
align-items: center;
background: var(--banner-background, var(--input-background));
border-radius: var(--banner-radius, var(--border-radius));
padding: var(--banner-top-bottom-padding, var(--padding))
var(--banner-left-right-padding, var(--padding-1_5x));
column-gap: var(--banner-column-gap, var(--padding-1_5x));
}

.icon-background {
padding: var(--icon-background-padding, 6px);
background-color: var(
--icon-background-color,
var(--dropdown-border-color)
);
border-radius: var(--icon-border-radius, 50%);
}

.icon-wrapper {
color: var(--icon-color, var(--elements-icons));
}

.action-wrapper {
flex-shrink: 0;
--button-min-height: 40px;
}

.text-content {
display: flex;
flex-direction: column;
flex-grow: 1;
}

.close-button {
padding: var(--close-button-padding, 10px);
border-radius: var(--close-button-border-radius, var(--border-radius));
background: var(--close-button-background, var(--card-background));
color: var(--elements-icons);
}
</style>
132 changes: 132 additions & 0 deletions src/routes/(split)/components/banner/+page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<script lang="ts">
import Banner from "$lib/components/Banner.svelte";
import IconInfo from "$lib/icons/IconInfo.svelte";
</script>

# Banner

A versatile component to render informative banners with customizable content, actions, and visibility control.

| Property | Description | Type | Default |
| --------- | --------------------------------------------------------------- | ----------------------- | ----------- |
| `testId` | Add a `data-tid` attribute to the DOM, useful for test purpose. | `string` or `undefined` | `undefined` |
| `visible` | Controls the visibility of the banner. | `boolean` | `true` |

## Slots

| Slot name | Description |
| ------------- | --------------------------------------------------------------------------- |
| `icon` | Icon appearing at the start of the banner. |
| `title` | Title of the banner. |
| `description` | Description text below the title. It should explain the banner's purpose. |
| `action` | Located on the right side of the banner. Useful for call-to-action buttons. |

## Events

| Event | Description |
| ---------- | -------------------------------------------- |
| `nnsClose` | Dispatched when the close button is clicked. |

## Showcase

The component has its own background color and visibility control, which can be customized using props and CSS variables.

### Example

<Banner>
<IconInfo slot="icon" />
<span slot="title" class='title'>Your ICP Wallet</span>
<span slot="description" class="description">
Create and link accounts, transfer ICP, participate in governance, and earn
rewards.
</span>
<button class="secondary" slot="action">Connect with Internet Identity</button>
</Banner>

<br />

<style lang="scss">
@use "../../../../lib/styles/mixins/fonts";

.title {
@include fonts.small(true);
}
.description {
@include fonts.small(false);
}
</style>

#### Code

```text
<Banner>
<IconInfo slot="icon" />
<span slot="title">Your ICP Wallet</span>
<span slot="description" class="description">
Create and link accounts, transfer ICP, participate in governance, and earn
rewards.
</span>
<button class="primary" slot="action">Connect Wallet</button>
</Banner>

<style lang="scss">
@use "../../../../lib/styles/mixins/fonts";

.title {
@include fonts.small(true);
}
.description {
@include fonts.small(false);
}
</style>

```

## Visibility Control

The Banner component includes built-in visibility control:

- The `visible` prop controls the visibility of the banner.
- When the close button is clicked, the `visible` prop is set to `false` and an `nnsClose` event is dispatched.

## Customization

You can customize the appearance of the Banner component using CSS variables:

| CSS Variable | Description | Default Value |
| ------------------------------ | ------------------------------------ | ------------------------------ |
| `--banner-background` | Background color of the banner | `var(--input-background)` |
| `--banner-radius` | Border radius of the banner | `var(--border-radius)` |
| `--banner-top-bottom-padding` | Vertical padding inside the banner | `var(--padding)` |
| `--banner-left-right-padding` | Horizontal padding inside the banner | `var(--padding-1_5x)` |
| `--banner-column-gap` | Gap between banner elements | `var(--padding-1_5x)` |
| `--icon-background-padding` | Padding around the icon | `6px` |
| `--icon-background-color` | Background color of the icon wrapper | `var(--dropdown-border-color)` |
| `--icon-border-radius` | Border radius of the icon wrapper | `50%` |
| `--icon-color` | Color of the icon | `var(--elements-icons)` |
| `--close-button-padding` | Padding of the close button | `10px` |
| `--close-button-border-radius` | Border radius of the close button | `var(--border-radius)` |
| `--close-button-background` | Background color of the close button | `var(--card-background)` |

Example of customization:

```svelte
<Banner>
<!-- Banner content -->
</Banner>

<style lang="scss">
:global(.banner) {
--banner-background: #f0f8ff;
--banner-radius: 10px;
--banner-top-bottom-padding: 12px;
--banner-left-right-padding: 18px;
--banner-column-gap: 18px;
--icon-background-color: #e6f3ff;
--icon-color: #0056b3;
--close-button-background: #ffffff;
}
</style>
```

This customization will apply a light blue theme to the banner and adjust the padding, border radius, and colors.
49 changes: 49 additions & 0 deletions src/tests/lib/components/Banner.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Banner from "$lib/components/Banner.svelte";
import { fireEvent, render } from "@testing-library/svelte";
import BannerTest from "./BannerTest.svelte";

describe("Banner", () => {
it("should render the banner when visible is true", () => {
const { container } = render(Banner);
expect(container.querySelector(".banner")).not.toBeNull();
});

it("should not render the banner when visible is false", () => {
const { container } = render(Banner, { props: { visible: false } });
expect(container.querySelector(".banner")).toBeNull();
});

it("should render slotted content", () => {
const { getByText } = render(BannerTest);
expect(getByText("Test Icon")).toBeInTheDocument();
expect(getByText("Test Title")).toBeInTheDocument();
expect(getByText("Test Description")).toBeInTheDocument();
expect(getByText("Test Action")).toBeInTheDocument();
});

it("should close the banner when close button is clicked", async () => {
const { container, component } = render(Banner);

const closeButton = container.querySelector(
".close-button",
) as HTMLButtonElement;
expect(closeButton).not.toBeNull();

const onClose = vi.fn();
component.$on("nnsClose", onClose);

await fireEvent.click(closeButton);

expect(onClose).toHaveBeenCalled();
expect(container.querySelector(".banner")).toBeNull();
});

it("should apply custom test ID", () => {
const testId = "custom-banner-id";
const { container } = render(Banner, { props: { testId } });

const banner = container.querySelector(".banner");
expect(banner).not.toBeNull();
expect(banner?.getAttribute("data-tid")).toBe(testId);
});
});
10 changes: 10 additions & 0 deletions src/tests/lib/components/BannerTest.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script lang="ts">
import Banner from "$lib/components/Banner.svelte";
</script>

<Banner>
<span slot="icon">Test Icon</span>
<span slot="title">Test Title</span>
<span slot="description">Test Description</span>
<button slot="action">Test Action</button>
</Banner>
Loading