DEV Community

Connie Leung
Connie Leung

Posted on

Day 24 - Alert Component Part 3 - Add an Alert Bar to change styles

On day 24, I create an Alert Bar component to show or hide the close button, apply a new style to the alerts, and change their direction.

Create a Close SVG Icon

Vue 3 application

Create an icons/CloseIcon.vue

<template>
    <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
        <path fill-rule="evenodd" clip-rule="evenodd" d="M5.29289 5.29289C5.68342 4.90237 6.31658 4.90237 6.70711 5.29289L12 10.5858L17.2929 5.29289C17.6834 4.90237 18.3166 4.90237 18.7071 5.29289C19.0976 5.68342 19.0976 6.31658 18.7071 6.70711L13.4142 12L18.7071 17.2929C19.0976 17.6834 19.0976 18.3166 18.7071 18.7071C18.3166 19.0976 17.6834 19.0976 17.2929 18.7071L12 13.4142L6.70711 18.7071C6.31658 19.0976 5.68342 19.0976 5.29289 18.7071C4.90237 18.3166 4.90237 17.6834 5.29289 17.2929L10.5858 12L5.29289 6.70711C4.90237 6.31658 4.90237 5.68342 5.29289 5.29289Z" fill="#0F1729"></path>
    </svg>
</template>
Enter fullscreen mode Exit fullscreen mode

Display the close icon in the alert component

<script setup lang="ts">
import CloseIcon from '@/icons/CloseIcon.vue'
</script>
Enter fullscreen mode Exit fullscreen mode
<template>
... omitted to save space ...
<div>
    <button class="btn btn-sm btn-primary" title="Close button" @click="closeAlert">
        <CloseIcon />
    </button>
</div>
</template>
Enter fullscreen mode Exit fullscreen mode

Import the CloseIcon and add the <CloseIcon> within the <template> tags to render the close icon on the button.


SvelteKit application

Create a lib/icons/close-icon.svelte

<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
    <path fill-rule="evenodd" clip-rule="evenodd" d="M5.29289 5.29289C5.68342 4.90237 6.31658 4.90237 6.70711 5.29289L12 10.5858L17.2929 5.29289C17.6834 4.90237 18.3166 4.90237 18.7071 5.29289C19.0976 5.68342 19.0976 6.31658 18.7071 6.70711L13.4142 12L18.7071 17.2929C19.0976 17.6834 19.0976 18.3166 18.7071 18.7071C18.3166 19.0976 17.6834 19.0976 17.2929 18.7071L12 13.4142L6.70711 18.7071C6.31658 19.0976 5.68342 19.0976 5.29289 18.7071C4.90237 18.3166 4.90237 17.6834 5.29289 17.2929L10.5858 12L5.29289 6.70711C4.90237 6.31658 4.90237 5.68342 5.29289 5.29289Z" fill="#0F1729"></path>
</svg>
Enter fullscreen mode Exit fullscreen mode

Display the close icon in the alert component

<script lang="ts">
    import CloseIcon from '$lib/icons/close-icon.svelte'
</script>
Enter fullscreen mode Exit fullscreen mode
... omitted to save space ... 
<div>
    <button class="btn btn-sm btn-primary" title="Close button" onclick={closeAlert}>
        <CloseIcon />
    </button>
</div>
Enter fullscreen mode Exit fullscreen mode

Import the CloseIcon and add the <CloseIcon /> in the HTML template to render the close icon on the button.


Angular 20 application

Create a CloseIconComponent

@Component({
  selector: 'app-close-icon',
  template: `
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
  <path fill-rule="evenodd" clip-rule="evenodd" d="M5.29289 5.29289C5.68342 4.90237 6.31658 4.90237 6.70711 5.29289L12 10.5858L17.2929 5.29289C17.6834 4.90237 18.3166 4.90237 18.7071 5.29289C19.0976 5.68342 19.0976 6.31658 18.7071 6.70711L13.4142 12L18.7071 17.2929C19.0976 17.6834 19.0976 18.3166 18.7071 18.7071C18.3166 19.0976 17.6834 19.0976 17.2929 18.7071L12 13.4142L6.70711 18.7071C6.31658 19.0976 5.68342 19.0976 5.29289 18.7071C4.90237 18.3166 4.90237 17.6834 5.29289 17.2929L10.5858 12L5.29289 6.70711C4.90237 6.31658 4.90237 5.68342 5.29289 5.29289Z" fill="#0F1729"></path>
</svg>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CloseIconComponent {}
Enter fullscreen mode Exit fullscreen mode

Display the close icon in the alert component

import { NgComponentOutlet } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, input, output, signal } from '@angular/core';
import { AlertType } from '../alert.type';
import { CloseIconComponent, ErrorIconComponent, InfoIconComponent, SuccessIconComponent, WarningIconComponent } from '../icons/icon.component';

@Component({
  selector: 'app-alert',
  imports: [NgComponentOutlet, CloseIconComponent],
  template: `
     ... omitted to save space ...
    <div>
      <button class="btn btn-sm btn-primary" title="Close button" (click)="closeAlert()">
        <app-close-icon />
      </button>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertComponent {}
Enter fullscreen mode Exit fullscreen mode

Import the CloseIconComponent and register it to the imports array of the @Component decorator.

In the inline template, use the app-close-icon selector to render the close icon on the button.


Add Show/Hide checkbox to the AlertBar Component

Vue 3 application

Since Vue 3.4, the recommended approach of two-way data binding is using the defineModel macro.

<script setup lang="ts">
const hasCloseButton = defineModel<boolean>('hasCloseButton', { default: true })
</script>
Enter fullscreen mode Exit fullscreen mode

Declare a defineModel<boolean> and assign the value to the hasCloseButton ref. { default: true } means the default value of the ref is true The checkbox is checked and the alert component should show a close button.

<template>
  <div>
    <p class="mb-[0.75rem]">
      <span>Has close button? </span>
      <input type="checkbox" class="mr-[0.5rem]" v-model="hasCloseButton" />
    </p>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

In the template, the v-model directive is added to the checkbox input and binded to hasCloseButton. The hasCloseButton enables 2-way data binding and passes the value up to the parent component, which is the AlertList component.


SvelteKit application

In Svelte 5, two-way binding is done with the $bindable function. Moreover, $bindable must be used with $props together. We will update the Prop type to add hasCloseButton and destructure the property from the $prop() call.


 type Props = {
    hasCloseButton: boolean;
}

let { 
    hasCloseButton = $bindable(), 
}: Props = $props();
Enter fullscreen mode Exit fullscreen mode
<div>
    <p class="mb-[0.75rem]">
        <span>Has close button?</span>
        <input type="checkbox" class="mr-[0.5rem]" bind:checked={hasCloseButton} />
    </p>
</div>
Enter fullscreen mode Exit fullscreen mode

In the template, the bind:checked attribute of the checkbox is binded to hasCloseButton. The hasCloseButton enables 2-way data binding and passes the value up to the parent component, which is the AlertList component.


Angular 20 application

In Angular, two-way binding is achieved by the the model Writable signal.

import { ChangeDetectionStrategy, Component, input, model } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-alert-bar',
  imports: [],
  template: `
  <div>
    <p class="mb-[0.75rem]">
      <span>Has close button? </span>
      <input type="checkbox" class="mr-[0.5rem]" [(ngModel)]="hasCloseButton" />
      </select>
    </p>
  </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertBarComponent {
  hasCloseButton = model<boolean>(true);
}
Enter fullscreen mode Exit fullscreen mode

Declare a model<boolean>(true) and assign the value to the hasCloseButton signal. The default value of model is true, so the checkbox is checked and the alert component should show a close button.


Pass hasCloseButton up to the AlertList Component

Vue 3 application

<script setup lang="ts">
import { computed, ref } from 'vue';

const hasCloseButton = ref(true)
</script>
Enter fullscreen mode Exit fullscreen mode

Declare a hasCloseButton ref in the AlertList component to receive the value from the child.

<template>
  <h2>Alert Components (Vue ver.)</h2>

  <AlertBar
    v-model:hasCloseButton="hasCloseButton"
  />
</template>
Enter fullscreen mode Exit fullscreen mode

A Vue component allows multiple v-model bindings.

v-model:hasCloseButton="hasCloseButton" binds the child's hasCloseButton to the hasCloseButton ref.


SvelteKit application

<script lang="ts">
    import AlertBar from './alert-bar.svelte';

    let hasCloseButton = $state(true);
</script>
Enter fullscreen mode Exit fullscreen mode

Declare a hasCloseButton rune in the AlertList component to receive the value from the child.

<AlertBar {configs} 
    bind:hasCloseButton={hasCloseButton} 
/>
Enter fullscreen mode Exit fullscreen mode

bind:hasCloseButton={hasCloseButton} means the AlertList component listens to what the hasCloseButton prop of the AlerBarcomponent has to say.


Angular 20 application

import { ChangeDetectionStrategy, Component, computed, input, signal } from '@angular/core';
import { AlertBarComponent } from '../alert-bar/alert-bar.component';

@Component({
  selector: 'app-alert-list',
  imports: [AlertBarComponent],
  template: `
    <app-alert-bar 
      [(hasCloseButton)]="hasCloseButton" 
    />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertListComponent {
  hasCloseButton = signal<boolean>(true);
}
Enter fullscreen mode Exit fullscreen mode

Declare a hasCloseButton signal in the AlertList component to receive the value from the child.

Use banana-in-the-box syntax, [(expression)], to perform two-way binding for the AlertList and AlertBar components.


Show/Hide Close Button in the Alert Component

Vue 3 application

const hasCloseButton = ref(true)

const alertConfig = computed(() => ({
  hasCloseButton: hasCloseButton.value,
}))
Enter fullscreen mode Exit fullscreen mode

Define a alertConfig computed ref to create an object to store the value of hasCloseButton.value.

<template>
    <Alert v-for="{ type, message } in alerts"
        class="mb-[0.75rem]"
        :key="type"
        :type="type"
        :alertConfig="alertConfig">
        {{  message }}
    </Alert>
</template>
Enter fullscreen mode Exit fullscreen mode

Pass the alertConfig to the alertConfig prop of the Alert component.

<script setup lang="ts">
    import { computed, ref } from 'vue'
    import CloseIcon from '@/icons/CloseIcon.vue'

    type Props = {
        alertConfig: {
            hasCloseButton: boolean
        },
        type: string
    }

    const { type, alertConfig } = defineProps<Props>()

    ... omit the template codes ...
</script>
Enter fullscreen mode Exit fullscreen mode

In the Alert component, add the new alertConfig property to the Prop type. Destructure the alertConfig from the defineProps macro.

<template>
  <div role="alert" :class="alertClasses" v-if="!closed">
    <component :is="icon" />
    <span><slot></slot></span>
    <div v-if="alertConfig.hasCloseButton">
        <button class="btn btn-sm btn-primary" alt="Close button" @click="closeAlert">
            <CloseIcon />
        </button>
    </div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

If alertConfig.hasCloseButton is true, the close button will be shown. Otherwise, it is hidden.


SvelteKit application

<script lang="ts">
    import AlertBar from './alert-bar.svelte';
    import Alert from './alert.svelte';
    import type { AlertMessage } from './alert.type';

    let hasCloseButton = $state(true);
</script>
Enter fullscreen mode Exit fullscreen mode

Declare a hasCloseButton rune in the AlertList component to receive the value from the child.

{#each alerts as alert (alert.type) } 
    <Alert {alert} {hasCloseButton} />
{/each}
Enter fullscreen mode Exit fullscreen mode

Pass the hasCloseButton rune to the hasCloseButton prop of the Alert component.

<AlertBar {configs} 
    bind:hasCloseButton={hasCloseButton} 
/>
Enter fullscreen mode Exit fullscreen mode

bind:hasCloseButton={hasCloseButton} means the AlertList component listens to what the hasCloseButton prop of the AlertBarcomponent has to say.

<script lang="ts">
    type Props = {
        ... other properties ...
        hasCloseButton: boolean;
    }

    const { 
        hasCloseButton, 
    }: Props = $props();
</script>
Enter fullscreen mode Exit fullscreen mode

In the Alert component, add a hasCloseButton property to the Prop type and destructure the property from the object.

{#if !closed}
    <div role="alert" class={alertClasses}>
        ... omit the template code...
        {#if hasCloseButton} 
            <div>
                <button class="btn btn-sm btn-primary" title="Close button" onclick={closeAlert}>
                    <CloseIcon />
                </button>
            </div>
        {/if}
    </div>
{/if}
Enter fullscreen mode Exit fullscreen mode

If hasCloseButton is true, the button is rendered. Otherwise, the button is hidden.


Angular 20 application

import { ChangeDetectionStrategy, Component, computed, input, signal } from '@angular/core';
import { AlertBarComponent } from '../alert-bar/alert-bar.component';

@Component({
  selector: 'app-alert-list',
  imports: [AlertBarComponent],
  template: `
    <app-alert-bar 
      [(hasCloseButton)]="hasCloseButton" 
    />

    @for (alert of alerts(); track alert.type) {
      <app-alert [type]="alert.type" 
        [alertConfig]="alertConfig()"
      >
        {{ alert.message }}
      </app-alert>
    }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertListComponent {
  hasCloseButton = signal<boolean>(true);

  alertConfig = computed(() => ({
    hasCloseButton: this.hasCloseButton(),
  }));
}
Enter fullscreen mode Exit fullscreen mode

Declare a hasCloseButton signal in the AlertList component to receive the value from the child.

Use banana-in-the-box syntax, [(expression)], to perform two-way binding for the AlertList and AlertBar components.

Define a alertConfig computed signal to create an object to store the value of the hasCloseButton signal.

Assign the value of the alertConfig computed signal to the input signal of AlertComponent.

import { NgComponentOutlet } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, input, output, signal } from '@angular/core';
import { AlertType } from '../alert.type';
import { CloseIconComponent, ErrorIconComponent, InfoIconComponent, SuccessIconComponent, WarningIconComponent } from '../icons/icon.component';

@Component({
  selector: 'app-alert',
  imports: [NgComponentOutlet, CloseIconComponent],
  template: `
    @if (!closed()) {
        ... omit the template codes ...
        @if (alertConfig().hasCloseButton) {
        <div>
          <button class="btn btn-sm btn-primary" title="Close button" (click)="closeAlert()">
            <app-close-icon />
          </button>
        </div>
        }
    }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertComponent {
  type = input.required<AlertType>();

  alertConfig = input.required<{
    hasCloseButton: boolean
  }>();
}
Enter fullscreen mode Exit fullscreen mode

In the AlertComponent, declare an alertConfig required input signal.

If alertConfig().hasCloseButton is true, the button is rendered. Otherwise, the button is hidden.

Next, we will repeat the same procedure to add style and direction dropdown lists to change the styling of the alert component.


Add Alert Style and Direction dropdown lists

Vue 3 application

<script setup lang="ts">
    type Props = {
        config: { 
          styleLabel: string
          styles: { text: string, value: string }[]
          directionLabel: string
          directions: { text: string, value: string }[]
        },
    }

    const props = defineProps<Props>()
    const { config } = props
</script>
Enter fullscreen mode Exit fullscreen mode

Declare a Prop in the AlertBar component to accept the style label, style dropdown list, direction label, and direction dropdown list.

const style = defineModel<string>('style', { default: 'color' })
const direction = defineModel<string>('direction', { default: 'horizontal' })
Enter fullscreen mode Exit fullscreen mode

Declare style and direction ref for two-way data binding. The default value of style is 'color' and the default value of direction is 'horizontal'.

<template>
  <div>
    <p class="mb-[0.75rem]">
      <span>{{ config.styleLabel }}&nbsp;&nbsp;</span>
      <select class="select select-info mr-[0.5rem]" v-model="style">
        <option v-for="{value, text} in config.styles" :key="value" :value="value">
          {{ text }}
        </option>
      </select>
      <span>{{ config.directionLabel }}&nbsp;&nbsp;</span>
      <select class="select select-info mr-[0.5rem]" v-model="direction">
        <option v-for="{ value, text } in config.directions" :key="value" :value="value">
          {{ text }}
        </option>
      </select>
    </p>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Use v-for to iterate config.styles and config.directions to populate the option items of the select dropdown.


SvelteKit application

<script lang="ts">
    import { capitalize } from './capitalize';
    import OpenIcon from './icons/open-icon.svelte';

    type Props = {
        configs: { 
            styleLabel: string
            styles: { text: string, value: string }[]
            directionLabel: string
            directions: { text: string, value: string }[]
        };
        hasCloseButton: string;
        style: string;
        direction: string;
    }

    let { 
        hasCloseButton = $bindable(), 
        style = $bindable(), 
        direction = $bindable(), 
        closedNotifications = $bindable(),
        configs 
    }: Props = $props();
</script>
Enter fullscreen mode Exit fullscreen mode

Add configs, style and direction to the Props type.
Destructure style and direction from $props() and bind them to the AlertList component using $bindable.

 <p class="mb-[0.75rem]">
    <span>{ configs.styleLabel }&nbsp;&nbsp;</span> { style }
    <select class="select select-info mr-[0.5rem]" bind:value={style}>
        {#each configs.styles as s (s.value) }
            <option value={s.value}>
                { s.text }
            </option>
        {/each}
    </select>
    <span>{ configs.directionLabel }&nbsp;&nbsp;</span>
    <select class="select select-info mr-[0.5rem]" bind:value={direction}> 
        {#each configs.directions as d (d.value)}
            <option value={d.value}>
                { d.text }
            </option>
        {/each}
    </select>
</p>
Enter fullscreen mode Exit fullscreen mode

bind:value={style} binds style rune to the style dropdown. bind:value={direction} bind the direction rune to the direction dropdown.

Use #each to iterate the styles and directions lists to populate the dropdown lists.

Angula 20 application

@Component({
  selector: 'app-alert-bar',
  imports: [FormsModule],
  template: `... inline template explained below ...`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertBarComponent {
  config = input.required<{ 
    styleLabel: string
    styles: { text: string, value: string }[]
    directionLabel: string
    directions: { text: string, value: string }[]
  }>();

  hasCloseButton = model<boolean>(true);
  style = model<string>('color');
  direction = model<string>('horizontal');
}
Enter fullscreen mode Exit fullscreen mode

The AlertBarComponent declares a required input signal for the labels and dropdown lists.

Similarly, style and direction are declared model signals for two-way binding.

The component also imports FormsModule to bind the model to the [(ngModel)] in the inline template.

  <div>
    @let c = config();
    <p class="mb-[0.75rem]">
      <span>{{ c.styleLabel }}&nbsp;&nbsp;</span>
      <select class="select select-info mr-[0.5rem]" [(ngModel)]="style">
        @for (style of c.styles; track style.value) {
          <option [ngValue]="style.value">
            {{ style.text }}
          </option>
        }
      </select>
      <span>{{ c.directionLabel }}&nbsp;&nbsp;</span>
      <select class="select select-info mr-[0.5rem]" [(ngModel)]="direction">
        @for (direction of c.directions; track direction.value) {
          <option [ngValue]="direction.value">
            {{ direction.text }}
          </option>
        }
      </select>
    </p>
  </div>
Enter fullscreen mode Exit fullscreen mode

[(ngModel)]="style" binds style to the style dropdown. Similarly, [(ngModel)]="direction" binds direction to the direction dropdown.

The @for loop iterates the styles and directions lists to display the value/text items.


Perform two-way binding

Vue 3 Application

The config ref defines the labels and items of the style and direction.

<script setup lang="ts">
import type { AlertType } from '@/types/alert-type';
import { computed, ref } from 'vue';
import Alert from './Alert.vue';
import AlertBar from './AlertBar.vue';

const hasCloseButton = ref(true)
const style = ref('color');
const direction = ref('horizontal')

const alertConfig = computed(() => ({
  style: style.value,
  direction: direction.value,
  hasCloseButton: hasCloseButton.value,
}))

const config = ref({
  styleLabel: "Alert styles:",
  styles: [
    { text: 'Default', value: 'color' },
    { text: 'Soft', value: 'soft' },
    { text: 'Outline', value: 'outline' },
    { text: 'Dash', value: 'dash' }
  ],
  directionLabel: "Alert direction:",
  directions: [
    { text: 'Horizontal', value: 'horizontal' },
    { text: 'Vertical', value: 'vertical' },
  ]
})
</script>
Enter fullscreen mode Exit fullscreen mode

Similar to hasCloseButton, style and direction pass the values from the AlertBar component to the AlertList component. Then, the alertConfig computed ref returns the value of style and direction. The alertConfig passes the new values to style the Alert component.

<AlertBar
    :config="config"
    v-model:hasCloseButton="hasCloseButton"
    v-model:style="style"
    v-model:direction="direction"
 />
Enter fullscreen mode Exit fullscreen mode

The AlertBar component receives the config prop. The v-model:style="style" binds the style ref of the AlertBar component to style ref of the AlertList component. Similarly, v-model:direction="direction" binds the direction ref of the AlertBar component to the direction ref of the AlertList component.

SvelteKit Application

The labels and items of style and direction are defined in the config rune.

<script lang="ts">
    import AlertBar from './alert-bar.svelte';
    import Alert from './alert.svelte';

    const configs = $state(... same object...);

    let hasCloseButton = $state(true);
    let style = $state('color');
    let direction = $state('horizontal');
</script>
Enter fullscreen mode Exit fullscreen mode

Similar to hasCloseButton, add style and direction runes to the AlertList component.

<AlertBar {configs} 
    bind:hasCloseButton={hasCloseButton} 
    bind:style={style}
    bind:direction={direction}
/>
Enter fullscreen mode Exit fullscreen mode

The AlertBar component receives the config prop to populate the dropdown. The AlertBar uses the bind syntax to pass the style and direction to the AlertList component.

{#each filteredNotification as alert (alert.type) } 
    <Alert {alert} {alertMessage} {notifyClosed} {hasCloseButton} {style} {direction} />
{/each}
Enter fullscreen mode Exit fullscreen mode

Pass the new prop values, style and direction, to the Alert component.

Angular 20 Application

The labels and items of style and direction are defined in the config signal.

import { ChangeDetectionStrategy, Component, computed, input, signal } from '@angular/core';
import { AlertComponent } from '../alert/alert.component';
import { AlertBarComponent } from '../alert-bar/alert-bar.component';

@Component({
  selector: 'app-alert-list',
  imports: [AlertComponent, AlertBarComponent],
  template: `...inline template shown below ...`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertListComponent {
  style = signal<string>('color');
  direction = signal<string>('horizontal');
  hasCloseButton = signal<boolean>(true);

  config = signal({... same object ... });

  alertConfig = computed(() => ({
    hasCloseButton: this.hasCloseButton(),
    style: this.style(),
    direction: this.direction()
  }));
}
Enter fullscreen mode Exit fullscreen mode

Similar to hasCloseButton, add style and direction signal to the AlertList component. The alertConfig computed signal returns the value of the style and direction signals.

 <app-alert-bar 
      [config]="config()" 
      [(style)]="style" 
      [(direction)]="direction"
      [(hasCloseButton)]="hasCloseButton"
/>
Enter fullscreen mode Exit fullscreen mode

The AlertBar component receives the config input to populate the dropdown. The AlertBar uses the banana-in-the-box syntax [(expression)] to bind the style and direction models of the AlertBar component to the style and direction signals of the AlertList component.

[(style)]="style" - The first style is the style model of the AlertBarComponent and the second style is the style signal of the AlertListComponent.

[(direction)]="direction" - The first direction is the direction model of the AlertBarComponent and the second direction is the direction signal of the AlertListComponent.


Apply Style and Direction to Alert Component

Vue 3 Application

 type Props = {
    alertConfig: {
        hasCloseButton: boolean
        style: string
        direction: string
    },
    type: string
}

const { type, alertConfig } = defineProps<Props>()
Enter fullscreen mode Exit fullscreen mode

In the Props type of the Alert component, add style and direction to the alertConfig property.

const alertColor = computed(() => ({
    info: 'alert-info',
    warning: 'alert-warning',
    error: 'alert-error',
    success: 'alert-success'
}[type]))

 const alertStyle = computed(() => ({
    color: '',
    dash: 'alert-dash',
    soft: 'alert-soft',
    outline: 'alert-outline'
}[alertConfig.style]))

const alertDirection = computed(() => ({
    horizontal: 'alert-horizontal',
    vertical: 'alert-vertical',
}[alertConfig.direction]))

const alertClasses = computed(() => `alert ${alertColor.value} ${alertStyle.value} ${alertDirection.value}`)
Enter fullscreen mode Exit fullscreen mode

Create the alertStyle computed ref to derive the style class. Create the alertDirection computed ref to derive the direction class.

The alertClasses computed ref concatenates the style classes of the alert component.

 <template>
  <div role="alert" :class="alertClasses" v-if="!closed">
    <!-- HTML button -->
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

The alertClasses binds the new styles to the class attribute to change the direction, border style and color.

SvelteKit Application

type Props = {
    hasCloseButton: boolean;
    style: string;
    direction: string;
}

const { 
    hasCloseButton, 
    direction, 
    style 
}: Props = $props();
Enter fullscreen mode Exit fullscreen mode

In the Props type of the Alert component, add style and direction properties.

const alertColor = $derived.by(() => ({
    info: 'alert-info',
    success: 'alert-success',
    warning: 'alert-warning',
    error: 'alert-error',
}[alert.type]));

const alertDirection = $derived.by(() => ({
    horizontal: 'alert-horizontal',
    vertical: 'alert-vertical',
}[direction]));

const alertStyle = $derived.by(() => ({
    color: '',
    soft: 'alert-soft',
    outline: 'alert-outline',
    dash: 'alert-dash',
}[style]));

const alertClasses = $derived(`alert ${alertColor} ${alertDirection} ${alertStyle} mb-[0.75rem]`);
Enter fullscreen mode Exit fullscreen mode

Create the alertStyle derived rune to derive the style class. Create the alertDirection derived rune to derive the direction class.

The alertClasses derived rune concatenates the style classes of the alert component.

{#if !closed}
    <div role="alert" class={alertClasses}>
       <!-- HTML button -->
    </div>
{/if}
Enter fullscreen mode Exit fullscreen mode

The alertClasses binds the new styles to the class attribute to change the direction, border style and color.

Angular 20 Application

@Component({
  selector: 'app-alert',
  imports: [NgComponentOutlet, CloseIconComponent],
  template: `... inline template ...`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertComponent {
  type = input.required<AlertType>();

  alertConfig = input.required<{
    hasCloseButton: boolean
    style: string
    direction: string
  }>();

  alertColor = computed(() => {
    return {
        info: 'alert-info',
        warning: 'alert-warning',
        error: 'alert-error',
        success: 'alert-success'
    }[this.type()]
  });

  alertStyle = computed(() => {
    return {
        color: '',
        dash: 'alert-dash',
        soft: 'alert-soft',
        outline: 'alert-outline'
    }[this.alertConfig().style]
  });

  alertDirection = computed(() => {
    return {
        horizontal: 'alert-horizontal',
        vertical: 'alert-vertical',
    }[this.alertConfig().direction]
  });

  alertClasses = computed(() => `alert ${this.alertColor()} ${this.alertStyle()} ${this.alertDirection()}`);
}
Enter fullscreen mode Exit fullscreen mode

Add style and direction to the alertConfig required input signal.

Create the alertStyle computed signal to derive the style class. Create the alertDirection computed signal to derive the direction class.

The alertClasses computed signal concatenates the style classes of the alert component.

@if (!closed()) {
<div role="alert" class="mb-[0.75rem]" [class]="alertClasses()">
       <!-- HTML button -->
</div>
}
Enter fullscreen mode Exit fullscreen mode

The alertClasses binds the new styles to the class attribute to change the direction, border style and color.

Now, users can select values in the AlertBar component to show/hide the close button, change the border style, direction, and color of the alerts.


Github Repositories

Github Pages

Resources

Resources

Top comments (0)