Skip to content

Commit

Permalink
Add empty state to chart widgets
Browse files Browse the repository at this point in the history
  • Loading branch information
juliangums committed Jun 26, 2024
1 parent 4cb232f commit d1b8eae
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 63 deletions.
7 changes: 7 additions & 0 deletions packages/panels/resources/lang/de/widgets/empty-state.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

return [

'heading' => 'Keine Daten vorhanden',

];
7 changes: 7 additions & 0 deletions packages/panels/resources/lang/en/widgets/empty-state.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

return [

'heading' => 'Nothing to display',

];
1 change: 1 addition & 0 deletions packages/support/docs/03-icons.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Alternatively, you may pass an SVG element into the component's slot instead of
- `panels::widgets.account.logout-button` - Button in the account widget to log out
- `panels::widgets.filament-info.open-documentation-button` - Button to open the documentation from the Filament info widget
- `panels::widgets.filament-info.open-github-button` - Button to open GitHub from the Filament info widget
- `panels::widgets.empty-state` - Empty state icon for chart widgets

### Form Builder icon aliases

Expand Down
57 changes: 57 additions & 0 deletions packages/widgets/docs/03-charts.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,60 @@ FilamentAsset::register([
```

You can find out more about [asset registration](../support/assets), and even [register assets for a specific panel](../panels/configuration#registering-assets-for-a-panel).

---

## Empty state

The chart's "empty state" is rendered when there are is no data to display.

## Setting the empty state heading

To customize the heading of the empty state, set `$emptyStateHeading`:

```php
protected string | Htmlable | Closure | null $emptyStateHeading = 'No data to display :(';
```

## Setting the empty state description

To customize the description of the empty state, set `$emptyStateDescription`:

```php
protected string | Htmlable | Closure | null $emptyStateDescription = 'You will see data here once you added some more of it.';
```

## Setting the empty state icon

To customize the [icon](https://blade-ui-kit.com/blade-icons?set=1#search) of the empty state, set `$emptyStateIcon`:

```php
protected string | Closure | null $emptyStateIcon = 'heroicon-o-chart-pie';
```

## Adding empty state actions

You can add Actions or Action Groups to the empty state to prompt users to take action. Simply override the `getEmptyStateActions()` method:

```php
use Filament\Actions\Action;

public function getEmptyStateActions(): array
{
return [
Action::make('create')
->label('Create post')
->url(route('posts.create'))
->icon('heroicon-m-plus')
->button(),
];
}
```

## Using a custom empty state view

You may use a completely custom empty state view by passing it to the `$emptyState`:

```php
protected View | Htmlable | Closure | null $emptyState = view('empty-state');
```
144 changes: 81 additions & 63 deletions packages/widgets/resources/views/chart-widget.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
$heading = $this->getHeading();
$description = $this->getDescription();
$filters = $this->getFilters();
$empty = $this->isEmpty();
@endphp

<x-filament-widgets::widget class="fi-wi-chart">
Expand Down Expand Up @@ -35,79 +36,96 @@ class="w-max sm:-my-2"
wire:poll.{{ $pollingInterval }}="updateChartData"
@endif
>

@if ($empty)
@if ($emptyState = $this->getEmptyState())
{{ $emptyState }}
@else
<div>
<x-filament-widgets::empty-state
:actions="$this->getEmptyStateActions()"
:description="$this->getEmptyStateDescription()"
:heading="$this->getEmptyStateHeading()"
:icon="$this->getEmptyStateIcon()"
/>
</div>
@endif
@else
<div
@if (FilamentView::hasSpaMode())
ax-load="visible"
@else
ax-load
@if (FilamentView::hasSpaMode())
ax-load="visible"
@else
ax-load
@endif
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('chart', 'filament/widgets') }}"
wire:ignore
x-data="chart({
cachedData: @js($this->getCachedData()),
options: @js($this->getOptions()),
type: @js($this->getType()),
})"
x-ignore
@class([
match ($color) {
'gray' => null,
default => 'fi-color-custom',
},
is_string($color) ? "fi-color-{$color}" : null,
])
>
<canvas
x-ref="canvas"
@if ($maxHeight = $this->getMaxHeight())
style="max-height: {{ $maxHeight }}"
@endif
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('chart', 'filament/widgets') }}"
wire:ignore
x-data="chart({
cachedData: @js($this->getCachedData()),
options: @js($this->getOptions()),
type: @js($this->getType()),
})"
x-ignore
></canvas>

<span
x-ref="backgroundColorElement"
@class([
match ($color) {
'gray' => null,
default => 'fi-color-custom',
'gray' => 'text-gray-100 dark:text-gray-800',
default => 'text-custom-50 dark:text-custom-400/10',
},
is_string($color) ? "fi-color-{$color}" : null,
])
>
<canvas
x-ref="canvas"
@if ($maxHeight = $this->getMaxHeight())
style="max-height: {{ $maxHeight }}"
@endif
></canvas>
@style([
\Filament\Support\get_color_css_variables(
$color,
shades: [50, 400],
alias: 'widgets::chart-widget.background',
) => $color !== 'gray',
])
></span>

<span
x-ref="backgroundColorElement"
@class([
match ($color) {
'gray' => 'text-gray-100 dark:text-gray-800',
default => 'text-custom-50 dark:text-custom-400/10',
},
])
@style([
\Filament\Support\get_color_css_variables(
$color,
shades: [50, 400],
alias: 'widgets::chart-widget.background',
) => $color !== 'gray',
])
></span>
<span
x-ref="borderColorElement"
@class([
match ($color) {
'gray' => 'text-gray-400',
default => 'text-custom-500 dark:text-custom-400',
},
])
@style([
\Filament\Support\get_color_css_variables(
$color,
shades: [400, 500],
alias: 'widgets::chart-widget.border',
) => $color !== 'gray',
])
></span>

<span
x-ref="borderColorElement"
@class([
match ($color) {
'gray' => 'text-gray-400',
default => 'text-custom-500 dark:text-custom-400',
},
])
@style([
\Filament\Support\get_color_css_variables(
$color,
shades: [400, 500],
alias: 'widgets::chart-widget.border',
) => $color !== 'gray',
])
></span>
<span
x-ref="gridColorElement"
class="text-gray-200 dark:text-gray-800"
></span>

<span
x-ref="gridColorElement"
class="text-gray-200 dark:text-gray-800"
></span>
<span
x-ref="textColorElement"
class="text-gray-500 dark:text-gray-400"
></span>
</div>
@endif

<span
x-ref="textColorElement"
class="text-gray-500 dark:text-gray-400"
></span>
</div>
</div>
</x-filament::section>
</x-filament-widgets::widget>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<p
{{ $attributes->class(['fi-wi-empty-state-description text-sm text-gray-500 dark:text-gray-400']) }}
>
{{ $slot }}
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<h4
{{ $attributes->class(['fi-wi-empty-state-heading text-base font-semibold leading-6 text-gray-950 dark:text-white']) }}
>
{{ $slot }}
</h4>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@php
use Filament\Support\Enums\Alignment;
@endphp

@props([
'actions' => [],
'description' => null,
'heading',
'icon',
])

<div
{{ $attributes->class(['fi-wi-empty-state px-6 py-12']) }}
>
<div
class="fi-wi-empty-state-content mx-auto grid max-w-lg justify-items-center text-center"
>
<div
class="fi-wi-empty-state-icon-ctn mb-4 rounded-full bg-gray-100 p-3 dark:bg-gray-500/20"
>
<x-filament::icon
:icon="$icon"
class="fi-wi-empty-state-icon h-6 w-6 text-gray-500 dark:text-gray-400"
/>
</div>

<x-filament-widgets::empty-state.heading>
{{ $heading }}
</x-filament-widgets::empty-state.heading>

@if ($description)
<x-filament-widgets::empty-state.description class="mt-1">
{{ $description }}
</x-filament-widgets::empty-state.description>
@endif

@if ($actions)
<x-filament::actions
:actions="$actions"
:alignment="Alignment::Center"
wrap
class="mt-6"
/>
@endif
</div>
</div>
1 change: 1 addition & 0 deletions packages/widgets/src/ChartWidget.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
abstract class ChartWidget extends Widget
{
use Concerns\CanPoll;
use Concerns\HasEmptyState;

/**
* @var array<string, mixed> | null
Expand Down
66 changes: 66 additions & 0 deletions packages/widgets/src/Concerns/HasEmptyState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Filament\Widgets\Concerns;

use Filament\Support\Concerns\EvaluatesClosures;
use Filament\Support\Facades\FilamentIcon;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\View\View;
use Closure;

trait HasEmptyState
{
use EvaluatesClosures;

protected View | Htmlable | Closure | null $emptyState = null;

protected string | Htmlable | Closure | null $emptyStateDescription = null;

protected string | Htmlable | Closure | null $emptyStateHeading = null;

protected string | Closure | null $emptyStateIcon = null;

protected bool $empty = false;

public function getEmptyState(): View | Htmlable | null
{
return $this->evaluate($this->emptyState);
}

/**
* @return array<Action | ActionGroup>
*/
public function getEmptyStateActions(): array
{
return [];
}

public function getEmptyStateDescription(): string | Htmlable | null
{
return $this->evaluate($this->emptyStateDescription);
}

public function getEmptyStateHeading(): string | Htmlable
{
return $this->evaluate($this->emptyStateHeading) ?? __('filament-panels::widgets/empty-state.heading');
}

public function getEmptyStateIcon(): string
{
return $this->evaluate($this->emptyStateIcon)
?? FilamentIcon::resolve('panels::widgets.empty-state')
?? 'heroicon-o-x-mark';
}

public function empty(bool $empty = true): void
{
$this->empty = $empty;
}

public function isEmpty(): bool
{
return $this->empty;
}
}

0 comments on commit d1b8eae

Please sign in to comment.