From d1b8eaea2eab43a0aa3508b94cd486a421d4aa2b Mon Sep 17 00:00:00 2001 From: Julian Gums Date: Wed, 26 Jun 2024 23:18:08 +0200 Subject: [PATCH 1/4] Add empty state to chart widgets --- .../resources/lang/de/widgets/empty-state.php | 7 + .../resources/lang/en/widgets/empty-state.php | 7 + packages/support/docs/03-icons.md | 1 + packages/widgets/docs/03-charts.md | 57 +++++++ .../resources/views/chart-widget.blade.php | 144 ++++++++++-------- .../empty-state/description.blade.php | 5 + .../components/empty-state/heading.blade.php | 5 + .../components/empty-state/index.blade.php | 46 ++++++ packages/widgets/src/ChartWidget.php | 1 + .../widgets/src/Concerns/HasEmptyState.php | 66 ++++++++ 10 files changed, 276 insertions(+), 63 deletions(-) create mode 100644 packages/panels/resources/lang/de/widgets/empty-state.php create mode 100644 packages/panels/resources/lang/en/widgets/empty-state.php create mode 100644 packages/widgets/resources/views/components/empty-state/description.blade.php create mode 100644 packages/widgets/resources/views/components/empty-state/heading.blade.php create mode 100644 packages/widgets/resources/views/components/empty-state/index.blade.php create mode 100644 packages/widgets/src/Concerns/HasEmptyState.php diff --git a/packages/panels/resources/lang/de/widgets/empty-state.php b/packages/panels/resources/lang/de/widgets/empty-state.php new file mode 100644 index 00000000000..8f315f150ae --- /dev/null +++ b/packages/panels/resources/lang/de/widgets/empty-state.php @@ -0,0 +1,7 @@ + 'Keine Daten vorhanden', + +]; diff --git a/packages/panels/resources/lang/en/widgets/empty-state.php b/packages/panels/resources/lang/en/widgets/empty-state.php new file mode 100644 index 00000000000..1eb9a70c6ab --- /dev/null +++ b/packages/panels/resources/lang/en/widgets/empty-state.php @@ -0,0 +1,7 @@ + 'Nothing to display', + +]; diff --git a/packages/support/docs/03-icons.md b/packages/support/docs/03-icons.md index a8f260b5ab8..298bc72ad26 100644 --- a/packages/support/docs/03-icons.md +++ b/packages/support/docs/03-icons.md @@ -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 diff --git a/packages/widgets/docs/03-charts.md b/packages/widgets/docs/03-charts.md index 3e598d3189d..268553bfc4d 100644 --- a/packages/widgets/docs/03-charts.md +++ b/packages/widgets/docs/03-charts.md @@ -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'); +``` diff --git a/packages/widgets/resources/views/chart-widget.blade.php b/packages/widgets/resources/views/chart-widget.blade.php index 83a2473239d..a37b52e740f 100644 --- a/packages/widgets/resources/views/chart-widget.blade.php +++ b/packages/widgets/resources/views/chart-widget.blade.php @@ -5,6 +5,7 @@ $heading = $this->getHeading(); $description = $this->getDescription(); $filters = $this->getFilters(); + $empty = $this->isEmpty(); @endphp @@ -35,79 +36,96 @@ class="w-max sm:-my-2" wire:poll.{{ $pollingInterval }}="updateChartData" @endif > + + @if ($empty) + @if ($emptyState = $this->getEmptyState()) + {{ $emptyState }} + @else +
+ +
+ @endif + @else
null, + default => 'fi-color-custom', + }, + is_string($color) ? "fi-color-{$color}" : null, + ]) + > + 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 + > + + 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, ]) - > - getMaxHeight()) - style="max-height: {{ $maxHeight }}" - @endif - > + @style([ + \Filament\Support\get_color_css_variables( + $color, + shades: [50, 400], + alias: 'widgets::chart-widget.background', + ) => $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', - ]) - > + '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', + ]) + > - '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', - ]) - > + - + +
+ @endif - -
diff --git a/packages/widgets/resources/views/components/empty-state/description.blade.php b/packages/widgets/resources/views/components/empty-state/description.blade.php new file mode 100644 index 00000000000..b839821f20b --- /dev/null +++ b/packages/widgets/resources/views/components/empty-state/description.blade.php @@ -0,0 +1,5 @@ +

class(['fi-wi-empty-state-description text-sm text-gray-500 dark:text-gray-400']) }} +> + {{ $slot }} +

diff --git a/packages/widgets/resources/views/components/empty-state/heading.blade.php b/packages/widgets/resources/views/components/empty-state/heading.blade.php new file mode 100644 index 00000000000..b4bc24312c2 --- /dev/null +++ b/packages/widgets/resources/views/components/empty-state/heading.blade.php @@ -0,0 +1,5 @@ +

class(['fi-wi-empty-state-heading text-base font-semibold leading-6 text-gray-950 dark:text-white']) }} +> + {{ $slot }} +

diff --git a/packages/widgets/resources/views/components/empty-state/index.blade.php b/packages/widgets/resources/views/components/empty-state/index.blade.php new file mode 100644 index 00000000000..bcde43c88a8 --- /dev/null +++ b/packages/widgets/resources/views/components/empty-state/index.blade.php @@ -0,0 +1,46 @@ +@php + use Filament\Support\Enums\Alignment; +@endphp + +@props([ + 'actions' => [], + 'description' => null, + 'heading', + 'icon', +]) + +
class(['fi-wi-empty-state px-6 py-12']) }} +> +
+
+ +
+ + + {{ $heading }} + + + @if ($description) + + {{ $description }} + + @endif + + @if ($actions) + + @endif +
+
diff --git a/packages/widgets/src/ChartWidget.php b/packages/widgets/src/ChartWidget.php index 5b19f0012c5..72f245d85c0 100644 --- a/packages/widgets/src/ChartWidget.php +++ b/packages/widgets/src/ChartWidget.php @@ -9,6 +9,7 @@ abstract class ChartWidget extends Widget { use Concerns\CanPoll; + use Concerns\HasEmptyState; /** * @var array | null diff --git a/packages/widgets/src/Concerns/HasEmptyState.php b/packages/widgets/src/Concerns/HasEmptyState.php new file mode 100644 index 00000000000..a5b70e9973a --- /dev/null +++ b/packages/widgets/src/Concerns/HasEmptyState.php @@ -0,0 +1,66 @@ +evaluate($this->emptyState); + } + + /** + * @return array + */ + 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; + } +} From 10eb7f34300869038d4dced152c50d6153c98b67 Mon Sep 17 00:00:00 2001 From: Julian Gums Date: Wed, 26 Jun 2024 23:28:46 +0200 Subject: [PATCH 2/4] fix style --- .../resources/views/chart-widget.blade.php | 168 +++++++++--------- .../widgets/src/Concerns/HasEmptyState.php | 6 +- 2 files changed, 86 insertions(+), 88 deletions(-) diff --git a/packages/widgets/resources/views/chart-widget.blade.php b/packages/widgets/resources/views/chart-widget.blade.php index a37b52e740f..faa91833b2e 100644 --- a/packages/widgets/resources/views/chart-widget.blade.php +++ b/packages/widgets/resources/views/chart-widget.blade.php @@ -36,96 +36,94 @@ class="w-max sm:-my-2" wire:poll.{{ $pollingInterval }}="updateChartData" @endif > - - @if ($empty) - @if ($emptyState = $this->getEmptyState()) - {{ $emptyState }} - @else -
- -
- @endif - @else -
null, - default => 'fi-color-custom', - }, - is_string($color) ? "fi-color-{$color}" : null, - ]) - > - getMaxHeight()) - style="max-height: {{ $maxHeight }}" + @if ($empty) + @if ($emptyState = $this->getEmptyState()) + {{ $emptyState }} + @else +
+ +
@endif - >
+ @else +
null, + default => 'fi-color-custom', + }, + is_string($color) ? "fi-color-{$color}" : null, + ]) + > + getMaxHeight()) + style="max-height: {{ $maxHeight }}" + @endif + > - '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', - ]) - > + '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', + ]) + > - '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', - ]) - > + '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', + ]) + > - - - -
- @endif + + +
+ @endif diff --git a/packages/widgets/src/Concerns/HasEmptyState.php b/packages/widgets/src/Concerns/HasEmptyState.php index a5b70e9973a..635fbb6500a 100644 --- a/packages/widgets/src/Concerns/HasEmptyState.php +++ b/packages/widgets/src/Concerns/HasEmptyState.php @@ -2,13 +2,13 @@ namespace Filament\Widgets\Concerns; -use Filament\Support\Concerns\EvaluatesClosures; -use Filament\Support\Facades\FilamentIcon; +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; -use Closure; trait HasEmptyState { From ceddbc0e833755d82a18517bb30efe20d31ca753 Mon Sep 17 00:00:00 2001 From: juliangums Date: Wed, 26 Jun 2024 21:29:51 +0000 Subject: [PATCH 3/4] chore: fix code style --- .../resources/views/chart-widget.blade.php | 168 +++++++++--------- .../widgets/src/Concerns/HasEmptyState.php | 6 +- 2 files changed, 86 insertions(+), 88 deletions(-) diff --git a/packages/widgets/resources/views/chart-widget.blade.php b/packages/widgets/resources/views/chart-widget.blade.php index a37b52e740f..faa91833b2e 100644 --- a/packages/widgets/resources/views/chart-widget.blade.php +++ b/packages/widgets/resources/views/chart-widget.blade.php @@ -36,96 +36,94 @@ class="w-max sm:-my-2" wire:poll.{{ $pollingInterval }}="updateChartData" @endif > - - @if ($empty) - @if ($emptyState = $this->getEmptyState()) - {{ $emptyState }} - @else -
- -
- @endif - @else -
null, - default => 'fi-color-custom', - }, - is_string($color) ? "fi-color-{$color}" : null, - ]) - > - getMaxHeight()) - style="max-height: {{ $maxHeight }}" + @if ($empty) + @if ($emptyState = $this->getEmptyState()) + {{ $emptyState }} + @else +
+ +
@endif - >
+ @else +
null, + default => 'fi-color-custom', + }, + is_string($color) ? "fi-color-{$color}" : null, + ]) + > + getMaxHeight()) + style="max-height: {{ $maxHeight }}" + @endif + > - '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', - ]) - > + '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', + ]) + > - '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', - ]) - > + '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', + ]) + > - - - -
- @endif + + +
+ @endif diff --git a/packages/widgets/src/Concerns/HasEmptyState.php b/packages/widgets/src/Concerns/HasEmptyState.php index a5b70e9973a..635fbb6500a 100644 --- a/packages/widgets/src/Concerns/HasEmptyState.php +++ b/packages/widgets/src/Concerns/HasEmptyState.php @@ -2,13 +2,13 @@ namespace Filament\Widgets\Concerns; -use Filament\Support\Concerns\EvaluatesClosures; -use Filament\Support\Facades\FilamentIcon; +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; -use Closure; trait HasEmptyState { From 68523797b834d5de82ed58b29138c128d3264249 Mon Sep 17 00:00:00 2001 From: Julian Gums Date: Wed, 26 Jun 2024 23:30:30 +0200 Subject: [PATCH 4/4] improve docs --- packages/widgets/docs/03-charts.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/widgets/docs/03-charts.md b/packages/widgets/docs/03-charts.md index 268553bfc4d..4968f7e4a85 100644 --- a/packages/widgets/docs/03-charts.md +++ b/packages/widgets/docs/03-charts.md @@ -325,6 +325,18 @@ You can find out more about [asset registration](../support/assets), and even [r ## 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