33 Commits

Author SHA1 Message Date
brabli 423b696525 Update logic 2026-04-30 16:35:19 +01:00
brabli b994b83fdb Update border colour 2026-04-30 16:35:15 +01:00
brabli 12169001d2 Update changelog 2026-04-30 15:52:53 +01:00
brabli cc7a2d5cd4 Update readme 2026-04-30 15:52:32 +01:00
brabli 1bd216f4a5 Remove glossy from outline badges 2026-04-30 15:52:20 +01:00
brabli 3fb9714010 Update showcase 2026-04-30 15:52:00 +01:00
brabli 1e8ac58578 Add aria labels 2026-04-30 15:51:56 +01:00
brabli d7ca3d9285 Update image 2026-04-30 15:51:41 +01:00
brabli 197f9a109e Add showcase image 2026-04-30 15:32:40 +01:00
brabli 67465e9790 Remove icon name 2026-04-30 15:31:42 +01:00
brabli d0a84f135d Remove unneeded service 2026-04-30 15:28:01 +01:00
brabli bf035399ee Update changelog 2026-04-30 15:25:01 +01:00
brabli 40f9aaceef Update readme 2026-04-30 15:20:39 +01:00
brabli a7aabcebef Update showcase 2026-04-30 15:20:29 +01:00
brabli fd6bf6ad4f Update badge logic 2026-04-30 15:20:24 +01:00
brabli 78afab2450 Update colours 2026-04-30 15:20:16 +01:00
brabli df76af9cdf Create badgeable trait 2026-04-30 14:40:45 +01:00
brabli 25d5441184 Rename sheen to glossy 2026-04-30 14:40:39 +01:00
brabli 5046edb4c9 Add shadow sm class to base 2026-04-30 14:27:41 +01:00
brabli 6a9cd627e2 Add serve command 2026-04-30 12:23:30 +01:00
brabli 23e02068c9 Update compose.yml 2026-04-30 12:23:18 +01:00
brabli 949d268b84 Add dev deps to serve twig files 2026-04-30 12:23:09 +01:00
brabli 5f1404d77c Add showcase code 2026-04-30 12:22:50 +01:00
brabli 93258abd38 Update logic 2026-04-30 12:17:08 +01:00
brabli b4cf626468 Render icon 2026-04-30 12:16:58 +01:00
brabli fa04d7e463 Update gitignore 2026-04-30 12:16:31 +01:00
brabli 9e85f5f9d6 Update readme 2026-04-29 17:33:13 +01:00
brabli 9cb7bf23a6 Render icon 2026-04-29 17:33:03 +01:00
brabli 1b9751324b Remove conditional 2026-04-29 17:07:00 +01:00
brabli c84f2b0973 Add icon param 2026-04-29 17:05:12 +01:00
brabli 4ecf0f2feb Update method sig 2026-04-29 16:15:18 +01:00
brabli 80d24dc948 Add get badge icon method 2026-04-29 16:09:37 +01:00
brabli bf2f473038 Replace tabs with 4 spaces 2026-04-27 16:57:04 +01:00
17 changed files with 456 additions and 50 deletions
+1
View File
@@ -5,3 +5,4 @@ composer.lock
.phpunit.result.cache .phpunit.result.cache
/var /var
.php-cs-fixer.cache .php-cs-fixer.cache
/dev/var
+11
View File
@@ -1,5 +1,16 @@
# Changelog # Changelog
## [x.x.x] - xxxx-xx-xx
## [2.0.0] - 2026-04-30
- Add new colours
- Update existing colours and styles slightly
- Add `getBadgeIcon` method to `BadgeableInterface`
- Add `BadgeableTrait` for a default implementation of `BadgeableInterface`
- Add new attribute `glossy` to render a glossy finish over the badge
- Add new attribute `icon` to render an icon next to the the badge label
- Add `aria` attributes to badges
## [1.1.0] - 2025-11-13 ## [1.1.0] - 2025-11-13
- Add new badge colours - Add new badge colours
- Tweak error message - Tweak error message
+52 -4
View File
@@ -2,7 +2,7 @@
Create badges from objects or as standalone elements. Create badges from objects or as standalone elements.
IMAGE HERE ![PCM Badge Bundle showcase](docs/showcase.png)
```twig ```twig
<twig:Pcm:Badge :obj="job.kind">{{ job.kind.label }}</twig:Pcm:Badge> <twig:Pcm:Badge :obj="job.kind">{{ job.kind.label }}</twig:Pcm:Badge>
@@ -20,9 +20,14 @@ Create badges from objects or as standalone elements.
Any object that you would like to be able to be turned into a badge must implement `BadgeableInterface`. Any object that you would like to be able to be turned into a badge must implement `BadgeableInterface`.
This interface specifies a single method `getBadgeColour()` which expects an instance of the enum `BadgeColour` to be returned and is used to determine the colour of the rendered badge. You can use `BadgeableTrait` to get a default implementation.
This method can contain as much logic in it as you'd like to return different colours of badge under different circumstances. EG: The interface specifies two methods:
- `getBadgeColour(): BadgeColour` — returns the enum case that determines the badge's colour.
- `getBadgeIcon(): ?string` — returns an [Iconify](https://iconify.design) icon name (e.g. `material-symbols:add-alert`), or `null` for no icon.
Either method can contain as much logic as you like:
```php ```php
// Job.php // Job.php
@@ -37,6 +42,11 @@ public function getBadgeColour(): BadgeColour
default => BadgeColour::GREY default => BadgeColour::GREY
}; };
} }
public function getBadgeIcon(): ?string
{
return null;
}
``` ```
You then specify the object using the `:obj` prop when rendering the badge: You then specify the object using the `:obj` prop when rendering the badge:
@@ -47,6 +57,26 @@ You then specify the object using the `:obj` prop when rendering the badge:
<twig:Pcm:Badge :obj="job">{{ job.kind.value }}</twig:Pcm:Badge> <twig:Pcm:Badge :obj="job">{{ job.kind.value }}</twig:Pcm:Badge>
``` ```
## Using `BadgeableTrait`
If you only care about one of the interface methods, use `BadgeableTrait` for sensible defaults (`BadgeColour::DEFAULT` and no icon) and override only what you need:
```php
use Pcm\BadgeBundle\Interface\BadgeableInterface;
use Pcm\BadgeBundle\Trait\BadgeableTrait;
class Job implements BadgeableInterface
{
use BadgeableTrait;
public function getBadgeColour(): BadgeColour
{
return BadgeColour::BLUE;
}
// getBadgeIcon() inherited from the trait — returns null
}
```
<br> <br>
# Standalone badges # Standalone badges
@@ -70,10 +100,14 @@ A badge must contain either an `obj` or a `colour`, but not both.
obj="{{ job.kind }}" obj="{{ job.kind }}"
``` ```
`colour` - One of the available colours specified by the `Badge` enum. For a full list of acceptable values type in some junk and read the exception message. `colour` - One of the available colours specified by the `BadgeColour` enum. The palette covers the usual primaries plus tones like `cyan`, `indigo`, `purple`, `slate`, `stone`, `teal` and `violet` — see `src/Enum/BadgeColour.php` for the full list, or pass an invalid value and read the exception message.
`outline` - A boolean attribute that changes the style of the badge to an outline. `outline` - A boolean attribute that changes the style of the badge to an outline.
`glossy` - A boolean attribute that adds a gradient sheen to the badge. Cannot be combined with `outline` — passing both will throw an exception.
`icon` - Render an icon to the left of the label. Pass an [Iconify](https://iconify.design) name (e.g. `icon="material-symbols:add-alert"`) to use a specific icon, or pass it as a boolean (`icon`) when using `:obj` to use the icon returned by `getBadgeIcon()`. Requires `symfony/ux-icons` in the host project.
`class` - Extra classes you want to add to the badge element. These are merged with the badge base classes taking priority in case of conflicts. `class` - Extra classes you want to add to the badge element. These are merged with the badge base classes taking priority in case of conflicts.
`label` - Badge label text. Content inside the content block will be prioritised over the label attribute if present. `label` - Badge label text. Content inside the content block will be prioritised over the label attribute if present.
@@ -93,3 +127,17 @@ obj="{{ job.kind }}"
pcm_badge: pcm_badge:
base_classes: "base classes here" base_classes: "base classes here"
``` ```
<br>
# Local development
The bundle ships with a small Symfony preview app under `dev/` that renders many different badge variants.
```bash
just composer_install # install dependencies
just serve # start the preview at http://localhost:8000
```
Edit `dev/templates/showcase.html.twig` to add or tweak variants — changes are picked up on refresh. Other handy recipes (see `justfile`):
+7 -1
View File
@@ -1,7 +1,13 @@
<div {{ attributes }} class="{{ this.finalClasses }}"> <div {{ attributes }} class="{{ this.finalClasses }}"{% if this.label %} aria-label="{{ this.label }}"{% endif %}>
<span class="inline-flex gap-1 items-center justify-center">
{% if this.icon %}
<twig:ux:icon name="{{ this.icon }}" class="size-[1em] shrink-0" aria-hidden="true" />
{% endif %}
{% if block('content') is not empty %} {% if block('content') is not empty %}
{% block content %}{% endblock %} {% block content %}{% endblock %}
{% else %} {% else %}
{{- this.label -}} {{- this.label -}}
{% endif %} {% endif %}
</span>
</div> </div>
+1
View File
@@ -5,3 +5,4 @@ services:
dockerfile: Containerfile dockerfile: Containerfile
volumes: volumes:
- ./:/code - ./:/code
network_mode: host
+5 -1
View File
@@ -20,7 +20,8 @@
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"Pcm\\BadgeBundle\\Tests\\": "tests/" "Pcm\\BadgeBundle\\Tests\\": "tests/",
"Pcm\\BadgeBundle\\Dev\\": "dev/src/"
} }
}, },
"require": { "require": {
@@ -32,6 +33,9 @@
}, },
"require-dev": { "require-dev": {
"symfony/test-pack": "^1.1", "symfony/test-pack": "^1.1",
"symfony/twig-bundle": "^7.1",
"symfony/ux-icons": "^2.18",
"symfony/http-client": "^7.1",
"friendsofphp/php-cs-fixer": "^3.61" "friendsofphp/php-cs-fixer": "^3.61"
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@ use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
return static function (DefinitionConfigurator $definition): void { return static function (DefinitionConfigurator $definition): void {
$definition->rootNode() $definition->rootNode()
->children() ->children()
->scalarNode('base_classes')->defaultValue('text-center rounded max-w-max text-xs px-2 py-1 border min-w-max')->end() ->scalarNode('base_classes')->defaultValue('shadow-sm text-center rounded max-w-max text-xs px-2 py-1 border min-w-max')->end()
->end() ->end()
; ;
}; };
+17
View File
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
use Pcm\BadgeBundle\Dev\Kernel;
use Symfony\Component\HttpFoundation\Request;
require_once dirname(__DIR__, 2).'/vendor/autoload.php';
$_SERVER['APP_ENV'] = $_SERVER['APP_ENV'] ?? 'dev';
$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? '1';
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
+76
View File
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Pcm\BadgeBundle\Dev\Controller;
use Pcm\BadgeBundle\Enum\BadgeColour;
use Pcm\BadgeBundle\Interface\BadgeableInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class ShowcaseController extends AbstractController
{
#[Route('/', name: 'showcase')]
public function __invoke(): Response
{
$colours = array_map(fn (BadgeColour $c) => strtolower($c->name), BadgeColour::cases());
$samples = [
new SampleBadgeable('Investigation', BadgeColour::BLUE),
new SampleBadgeable('Interrogation', BadgeColour::RED),
new SampleBadgeable('Shootout', BadgeColour::BLACK),
new SampleBadgeable('Sip Whiskey', BadgeColour::FOREST),
new SampleBadgeable('Unknown', BadgeColour::GREY),
];
$iconSamples = [
new SampleIconBadge('Investigation', BadgeColour::BLUE, 'mdi:magnify'),
new SampleIconBadge('Interrogation', BadgeColour::RED, 'bi:cassette-fill'),
new SampleIconBadge('Shootout', BadgeColour::BLACK, 'mdi:gun'),
new SampleIconBadge('Sip Whiskey', BadgeColour::FOREST, 'flowbite:whiskey-glass-outline'),
new SampleIconBadge('Unknown', BadgeColour::GREY, 'carbon:unknown'),
];
return $this->render('showcase.html.twig', [
'colours' => $colours,
'samples' => $samples,
'icon_samples' => $iconSamples
]);
}
}
final readonly class SampleBadgeable implements BadgeableInterface
{
public function __construct(public string $label, private BadgeColour $colour)
{
}
public function getBadgeColour(): BadgeColour
{
return $this->colour;
}
public function getBadgeIcon(): ?string
{
return null;
}
}
final readonly class SampleIconBadge implements BadgeableInterface
{
public function __construct(public string $label, private BadgeColour $colour, private string $icon)
{
}
public function getBadgeColour(): BadgeColour
{
return $this->colour;
}
public function getBadgeIcon(): ?string
{
return $this->icon;
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Pcm\BadgeBundle\Dev;
use Pcm\BadgeBundle\PcmBadgeBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
use Symfony\UX\Icons\UXIconsBundle;
use Symfony\UX\TwigComponent\TwigComponentBundle;
final class Kernel extends BaseKernel
{
use MicroKernelTrait;
public function registerBundles(): iterable
{
return [
new FrameworkBundle(),
new TwigBundle(),
new TwigComponentBundle(),
new UXIconsBundle(),
new PcmBadgeBundle(),
];
}
public function getProjectDir(): string
{
return \dirname(__DIR__);
}
public function getCacheDir(): string
{
return $this->getProjectDir().'/var/cache/'.$this->environment;
}
public function getLogDir(): string
{
return $this->getProjectDir().'/var/log';
}
private function configureContainer(ContainerConfigurator $container): void
{
$container->extension('framework', [
'secret' => 'dev',
'router' => ['utf8' => true],
'test' => false,
'http_method_override' => false,
'handle_all_throwables' => true,
'php_errors' => ['log' => true],
]);
$container->extension('twig', [
'default_path' => $this->getProjectDir().'/templates',
]);
// Default config for the bundle. Tweak to preview different base classes.
$container->extension('pcm_badge', []);
// Fetch icons on-demand from iconify.design so no JS toolchain is needed.
$container->extension('ux_icons', [
'icon_dir' => '%kernel.project_dir%/var/icons',
'iconify' => ['enabled' => true, 'on_demand' => true],
]);
$container->services()
->defaults()
->autowire()
->autoconfigure()
->load(__NAMESPACE__.'\\Controller\\', __DIR__.'/Controller/')
->public()
;
}
private function configureRoutes(RoutingConfigurator $routes): void
{
$routes->import(__DIR__.'/Controller/', 'attribute');
}
}
+82
View File
@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>PCM Badge Bundle — Dev Preview</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
accent: '#7c3aed',
primary: '#0f172a',
},
fontSize: {
'2xs': ['0.625rem', '0.875rem'],
},
},
},
};
</script>
</head>
<body class="bg-zinc-50 text-zinc-900 p-8 font-sans">
<h1 class="text-2xl font-bold mb-2">PCM Badge Bundle</h1>
<p class="text-zinc-600 mb-8">Local dev preview. Edit <code class="bg-zinc-200 px-1 rounded">dev/templates/showcase.html.twig</code> to add new variants.</p>
<section class="mb-10">
<h2 class="text-lg font-semibold mb-3">Solid</h2>
<div class="flex flex-wrap gap-2">
{% for colour in colours %}
<twig:Pcm:Badge colour="{{ colour }}">{{ colour }}</twig:Pcm:Badge>
{% endfor %}
</div>
</section>
<section class="mb-10">
<h2 class="text-lg font-semibold mb-3">Outline</h2>
<div class="flex flex-wrap gap-2">
{% for colour in colours %}
<twig:Pcm:Badge colour="{{ colour }}" outline>{{ colour }}</twig:Pcm:Badge>
{% endfor %}
</div>
</section>
<section class="mb-10">
<h2 class="text-lg font-semibold mb-3">Solid glossy</h2>
<div class="flex flex-wrap gap-2">
{% for colour in colours %}
<twig:Pcm:Badge glossy colour="{{ colour }}">{{ colour }}</twig:Pcm:Badge>
{% endfor %}
</div>
</section>
<section class="mb-10">
<h2 class="text-lg font-semibold mb-3">Solid icon</h2>
<div class="flex flex-wrap gap-2">
{% for colour in colours %}
<twig:Pcm:Badge icon="material-symbols:add" colour="{{ colour }}">{{ colour }}</twig:Pcm:Badge>
{% endfor %}
</div>
</section>
<section class="mb-10">
<h2 class="text-lg font-semibold mb-3">Outline icon</h2>
<div class="flex flex-wrap gap-2">
{% for colour in colours %}
<twig:Pcm:Badge icon="material-symbols:add" colour="{{ colour }}" outline>{{ colour }}</twig:Pcm:Badge>
{% endfor %}
</div>
</section>
<section class="mb-10">
<h2 class="text-lg font-semibold mb-3">Solid icon glossy</h2>
<div class="flex flex-wrap gap-2">
{% for colour in colours %}
<twig:Pcm:Badge glossy icon="material-symbols:edit-outline" colour="{{ colour }}">{{ colour }}</twig:Pcm:Badge>
{% endfor %}
</div>
</section>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

+4
View File
@@ -13,3 +13,7 @@ tests:
@{{php}} rm -rf var/cache @{{php}} rm -rf var/cache
@{{php}} vendor/bin/phpunit @{{php}} vendor/bin/phpunit
# Run the local Symfony preview app at http://localhost:8000
serve:
@docker compose run --rm php sh -c "rm -rf dev/var/cache && php -S 0.0.0.0:8000 -t dev/public"
+41 -28
View File
@@ -13,6 +13,7 @@ enum BadgeColour
case BLACK; case BLACK;
case BLUE; case BLUE;
case BROWN; case BROWN;
case CYAN;
case DEFAULT; case DEFAULT;
case EMERALD; case EMERALD;
case FOREST; case FOREST;
@@ -20,47 +21,59 @@ enum BadgeColour
case GOLD; case GOLD;
case GREEN; case GREEN;
case GREY; case GREY;
case INDIGO;
case LIME; case LIME;
case RED;
case ROSE;
case SKY;
case MAROON; case MAROON;
case NAVY; case NAVY;
case OCHRE; case OCHRE;
case ORANGE; case ORANGE;
case PINK; case PINK;
case PURPLE;
case RED;
case ROSE;
case SKY;
case SLATE;
case STONE;
case STRIPE; case STRIPE;
case TEAL;
case VIOLET;
case YELLOW; case YELLOW;
case ZINC; case ZINC;
public function getPalette(): BadgePalette public function getPalette(): BadgePalette
{ {
return match ($this) { return match ($this) {
$this::ACCENT => new BadgePalette('text-accent', 'border-accent', 'bg-accent'), $this::ACCENT => new BadgePalette('text-accent', 'border-accent/50', 'bg-accent'),
$this::AMBER => new BadgePalette('text-amber-500', 'border-amber-500', 'bg-amber-500'), $this::AMBER => new BadgePalette('text-amber-600', 'border-amber-600/50', 'bg-amber-600'),
$this::BLACK => new BadgePalette('text-zinc-900', 'border-zinc-900', 'bg-zinc-900'), $this::BLACK => new BadgePalette('text-zinc-900', 'border-zinc-900/50', 'bg-zinc-900'),
$this::BLUE => new BadgePalette('text-sky-700', 'border-sky-700', 'bg-sky-700'), $this::BLUE => new BadgePalette('text-blue-700', 'border-blue-700/50', 'bg-blue-700'),
$this::BROWN => new BadgePalette('text-orange-900', 'border-orange-900', 'bg-orange-900'), $this::BROWN => new BadgePalette('text-stone-700', 'border-stone-700/50', 'bg-stone-700'),
$this::DEFAULT => new BadgePalette('text-primary', 'border-primary', 'bg-primary'), $this::CYAN => new BadgePalette('text-cyan-600', 'border-cyan-600/50', 'bg-cyan-600'),
$this::EMERALD => new BadgePalette('text-emerald-600', 'border-emerald-600', 'bg-emerald-600'), $this::DEFAULT => new BadgePalette('text-primary', 'border-primary/50', 'bg-primary'),
$this::FOREST => new BadgePalette('text-green-800', 'border-green-800', 'bg-green-800'), $this::EMERALD => new BadgePalette('text-emerald-600', 'border-emerald-600/50', 'bg-emerald-600'),
$this::FUCHSIA => new BadgePalette('text-fuchsia-500', 'border-fuchsia-500', 'bg-fuchsia-500'), $this::FOREST => new BadgePalette('text-green-800', 'border-green-800/50', 'bg-green-800'),
$this::GOLD => new BadgePalette('text-yellow-600', 'border-yellow-600', 'bg-yellow-600'), $this::FUCHSIA => new BadgePalette('text-fuchsia-600', 'border-fuchsia-600/50', 'bg-fuchsia-600'),
$this::GREEN => new BadgePalette('text-green-600', 'border-green-600', 'bg-green-600'), $this::GOLD => new BadgePalette('text-amber-500', 'border-amber-500/50', 'bg-amber-500'),
$this::GREY => new BadgePalette('text-neutral-400', 'border-neutral-400', 'bg-neutral-400'), $this::GREEN => new BadgePalette('text-green-600', 'border-green-600/50', 'bg-green-600'),
$this::LIME => new BadgePalette('text-lime-600', 'border-lime-600', 'bg-lime-600'), $this::GREY => new BadgePalette('text-neutral-500', 'border-neutral-500/50', 'bg-neutral-500'),
$this::RED => new BadgePalette('text-red-600', 'border-red-600', 'bg-red-600'), $this::INDIGO => new BadgePalette('text-indigo-700', 'border-indigo-700/50', 'bg-indigo-700'),
$this::ROSE => new BadgePalette('text-rose-600', 'border-rose-600', 'bg-rose-600'), $this::LIME => new BadgePalette('text-lime-600', 'border-lime-600/50', 'bg-lime-600'),
$this::SKY => new BadgePalette('text-sky-500', 'border-sky-500', 'bg-sky-500'), $this::MAROON => new BadgePalette('text-red-900', 'border-red-900/50', 'bg-red-900'),
$this::MAROON => new BadgePalette('text-red-900', 'border-red-900', 'bg-red-900'), $this::NAVY => new BadgePalette('text-blue-900', 'border-blue-900/50', 'bg-blue-900'),
$this::NAVY => new BadgePalette('text-blue-900', 'border-blue-900', 'bg-blue-900'), $this::OCHRE => new BadgePalette('text-yellow-800', 'border-yellow-800/50', 'bg-yellow-800'),
$this::OCHRE => new BadgePalette('text-yellow-800', 'border-yellow-800', 'bg-yellow-800'), $this::ORANGE => new BadgePalette('text-orange-500', 'border-orange-500/50', 'bg-orange-500'),
$this::ORANGE => new BadgePalette('text-orange-500', 'border-orange-500', 'bg-orange-500'), $this::PINK => new BadgePalette('text-pink-500', 'border-pink-500/50', 'bg-pink-500'),
$this::PINK => new BadgePalette('text-pink-400', 'border-pink-400', 'bg-pink-400'), $this::PURPLE => new BadgePalette('text-purple-600', 'border-purple-600/50', 'bg-purple-600'),
$this::STRIPE => new BadgePalette('text-indigo-500', 'border-indigo-500', 'bg-indigo-500'), $this::RED => new BadgePalette('text-red-600', 'border-red-600/50', 'bg-red-600'),
$this::YELLOW => new BadgePalette('text-yellow-500', 'border-yellow-500', 'bg-yellow-500'), $this::ROSE => new BadgePalette('text-rose-600', 'border-rose-600/50', 'bg-rose-600'),
$this::ZINC => new BadgePalette('text-zinc-500', 'border-zinc-500', 'bg-zinc-500'), $this::SKY => new BadgePalette('text-sky-600', 'border-sky-600/50', 'bg-sky-600'),
$this::SLATE => new BadgePalette('text-slate-600', 'border-slate-600/50', 'bg-slate-600'),
$this::STONE => new BadgePalette('text-stone-500', 'border-stone-500/50', 'bg-stone-500'),
$this::STRIPE => new BadgePalette('text-indigo-500', 'border-indigo-500/50', 'bg-indigo-500'),
$this::TEAL => new BadgePalette('text-teal-600', 'border-teal-600/50', 'bg-teal-600'),
$this::VIOLET => new BadgePalette('text-violet-600', 'border-violet-600/50', 'bg-violet-600'),
$this::YELLOW => new BadgePalette('text-yellow-600', 'border-yellow-600/50', 'bg-yellow-600'),
$this::ZINC => new BadgePalette('text-zinc-500', 'border-zinc-500/50', 'bg-zinc-500'),
}; };
} }
} }
+7
View File
@@ -17,5 +17,12 @@ interface BadgeableInterface
* @return BadgeColour Colour of badge to render * @return BadgeColour Colour of badge to render
*/ */
public function getBadgeColour(): BadgeColour; public function getBadgeColour(): BadgeColour;
/**
* Get the badge icon name
*
* @return ?string Icon name or `null` if no icon exists.
*/
public function getBadgeIcon(): ?string;
} }
+24
View File
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Pcm\BadgeBundle\Trait;
use Pcm\BadgeBundle\Enum\BadgeColour;
/**
* Provides a default implementation of {@see Pcm\BadgeBundle\Interface\BadgeableInterface}
*/
trait BadgeableTrait
{
public function getBadgeColour(): BadgeColour
{
return BadgeColour::DEFAULT;
}
public function getBadgeIcon(): ?string
{
return null;
}
}
+33 -5
View File
@@ -14,21 +14,43 @@ final class Badge
{ {
public string $finalClasses; public string $finalClasses;
public ?string $label = null; public ?string $label = null;
public ?string $icon = null;
public function __construct(private string $baseClasses) public function __construct(private string $baseClasses)
{ {
} }
/** /**
* @param ?BadgeableInterface $obj the object to be converted into a badge * @param ?BadgeableInterface $obj The object to be converted into a badge
* @param ?string $class Extra classes to add to the badge element. * @param ?string $class Extra classes to add to the badge element.
* These will override the base classes in case * These will override the base classes in case
* of conflicts. * of conflicts.
* @param ?string $colour specify the colour of an objectless badge * @param ?string $colour Manually specify the colour of a badge
* @param bool $outline if the badge should be rendered as an outline * @param bool $outline Whether the badge should be rendered with an outline
* @param ?string $icon
*/ */
public function mount(?BadgeableInterface $obj = null, ?string $class = null, ?string $colour = null, ?string $label = null, bool $outline = false): void public function mount(?BadgeableInterface $obj = null, ?string $class = null, ?string $colour = null, ?string $label = null, bool $outline = false, ?string $icon = null, bool $glossy = false): void
{ {
if ($outline && $glossy) {
throw new \RuntimeException('The "outline" and "glossy" props cannot be used together.');
}
if (!$icon) {
$this->icon = null;
} else if ("1" === $icon) {
if (null === $obj) {
throw new \RuntimeException("Missing icon name.");
}
$this->icon = $obj->getBadgeIcon();
if (null === $this->icon) {
throw new \RuntimeException("Missing icon name.");
}
} else {
$this->icon = $icon;
}
$this->label = $label; $this->label = $label;
if (!$obj && !$colour) { if (!$obj && !$colour) {
@@ -60,7 +82,13 @@ final class Badge
if (true === $outline) { if (true === $outline) {
$classes = sprintf('bg-white %s %s %s %s', $palette->borderColourClass, $palette->textColourClass, $this->baseClasses, $class); $classes = sprintf('bg-white %s %s %s %s', $palette->borderColourClass, $palette->textColourClass, $this->baseClasses, $class);
} else { } else {
$classes = sprintf('text-white %s %s %s %s', $palette->borderColourClass, $palette->backgroundColourClass, $this->baseClasses, $class); $glossyClass = $glossy ? 'bg-[image:radial-gradient(ellipse_at_top_left,rgba(255,255,255,0.25),transparent_65%),linear-gradient(to_bottom,rgba(255,255,255,0.05),rgba(0,0,0,0.12))]' : '';
$classes = sprintf('text-white %s %s %s %s', $palette->backgroundColourClass, $glossyClass, $this->baseClasses, $class);
}
if ($this->icon !== null) {
$classes = sprintf("flex gap-1 items-center %s", $classes);
} }
$this->finalClasses = $merger->merge(trim($classes)); $this->finalClasses = $merger->merge(trim($classes));