Playing Next Lesson In
seconds

EdgeJS Components #4.4

Dismissible & Self Destructing Alerts

In This Lesson

We'll focus on adding interactivity to our alerts by making them both conditionally dismissible and self-destructable.

Created by
@tomgobich
Published

Lastly, for our alerts, let’s add in a little interactivity. In this lesson, we’re going to make our alerts:

  • Dismissible, meaning that if we click a close button the alert should go away.

  • Self-destructible, meaning that after a defined time the alert should remove itself.

Making Our Alerts Dismissible

First, let’s conditionally make our alerts dismissible. Since our AlpineJS state for this is going to be pretty simple, and we’ll want to derive the default value from our component’s props, I’m going to define the x-data within EdgeJS and pass it into AlpineJS as a string.

@set('variant', variant ?? 'light')
@set('variants', variants ?? {
  light: `bg-white text-neutral-900`,
  dark: `bg-black text-neutral-100`,
  blue: `border-transparent bg-blue-600 text-white`,
  red: `border-transparent bg-red-600 text-white`,
  green: `border-transparent bg-green-500 text-white`,
  yellow: `border-transparent bg-yellow-500 text-white`,
})

@set('slots', { ...$slots, ...$props.$slots })
@set('data', {
  show: show ?? true,
  dismissible: dismissible ?? false,
})

<div 
  x-data="{{ stringify(data) }}"
  x-show="show"
  class="{{ variants[variant] }} relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] {{ slots.icon ? 'pl-11' : '' }}">

    @if (slots.icon)
      {{{ await slots.icon() }}}
    @endif

    @if (title || slots.title)
      <h5 class="mb-1 font-medium leading-none tracking-tight">
        @if (title)
          {{ title }}
        @else
          {{{ await slots.title() }}}
        @endif
        Alert Message Headline
      </h5>
    @endif

    @set('mainSlot', await slots.main())
    @if (message || mainSlot)
      <div class="text-sm opacity-70">
        @if (message)
          {{ message }}
        @else
          {{{ mainSlot }}}
        @endif
        This is the subtext for your alert message, providing important information or instructions.
      </div>
    @endif
</div>
Copied!
  • resources
  • views
  • components
  • alert
  • index.edge

Here we’re defining a variable called data within EdgeJS that has the properties show and dismissible. Both of these properties will attempt to get their default value from the component props and use a fallback value if a prop value isn’t provided.

Then, we’re using the stringify utility to convert data into an HTML-safe string that we’re passing directly into AlpineJS as our state.

Lastly, we’re using the show value from our AlpineJS state to determine whether the alert should be displayed.

Adding Our Dismiss Button

Now we need to add our dismiss button when the alert is dismissible.

@set('variant', variant ?? 'light')
@set('variants', variants ?? {
  light: `bg-white text-neutral-900`,
  dark: `bg-black text-neutral-100`,
  blue: `border-transparent bg-blue-600 text-white`,
  red: `border-transparent bg-red-600 text-white`,
  green: `border-transparent bg-green-500 text-white`,
  yellow: `border-transparent bg-yellow-500 text-white`,
})

@set('slots', { ...$slots, ...$props.$slots })
@set('data', {
  show: show ?? true,
  dismissible: dismissible ?? false,
})

<div 
  x-data="{{ stringify(data) }}"
  x-show="show"
  class="{{ variants[variant] }} relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] {{ slots.icon ? 'pl-11' : '' }}">

  <button 
    x-show="dismissible" 
    class="absolute top-4 right-4" 
    @click="show = false">
    <svg xmlns="<http://www.w3.org/2000/svg>" class="icon icon-tabler icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
      <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
      <path d="M18 6l-12 12"></path>
      <path d="M6 6l12 12"></path>
    </svg>
  </button>

    @if (slots.icon)
      {{{ await slots.icon() }}}
    @endif

    @if (title || slots.title)
      <h5 class="mb-1 font-medium leading-none tracking-tight">
        @if (title)
          {{ title }}
        @else
          {{{ await slots.title() }}}
        @endif
        Alert Message Headline
      </h5>
    @endif

    @set('mainSlot', await slots.main())
    @if (message || mainSlot)
      <div class="text-sm opacity-70">
        @if (message)
          {{ message }}
        @else
          {{{ mainSlot }}}
        @endif
        This is the subtext for your alert message, providing important information or instructions.
      </div>
    @endif
</div>
Copied!
  • resources
  • views
  • components
  • alert
  • index.edge

Here we’ve added a button, absolutely positioned to the top-right corner of our alert. The button is only displayed when the AlpineJS variable dismissible is truthy. Then, when the button is clicked, we’re setting the show AlpineJS value to false so that the alert itself disappears.

Making Our Alert Self Destructible

Next, let’s make our alert able to conditionally auto-remove itself after a defined portion of time. For this, we’ll add two new variables to our data state.

  • selfDestruct - Controls whether the alert will self-destruct

  • selfDestructTimeout - Controls how long the alert will remain visible before self-destructing

@set('variant', variant ?? 'light')
@set('variants', variants ?? {
  light: `bg-white text-neutral-900`,
  dark: `bg-black text-neutral-100`,
  blue: `border-transparent bg-blue-600 text-white`,
  red: `border-transparent bg-red-600 text-white`,
  green: `border-transparent bg-green-500 text-white`,
  yellow: `border-transparent bg-yellow-500 text-white`,
})

@set('slots', { ...$slots, ...$props.$slots })
@set('data', {
  show: show ?? true,
  dismissible: dismissible ?? false,
  selfDestruct: selfDestruct ?? false,
  selfDestructTimeout: selfDestructTimeout ?? 5000
})

{{-- ... --}}
Copied!
  • resources
  • views
  • components
  • alert
  • index.edge

Then, when selfDestruct is truthy, we need to initialize our timeout, set to the time of our selfDestructTimeout. For this, we can make use of AlpineJS’ x-init attribute. This is called whenever AlpineJS initializes the element the x-init resides on.

@set('variant', variant ?? 'light')
@set('variants', variants ?? {
  light: `bg-white text-neutral-900`,
  dark: `bg-black text-neutral-100`,
  blue: `border-transparent bg-blue-600 text-white`,
  red: `border-transparent bg-red-600 text-white`,
  green: `border-transparent bg-green-500 text-white`,
  yellow: `border-transparent bg-yellow-500 text-white`,
})

@set('slots', { ...$slots, ...$props.$slots })
@set('data', {
  show: show ?? true,
  dismissible: dismissible ?? false,
  selfDestruct: selfDestruct ?? false,
  selfDestructTimeout: selfDestructTimeout ?? 5000
})

<div 
  x-data="{{ stringify(data) }}"
  x-show="show"
  class="{{ variants[variant] }} relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] {{ slots.icon ? 'pl-11' : '' }}">

  <button 
    x-show="dismissible"
    x-init="selfDestruct && setTimeout(() => show = false, selfDestructTimeout)" 
    class="absolute top-4 right-4" 
    @click="show = false">
    <svg xmlns="<http://www.w3.org/2000/svg>" class="icon icon-tabler icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
      <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
      <path d="M18 6l-12 12"></path>
      <path d="M6 6l12 12"></path>
    </svg>
  </button>

    @if (slots.icon)
      {{{ await slots.icon() }}}
    @endif

    @if (title || slots.title)
      <h5 class="mb-1 font-medium leading-none tracking-tight">
        @if (title)
          {{ title }}
        @else
          {{{ await slots.title() }}}
        @endif
        Alert Message Headline
      </h5>
    @endif

    @set('mainSlot', await slots.main())
    @if (message || mainSlot)
      <div class="text-sm opacity-70">
        @if (message)
          {{ message }}
        @else
          {{{ mainSlot }}}
        @endif
        This is the subtext for your alert message, providing important information or instructions.
      </div>
    @endif
</div>
Copied!
  • resources
  • views
  • components
  • alert
  • index.edge

Now, we can successfully tell our alert to be dismissible, self-destructible, or both!

@layout.app({ title: request.params().name })

  <h3 class="font-bold">Base Alert</h3>
  <div x-data class="mb-8 space-x-3 space-y-3">
    @alert({ dismissable: true, selfDestruct: true })
      @slot('icon')
        <svg class="w-4 h-4" xmlns="<http://www.w3.org/2000/svg>" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"></polyline><line x1="12" x2="20" y1="19" y2="19"></line></svg>
      @endslot

      @slot('title')
        Default, Base Alert
      @endslot

      This is our message!
    @end
  </div>

  {{-- ... --}}
@end
Copied!
  • resources
  • views
  • pages
  • components
  • alert.edge

Join the Discussion 0 comments

Create a free account to join in on the discussion
robot comment bubble

Be the first to comment!