Skip to content
Merged
59 changes: 59 additions & 0 deletions examples/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,65 @@ You can also get a dismissible alert by specifying `dismissible`, this flag adds
| dismissible | Whether the alert box is dismissable or not [default=false] | no |
| title | Title override instead of using the generic one based on the type [default=null] | no |

### Toasts

> Note: Requires various icons to be present to properly work. Relies on [Blade SVG](https://github.com/adamwathan/blade-svg) to load them.

Simple inline usage with a string message (if not specified, it sets `type="info"` by default):

`<x-ark-toast message="your-message-here" />`

The available types are: "info", "success", "warning", "error", "question".

Additionally, you can use it as a block and set the content:

```php
<x-ark-alert type="info" title="Account Updated">
<x-slot name="message">
Your account has been updated.
</x-slot>
</x-ark-alert>
```

To configure click events, you may pass `wire-close` or `alpine-close` props to the component depending on whether you want to handle clicks with Livewire or Alpine.

```php
<x-ark-alert
type="info"
message="Account updated"
alpine-click="submit" />
```

If handling Livewire clicks, you may pass the `target` prop to the component to set which Livewire method is targeted. When toast is clicked and the targeted method is executed, a loading spinner will be shown instead of the "dismiss" button. You may read more about Livewire targeting on the [Livewire docs](https://laravel-livewire.com/docs/2.x/loading-states#targeting-actions).

```php
<x-ark-alert
type="info"
message="Account updated"
wire-click="dismiss({{ $id }})"
target="dismiss" />
```

Of course, you may pass any additional HTML attributes to the component and they'll be merged with the base HTML attributes in the root element.

```php
<x-ark-alert
type="info"
message="Account updated"
alpine-click="submit"
class="max-w-4xl"
aria-label="Toast" />
```

| Parameter | Description | Required |
| ------------ | -------------------------------------------------------------------------------- | -------- |
| message | Alternative to slot #message | no |
| type | Type of toast box [default=info] | no |
| alpine-click | Alpine.js action to run on click [default=null] | no |
| wire-click | Livewire action to run on click [default=null] | no |
| target | Targeting Livewire method for a loading spinner [default=null] | no |
| title | Title override instead of using the generic one based on the type [default=null] | no |

### Accordion

| Parameter | Description | Required |
Expand Down
2 changes: 1 addition & 1 deletion resources/assets/css/_alerts.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
}

.alert-wrapper .alert-title .alert-icon {
@apply inline-flex w-3.5 h-3.5 mr-2;
@apply inline-flex w-4 h-4 mr-2;
}

.alert-wrapper .alert-title span {
Expand Down
59 changes: 39 additions & 20 deletions resources/assets/css/_toasts.css
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
@layer components {
.toast {
@apply relative inline-flex flex-col items-center max-w-4xl p-4 text-sm select-none text-theme-secondary-900 dark:text-theme-secondary-500 dark:bg-theme-secondary-800 sm:flex-row sm:space-x-4 rounded-xl;
@apply relative inline-flex flex-col sm:flex-row items-center max-w-4xl text-sm select-none text-theme-secondary-900 dark:text-theme-secondary-200 dark:bg-theme-secondary-800 rounded-xl overflow-hidden;
}

.toast-body {
@apply mt-4 text-center sm:pr-6 sm:mt-0 sm:text-left;
@apply p-4 text-left mr-4 sm:mt-0;
}

.toast-icon {
@apply flex items-center justify-center flex-shrink-0 text-white rounded w-11 h-11;
@apply space-x-2 flex items-center px-4 sm:px-0 py-2 sm:py-0 sm:justify-center flex-shrink-0 text-white w-full sm:w-12 sm:h-14;
}

.toast-button {
@apply absolute top-0 right-0 flex items-center justify-center flex-shrink-0 m-4 rounded sm:m-0 sm:top-auto sm:right-auto sm:relative w-11 h-11 text-theme-secondary-900;
.toast-button,
.toast-spinner {
@apply absolute sm:relative top-0 sm:top-auto right-0 sm:right-auto m-2.5 sm:my-0 sm:ml-0 flex items-center justify-center flex-shrink-0 mr-4 rounded text-theme-secondary-900 dark:text-white sm:dark:text-theme-secondary-600;
}

.toast-info {
Expand All @@ -32,34 +33,52 @@
}

.toast-info .toast-icon {
@apply bg-theme-primary-600;
@apply bg-theme-primary-100 dark:bg-theme-primary-700 text-theme-primary-700 dark:text-white;
}
.toast-warning .toast-icon {
@apply bg-theme-warning-600;
@apply bg-theme-warning-100 dark:bg-theme-warning-700 text-theme-warning-900 dark:text-white;
}
.toast-danger .toast-icon {
@apply bg-theme-danger-400;
@apply bg-theme-danger-100 dark:bg-theme-danger-500 text-theme-danger-700 dark:text-white;
}
.toast-hint .toast-icon {
@apply bg-theme-hint-500;
@apply bg-theme-hint-100 dark:bg-theme-hint-700 text-theme-hint-700 dark:text-white;
}
.toast-success .toast-icon {
@apply bg-theme-success-600;
@apply bg-theme-success-100 dark:bg-theme-success-700 text-theme-success-700 dark:text-white;
}

.toast-info .toast-button {
@apply bg-theme-primary-100 hover:bg-theme-primary-200 dark:bg-theme-secondary-900 dark:text-theme-secondary-600 dark:hover:bg-theme-secondary-500 dark:hover:text-theme-secondary-400;
.dark .toast-info .toast-spinner,
.dark .toast-warning .toast-spinner,
.dark .toast-danger .toast-spinner,
.dark .toast-hint .toast-spinner,
.dark .toast-success .toast-spinner {
--theme-color-spinner: var(--theme-color-secondary-900);
}
.toast-warning .toast-button {
@apply bg-theme-warning-100 hover:bg-theme-warning-200 dark:bg-theme-secondary-900 dark:text-theme-secondary-600 dark:hover:bg-theme-secondary-500 dark:hover:text-theme-secondary-400;

.toast-info .toast-spinner {
@apply text-theme-primary-500 dark:text-theme-primary-600;

--theme-color-spinner: var(--theme-color-primary-200);
}
.toast-danger .toast-button {
@apply bg-theme-danger-100 hover:bg-theme-danger-200 dark:bg-theme-secondary-900 dark:text-theme-secondary-600 dark:hover:bg-theme-secondary-500 dark:hover:text-theme-secondary-400;
.toast-warning .toast-spinner {
@apply text-theme-warning-500 dark:text-theme-warning-600;

--theme-color-spinner: var(--theme-color-warning-200);
}
.toast-hint .toast-button {
@apply bg-theme-hint-100 hover:bg-theme-hint-200 dark:bg-theme-secondary-900 dark:text-theme-secondary-600 dark:hover:bg-theme-secondary-500 dark:hover:text-theme-secondary-400;
.toast-danger .toast-spinner {
@apply text-theme-danger-500 dark:text-theme-danger-600;

--theme-color-spinner: var(--theme-color-danger-200);
}
.toast-success .toast-button {
@apply bg-theme-success-100 hover:bg-theme-success-200 dark:bg-theme-secondary-900 dark:text-theme-secondary-600 dark:hover:bg-theme-secondary-500 dark:hover:text-theme-secondary-400;
.toast-hint .toast-spinner {
@apply text-theme-hint-500 dark:text-theme-hint-600;

--theme-color-spinner: var(--theme-color-hint-200);
}
.toast-success .toast-spinner {
@apply text-theme-success-500 dark:text-theme-success-600;

--theme-color-spinner: var(--theme-color-success-200);
}
}
12 changes: 12 additions & 0 deletions resources/lang/en/toasts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

return [
'info' => 'Information',
'success' => 'Success',
'warning' => 'Warning',
'error' => 'Error',
'danger' => 'Error',
'hint' => 'Hint',
];
2 changes: 1 addition & 1 deletion resources/views/alert.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class="alert-content-wrapper"
<x-ark-icon
:name="alertIcon($type)"
class="alert-icon"
size="xs"
size="sm"
/>

<span>
Expand Down
10 changes: 7 additions & 3 deletions resources/views/livewire/toast.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
@foreach ($toasts as $key => $toast)
<div
class="z-20 cursor-pointer"
x-data="{ dismiss() { livewire.emit('dismissToast', '{{ $key }}' ) } }"
x-data="{
dismiss() {
Livewire.emit('dismissToast', '{{ $key }}')
}
}"
x-init="$nextTick(() => setTimeout(() => dismiss(), 5000))"
@click="dismiss()"
wire:click="dismissToast('{{ $key }}')"
wire:key="{{ $key }}"
>
<x-ark-toast :type="$toast['type']" wire-close="dismissToast('{{ $key }}')">
<x-ark-toast :type="$toast['type']" wire-close="dismissToast('{{ $key }}')" target="dismissToast">
<x-slot name='message'>
{!! $toast['message'] !!}
</x-slot>
Expand Down
3 changes: 2 additions & 1 deletion resources/views/spinner-icon.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
'stroke' => 'currentColor',
'circleColor' => null,
'size' => 'w-6 h-6',
'strokeWidth' => 6,
])

<svg class="{{ $size }}" viewBox="-3 -3 43 43" xmlns="http://www.w3.org/2000/svg" stroke="currentColor">
<g transform="translate(1 1)" stroke-width="6" fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="{{ $strokeWidth }}" fill="none" fill-rule="evenodd">
<circle cx="18" cy="18" r="18" @if($circleColor) stroke="var(--theme-color-{{ $circleColor }})" @else stroke-opacity=".5" @endif />
<path d="M36 18c0-9.94-8.06-18-18-18"><animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"/></path>
</g>
Expand Down
33 changes: 25 additions & 8 deletions resources/views/toast.blade.php
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
@props([
@props ([
'type' => 'info',
'title' => null,
'message' => '',
'wireClose' => false,
'alpineClose' => false,
'target' => null,
])

@php
$icon = Arr::get([
'warning' => 'circle.exclamation-mark',
'error' => 'circle.cross-big',
'danger' => 'circle.cross-big',
'success' => 'circle.check-mark-big',
'error' => 'circle.cross',
'danger' => 'circle.cross',
'success' => 'circle.check-mark',
'info' => 'circle.info',
'hint' => 'circle.question-mark-big',
'hint' => 'circle.question-mark',
], $type);

$toastClass = Arr::get([
Expand All @@ -25,9 +27,10 @@
], $type);
@endphp

<div {{ $attributes->merge(['class' => 'toast ' . $toastClass]) }}>
<div role="alert" aria-live="polite" {{ $attributes->class('toast')->class($toastClass) }}>
<span class="toast-icon">
<x-ark-icon :name="$icon"/>
<x-ark-icon :name="$icon" size="sm" />
<span class="text-sm font-semibold sm:hidden">{{ $title ?? trans('ui::toasts.'.$type) }}</span>
</span>

<div class="toast-body">{{ $message }}</div>
Expand All @@ -37,7 +40,21 @@
@if ($alpineClose) @click="{{ $alpineClose }}" @endif
type="button"
class="toast-button"
@if ($target)
wire:loading.remove
wire:target="{{ $target }}"
@endif
>
<x-ark-icon name="cross" />
<x-ark-icon name="cross" size="sm" />
</button>

<div
class="toast-spinner"
@if ($target)
wire:loading
wire:target="{{ $target }}"
@endif
>
<x-ark-spinner-icon circle-color="spinner" :stroke-width="3" />
</div>
</div>