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 empty state to chart widget #13408

Draft
wants to merge 5 commits into
base: 3.x
Choose a base branch
from
Draft
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
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
69 changes: 69 additions & 0 deletions packages/widgets/docs/03-charts.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,72 @@ 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.
By default, the chart will not display this by itself. You have to figure out when to display it and simply call `$this->empty()` from another method of a Chart Widget.
Usually, you would call this method from the `getData()` method, when there is no data to display.

```php
protected function getData(): array
{
...
if ($noData) {
$this->empty();
}
}
```

## 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');
```
142 changes: 79 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,94 @@ class="w-max sm:-my-2"
wire:poll.{{ $pollingInterval }}="updateChartData"
@endif
>
<div
@if (FilamentView::hasSpaMode())
ax-load="visible"
@if ($empty)
@if ($emptyState = $this->getEmptyState())
{{ $emptyState }}
@else
ax-load
<div>
<x-filament-widgets::empty-state
:actions="$this->getEmptyStateActions()"
:description="$this->getEmptyStateDescription()"
:heading="$this->getEmptyStateHeading()"
:icon="$this->getEmptyStateIcon()"
/>
</div>
@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 }}"
@else
<div
@if (FilamentView::hasSpaMode())
ax-load="visible"
@else
ax-load
@endif
></canvas>

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

<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="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="gridColorElement"
class="text-gray-200 dark:text-gray-800"
></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="textColorElement"
class="text-gray-500 dark:text-gray-400"
></span>
</div>
<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
</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 Closure;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Support\Concerns\EvaluatesClosures;
use Filament\Support\Facades\FilamentIcon;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Contracts\View\View;

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;
}
}
Loading