Compare commits

...

3 Commits

Author SHA1 Message Date
Pedro Cabral
1bc3e5f885 added button component 2026-05-08 20:38:00 +02:00
Pedro Cabral
1b5d3f5bdf added storybook 2026-04-28 12:09:02 +02:00
Pedro Cabral
5c04ff57ac added colour palette 2026-04-28 12:08:53 +02:00
7 changed files with 249 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace App\View\Components;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Button extends Component
{
public const VARIANT_PRIMARY = 'primary';
public const VARIANT_SECONDARY = 'secondary';
public const VARIANT_OUTLINE = 'outline';
public function __construct(public string $variant = self::VARIANT_PRIMARY, public bool $disabled = false)
{
//
}
public function classes(): string
{
$base = 'inline-flex items-center justify-center px-4 py-2 rounded-md text-sm font-medium transition ' .
'duration-150 ease-in-out';
$classes = match ($this->variant) {
self::VARIANT_SECONDARY => [
'base' => 'bg-secondary text-text',
'hover' => 'hover:bg-secondary-dark',
'disabled' => 'disabled:bg-secondary',
],
self::VARIANT_OUTLINE => [
'base' => 'bg-surface border border-primary text-primary',
'hover' => 'hover:bg-primary hover:text-text-inverted',
'disabled' => 'disabled:bg-surface disabled:text-primary',
],
default => [
'base' => 'bg-primary text-text-inverted',
'hover' => 'hover:bg-primary-dark',
'disabled' => 'disabled:bg-primary'
]
};
$baseDisabled = 'disabled:cursor-not-allowed disabled:opacity-60';
return trim(
$base . ' ' .
$classes['base'] . ' ' .
$classes['hover'] . ' ' .
$baseDisabled . ' ' .
$classes['disabled']
);
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View
{
return view('components.button');
}
}

View File

@@ -8,4 +8,32 @@
@theme {
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
/* PRIMARY */
--color-primary: #0F172A;
--color-primary-light: #1E293B;
--color-primary-dark: #020617;
/* SECONDARY */
--color-secondary: #F59E0B;
--color-secondary-light: #FCD34D;
--color-secondary-dark: #B45309;
/* BACKGROUND */
--color-background: #F8FAFC;
--color-background-dark: #E2E8F0;
/* SURFACE */
--color-surface: #ffffff;
--color-surface-muted: #F1F5F9;
/* TEXT */
--color-text: #0F172A;
--color-text-muted: #64748B;
--color-text-inverted: #FFFFFF;
/* STATES */
--color-success: #22C55E;
--color-warning: #F97316;
--color-error: #EF4444;
}

View File

@@ -0,0 +1,8 @@
<button
{{ $attributes
->merge(['class' => $classes()])
->merge(['disabled' => $disabled])
}}
>
{{ $slot }}
</button>

View File

@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Storybook</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-background text-text p-10 space-y-12">
<h1 class="text-3xl font-bold">Storybook</h1>
<!-- COLOURS -->
<section class="space-y-4">
<h2 class="text-xl font-semibold">Colors</h2>
<div class="grid grid-cols-3 gap-4">
<div class="p-4 bg-primary text-text-inverted rounded">Primary</div>
<div class="p-4 bg-secondary text-black rounded">Secondary</div>
<div class="p-4 bg-surface border rounded">Surface</div>
</div>
</section>
<!-- BUTTONS -->
<section class="space-y-4">
<h2 class="text-xl font-semibold">Buttons</h2>
<h3 class="text-l font-semibold">Enabled</h3>
<div class="flex gap-4">
<x-button onclick="alert('Default was clicked!')">Default</x-button>
<x-button variant="primary" onclick="alert('Primary was clicked!')">Primary</x-button>
<x-button variant="secondary" onclick="alert('Secondary was clicked!')">Secondary</x-button>
<x-button variant="outline" onclick="alert('Outline was clicked!')">Outline</x-button>
</div>
<h3 class="text-l font-semibold">Disabled</h3>
<div class="flex gap-4">
<x-button disabled>Default</x-button>
<x-button variant="primary" disabled>Primary</x-button>
<x-button variant="secondary" disabled>Secondary</x-button>
<x-button variant="outline" disabled>Outline</x-button>
</div>
</section>
<!-- CARDS -->
<section class="space-y-4">
<h2 class="text-xl font-semibold">Cards</h2>
<div class="bg-surface p-6 rounded shadow">
<h3 class="font-semibold">Card title</h3>
<p class="text-text-muted">This is a simple card component.</p>
</div>
</section>
<!-- TYPOGRAPHY -->
<section class="space-y-2">
<h2 class="text-xl font-semibold">Typography</h2>
<p class="text-text">Default text</p>
<p class="text-text-muted">Muted text</p>
<p class="text-primary">Primary text</p>
</section>
</body>
</html>

View File

@@ -5,3 +5,7 @@ use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/storybook', function () {
return view('storybook.index');
})->name('storybook');

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Tests\Feature\Component;
use Tests\Feature\Component\DataProvider\ButtonTestDataProvider;
use Tests\TestCase;
class ButtonTest extends TestCase
{
public function testGeneratesButtonElement(): void
{
$view = $this->blade('<x-button>Click!</x-button>');
$view->assertSeeHtml('<button ');
$view->assertDontSeeHtml('<a ');
}
public function testButtonCanHaveAttributes(): void
{
$view = $this->blade('<x-button data-attr="123">Click!</x-button>');
$view->assertSeeHtml('data-attr="123"');
}
public function testDefaultVariantIsPrimary(): void
{
$view = $this->blade('<x-button>Click!</x-button>');
$view->assertSeeHtml('bg-primary text-text-inverted');
}
#[\PHPUnit\Framework\Attributes\DataProviderExternal(ButtonTestDataProvider::class, 'getDataForButtonTestWithButtons')]
public function testVariants(string $variant, string $expectedHtml): void
{
$button = sprintf('<x-button variant="%s">Click!</x-button>', $variant);
$view = $this->blade($button);
$view->assertSeeHtml($expectedHtml);
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Tests\Feature\Component\DataProvider;
use App\View\Components\Button;
class ButtonTestDataProvider
{
public static function getDataForButtonTestWithButtons(): array
{
return [
'primary variant' => [
'variant' => Button::VARIANT_PRIMARY,
'expectedHtml' => 'bg-primary text-text-inverted'
],
'secondary variant' => [
'variant' => Button::VARIANT_SECONDARY,
'expectedHtml' => 'bg-secondary text-text'
],
'outline variant' => [
'variant' => Button::VARIANT_OUTLINE,
'expectedHtml' => 'bg-surface border border-primary text-primary'
],
];
}
}