diff --git a/docs/docs/8.x/datatables/button.md b/docs/docs/8.x/datatables/button.md
index f32d769f..2423aa22 100644
--- a/docs/docs/8.x/datatables/button.md
+++ b/docs/docs/8.x/datatables/button.md
@@ -20,6 +20,7 @@ Button::add()
| [class](#class) | Sets additional class |
| [route](#route) | Sets the button link href by using a route |
| [link](#link) | Sets the button link href |
+| [tooltip](#tooltip) | Sets a tooltip for the button |
| [attributes](#attributes) | Sets HTML attributes to the button |
| [make](#make) | Renders the button |
@@ -93,6 +94,27 @@ Sets the button link href.
->link(route('boilerplate.users.edit', $user->id))
```
+## tooltip
+
+Sets a tooltip for the button using the HTML `title` attribute.
+
+```php
+->tooltip('Edit this user')
+```
+
+The tooltip is only rendered when a non-empty string is provided, avoiding unnecessary HTML attributes.
+
+**Example with tooltip:**
+
+```php
+Button::add('Edit')
+ ->route('boilerplate.users.edit', $user->id)
+ ->icon('pencil-alt')
+ ->color('primary')
+ ->tooltip('Edit this user')
+ ->make();
+```
+
## attributes
Sets HTML attributes.
@@ -113,6 +135,8 @@ Renders the button.
## Button aliases
+### Predefined buttons
+
```php
Button::show('route.to.resource.show', $resource);
```
@@ -126,3 +150,33 @@ Button::delete('route.to.resource.destroy', $resource);
```
> `Button::delete` will show a modal to confirm the deletion. You can set another confirmation message by using the [`Datatable::locale()` method](options#locale).
+
+### Custom button helper
+
+The `custom()` method provides a convenient way to create custom buttons with all parameters in a single call:
+
+```php
+Button::custom(
+ 'route.name', // Route name (required)
+ $args, // Route arguments (optional, default: [])
+ 'icon-name', // FontAwesome icon (optional, default: '')
+ 'Tooltip text', // Tooltip text (optional, default: '')
+ 'primary', // Button color (optional, default: 'default')
+ ['data-action' => 'x'] // HTML attributes (optional, default: [])
+);
+```
+
+**Example:**
+
+```php
+Button::custom(
+ 'users.export',
+ ['id' => $user->id],
+ 'download',
+ 'Export user data',
+ 'success',
+ ['data-confirm' => 'Export this user?']
+);
+```
+
+**Note:** The icon parameter comes before the tooltip for better ergonomics. If no icon is provided, no empty HTML markup is generated.
diff --git a/src/Datatables/Button.php b/src/Datatables/Button.php
index e38c8322..19f2f734 100644
--- a/src/Datatables/Button.php
+++ b/src/Datatables/Button.php
@@ -4,12 +4,13 @@
class Button
{
- protected $class = '';
- protected $color = 'default';
- protected $href = '#';
- protected $icon = '';
- protected $label = '';
- protected $attributes = [];
+ protected string $class = '';
+ protected string $color = 'default';
+ protected string $href = '#';
+ protected string $icon = '';
+ protected string $label = '';
+ protected array $attributes = [];
+ protected string $tooltip = '';
/**
* Instanciate a new button.
@@ -32,6 +33,28 @@ public static function add(string $label = ''): self
return new static($label);
}
+ /**
+ * Returns a custom button.
+ *
+ * @param string $route
+ * @param array|string $args
+ * @param string $icon
+ * @param string $tooltip
+ * @param string $color
+ * @param array $attributes
+ * @return string
+ */
+ public static function custom(string $route, array|string $args = [], string $icon = '', string $tooltip = '', string $color = 'default', array $attributes = []): string
+ {
+ $button = self::add()->route($route, $args)->tooltip($tooltip);
+
+ if (! empty($icon)) {
+ $button->icon($icon);
+ }
+
+ return $button->color($color)->attributes($attributes)->make();
+ }
+
/**
* Returns an edit button.
*
@@ -168,8 +191,6 @@ public function link(string $href): self
*/
public function make(): string
{
- $str = '%s%s';
-
if (! empty($this->label) && ! empty($this->icon)) {
$this->label = $this->label.' ';
}
@@ -182,6 +203,22 @@ public function make(): string
return sprintf('%s="%s"', $k, $this->attributes[$k]);
}, array_keys($this->attributes)));
- return sprintf($str, $this->href, $this->color, $this->class, $attributes, $this->label, $this->icon);
+ $tooltip = ! empty($this->tooltip) ? sprintf(' title="%s"', $this->tooltip) : '';
+ $str = '%s%s';
+
+ return sprintf($str, $this->href, $tooltip, $this->color, $this->class, $attributes, $this->label, $this->icon);
+ }
+
+ /**
+ * Sets tooltip of button.
+ *
+ * @param string $tooltip
+ * @return $this
+ */
+ public function tooltip(string $tooltip): self
+ {
+ $this->tooltip = $tooltip;
+
+ return $this;
}
}
diff --git a/tests/Datatables/ButtonTest.php b/tests/Datatables/ButtonTest.php
new file mode 100644
index 00000000..bcc8374a
--- /dev/null
+++ b/tests/Datatables/ButtonTest.php
@@ -0,0 +1,128 @@
+link('/test')
+ ->tooltip('This is a tooltip')
+ ->make();
+
+ $this->assertStringContainsString('title="This is a tooltip"', $button);
+ }
+
+ public function testTooltipNotAddedWhenEmpty()
+ {
+ $button = Button::add('Test Button')
+ ->link('/test')
+ ->make();
+
+ $this->assertStringNotContainsString('title=', $button);
+ }
+
+ public function testTooltipMethodChaining()
+ {
+ $button = Button::add('Test')
+ ->link('/test')
+ ->tooltip('Tooltip text')
+ ->color('primary')
+ ->icon('star')
+ ->make();
+
+ $this->assertStringContainsString('title="Tooltip text"', $button);
+ $this->assertStringContainsString('btn-primary', $button);
+ $this->assertStringContainsString('fa-star', $button);
+ }
+
+ public function testButtonHtmlStructure()
+ {
+ $button = Button::add('Click me')
+ ->link('/action')
+ ->tooltip('Button tooltip')
+ ->color('success')
+ ->make();
+
+ $this->assertMatchesRegularExpression('/assertStringContainsString('btn-success', $button);
+ $this->assertStringContainsString('fa-star', $button);
+ $this->assertStringContainsString('/test/1', $button);
+ }
+
+ public function testCustomButtonWithAttributes()
+ {
+ $this->app['router']->get('/test', function () {
+ return 'test';
+ })->name('test.route2');
+
+ $button = Button::custom(
+ 'test.route2',
+ [],
+ 'heart',
+ 'Custom tooltip',
+ 'primary',
+ ['data-action' => 'custom-action', 'data-id' => '123']
+ );
+
+ $this->assertStringContainsString('data-action="custom-action"', $button);
+ $this->assertStringContainsString('data-id="123"', $button);
+ $this->assertStringContainsString('btn-primary', $button);
+ $this->assertStringContainsString('fa-heart', $button);
+ }
+
+ public function testCustomButtonWithDefaults()
+ {
+ $this->app['router']->get('/default', function () {
+ return 'test';
+ })->name('test.default');
+
+ $button = Button::custom('test.default');
+
+ $this->assertStringNotContainsString('title=', $button);
+ $this->assertStringContainsString('btn-default', $button);
+ $this->assertStringNotContainsString('fa-', $button); // No icon by default
+ }
+
+ public function testCustomButtonArgumentOrder()
+ {
+ // Test that $icon comes before $tooltip in the method signature
+ $this->app['router']->get('/order', function () {
+ return 'test';
+ })->name('test.order');
+
+ $button = Button::custom(
+ 'test.order',
+ [],
+ 'cog', // $icon (3rd parameter)
+ 'Tooltip', // $tooltip (4th parameter)
+ 'warning' // $color (5th parameter)
+ );
+
+ $this->assertStringContainsString('fa-cog', $button);
+ $this->assertStringContainsString('title="Tooltip"', $button);
+ $this->assertStringContainsString('btn-warning', $button);
+ }
+}