50 Commits

Author SHA1 Message Date
brabli f0730c268e Update to version 2.2.0 2026-05-06 12:34:52 +01:00
brabli f06dbf90bc Add semantic badge colours 2026-05-06 12:34:34 +01:00
brabli 44f878715b Fix grammar 2026-05-06 12:34:15 +01:00
brabli edb8a05bee Fix tag 2026-05-06 12:34:08 +01:00
brabli 81213d8f36 Updatechangelog 2026-05-01 15:42:03 +01:00
brabli 3b6521bfce Set default badge style to fully rounded 2026-05-01 15:39:56 +01:00
brabli 2c93518654 Fix issues with glossy class 2026-05-01 15:38:29 +01:00
brabli cea8ed8d95 Update command to work on mac 2026-05-01 15:38:15 +01:00
brabli 52adc81dbd Update radial gradient 2026-05-01 15:14:54 +01:00
brabli f54c745de0 Fix border on glossy badges 2026-05-01 15:12:15 +01:00
brabli 2687a584b9 Increase border transparency 2026-05-01 15:12:07 +01:00
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
brabli 0d9fbc3aed Update readme 2025-11-13 16:37:38 +00:00
brabli 14fd9ca2ec Tweak error message 2025-11-13 16:36:58 +00:00
brabli 93c4dd1e64 Add accent badge colour 2025-11-13 16:34:58 +00:00
brabli 61eaa62406 Update changelog 2025-11-13 16:30:22 +00:00
brabli bcd97f20a3 Add zinc badge colour 2025-11-13 16:30:11 +00:00
brabli 9c1c0c1ba1 Add new colours 2025-11-13 16:26:35 +00:00
17 changed files with 487 additions and 43 deletions
+1
View File
@@ -5,3 +5,4 @@ composer.lock
.phpunit.result.cache
/var
.php-cs-fixer.cache
/dev/var
+24
View File
@@ -1,5 +1,29 @@
# Changelog
## [x.x.x] - xxxx-xx-xx
## [2.2.0] - 2026-05-05
- Add semantic badge colours (`SUCCESS`, `WARNING`, `DANGER`)
- Fix typos
## [2.1.0] - 2026-05-01
- Fix glossy badge border colour
- Change default badge style to be fully rounded
- Reduce intensity of outline badge borders
## [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
- Add new badge colours
- Tweak error message
## [1.0.0] - 2025-02-26
- Add label attribute to set badge text
- Rename `Badge` enum to `BadgeColour`
+53 -5
View File
@@ -2,7 +2,7 @@
Create badges from objects or as standalone elements.
IMAGE HERE
![PCM Badge Bundle showcase](docs/showcase.png)
```twig
<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`.
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
// Job.php
@@ -37,6 +42,11 @@ public function getBadgeColour(): BadgeColour
default => BadgeColour::GREY
};
}
public function getBadgeIcon(): ?string
{
return null;
}
```
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>
```
## 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>
# Standalone badges
@@ -70,10 +100,14 @@ A badge must contain either an `obj` or a `colour`, but not both.
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.
`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.
`label` - Badge label text. Content inside the content block will be prioritised over the label attribute if present.
@@ -82,7 +116,7 @@ obj="{{ job.kind }}"
<twig:Pcm:Badge colour="red" label="Warning!" />
<twig:Pcm:Badge colour="red">Warning!<twig:Pcm:Badge>
<twig:Pcm:Badge colour="red">Warning!</twig:Pcm:Badge>
```
<br>
@@ -93,3 +127,17 @@ obj="{{ job.kind }}"
pcm_badge:
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 %}
{% block content %}{% endblock %}
{% else %}
{{- this.label -}}
{% endif %}
</span>
</div>
+2
View File
@@ -5,3 +5,5 @@ services:
dockerfile: Containerfile
volumes:
- ./:/code
ports:
- "8000:8000"
+5 -1
View File
@@ -20,7 +20,8 @@
},
"autoload-dev": {
"psr-4": {
"Pcm\\BadgeBundle\\Tests\\": "tests/"
"Pcm\\BadgeBundle\\Tests\\": "tests/",
"Pcm\\BadgeBundle\\Dev\\": "dev/src/"
}
},
"require": {
@@ -32,6 +33,9 @@
},
"require-dev": {
"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"
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
return static function (DefinitionConfigurator $definition): void {
$definition->rootNode()
->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-full max-w-max text-xs px-2 py-1 border min-w-max')->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}} vendor/bin/phpunit
# Run the local Symfony preview app at http://localhost:8000
serve:
@docker compose run --rm --service-ports php sh -c "rm -rf dev/var/cache && php -S 0.0.0.0:8000 -t dev/public"
+55 -18
View File
@@ -8,41 +8,78 @@ use Pcm\BadgeBundle\Model\BadgePalette;
enum BadgeColour
{
case ACCENT;
case AMBER;
case BLACK;
case BLUE;
case BROWN;
case CYAN;
case DANGER;
case DEFAULT;
case EMERALD;
case FOREST;
case FUCHSIA;
case GOLD;
case GREEN;
case GREY;
case RED;
case ROSE;
case INDIGO;
case LIME;
case MAROON;
case NAVY;
case OCHRE;
case ORANGE;
case PINK;
case PURPLE;
case RED;
case ROSE;
case SKY;
case SLATE;
case STONE;
case STRIPE;
case SUCCESS;
case TEAL;
case VIOLET;
case WARNING;
case YELLOW;
case ZINC;
public function getPalette(): BadgePalette
{
return match ($this) {
$this::AMBER => new BadgePalette('text-amber-500', 'border-amber-500', 'bg-amber-500'),
$this::BLACK => new BadgePalette('text-zinc-900', 'border-zinc-900', 'bg-zinc-900'),
$this::BLUE => new BadgePalette('text-sky-700', 'border-sky-700', 'bg-sky-700'),
$this::DEFAULT => new BadgePalette('text-primary', 'border-primary', 'bg-primary'),
$this::FOREST => new BadgePalette('text-green-800', 'border-green-800', 'bg-green-800'),
$this::GREEN => new BadgePalette('text-green-600', 'border-green-600', 'bg-green-600'),
$this::GREY => new BadgePalette('text-neutral-400', 'border-neutral-400', 'bg-neutral-400'),
$this::RED => new BadgePalette('text-red-600', 'border-red-600', 'bg-red-600'),
$this::ROSE => new BadgePalette('text-rose-600', 'border-rose-600', 'bg-rose-600'),
$this::MAROON => new BadgePalette('text-red-900', 'border-red-900', 'bg-red-900'),
$this::NAVY => new BadgePalette('text-blue-900', 'border-blue-900', 'bg-blue-900'),
$this::OCHRE => new BadgePalette('text-yellow-800', 'border-yellow-800', 'bg-yellow-800'),
$this::ORANGE => new BadgePalette('text-orange-500', 'border-orange-500', 'bg-orange-500'),
$this::STRIPE => new BadgePalette('text-indigo-500', 'border-indigo-500', 'bg-indigo-500'),
$this::YELLOW => new BadgePalette('text-yellow-500', 'border-yellow-500', 'bg-yellow-500'),
$this::ACCENT => new BadgePalette('text-accent', 'border-accent/30', 'bg-accent'),
$this::AMBER => new BadgePalette('text-amber-600', 'border-amber-600/30', 'bg-amber-600'),
$this::BLACK => new BadgePalette('text-zinc-900', 'border-zinc-900/30', 'bg-zinc-900'),
$this::BLUE => new BadgePalette('text-blue-700', 'border-blue-700/30', 'bg-blue-700'),
$this::BROWN => new BadgePalette('text-stone-700', 'border-stone-700/30', 'bg-stone-700'),
$this::CYAN => new BadgePalette('text-cyan-600', 'border-cyan-600/30', 'bg-cyan-600'),
$this::DANGER => new BadgePalette('text-danger', 'border-danger/30', 'bg-danger'),
$this::DEFAULT => new BadgePalette('text-primary', 'border-primary/30', 'bg-primary'),
$this::EMERALD => new BadgePalette('text-emerald-600', 'border-emerald-600/30', 'bg-emerald-600'),
$this::FOREST => new BadgePalette('text-green-800', 'border-green-800/30', 'bg-green-800'),
$this::FUCHSIA => new BadgePalette('text-fuchsia-600', 'border-fuchsia-600/30', 'bg-fuchsia-600'),
$this::GOLD => new BadgePalette('text-amber-500', 'border-amber-500/30', 'bg-amber-500'),
$this::GREEN => new BadgePalette('text-green-600', 'border-green-600/30', 'bg-green-600'),
$this::GREY => new BadgePalette('text-neutral-500', 'border-neutral-500/30', 'bg-neutral-500'),
$this::INDIGO => new BadgePalette('text-indigo-700', 'border-indigo-700/30', 'bg-indigo-700'),
$this::LIME => new BadgePalette('text-lime-600', 'border-lime-600/30', 'bg-lime-600'),
$this::MAROON => new BadgePalette('text-red-900', 'border-red-900/30', 'bg-red-900'),
$this::NAVY => new BadgePalette('text-blue-900', 'border-blue-900/30', 'bg-blue-900'),
$this::OCHRE => new BadgePalette('text-yellow-800', 'border-yellow-800/30', 'bg-yellow-800'),
$this::ORANGE => new BadgePalette('text-orange-500', 'border-orange-500/30', 'bg-orange-500'),
$this::PINK => new BadgePalette('text-pink-500', 'border-pink-500/30', 'bg-pink-500'),
$this::PURPLE => new BadgePalette('text-purple-600', 'border-purple-600/30', 'bg-purple-600'),
$this::RED => new BadgePalette('text-red-600', 'border-red-600/30', 'bg-red-600'),
$this::ROSE => new BadgePalette('text-rose-600', 'border-rose-600/30', 'bg-rose-600'),
$this::SKY => new BadgePalette('text-sky-600', 'border-sky-600/30', 'bg-sky-600'),
$this::SLATE => new BadgePalette('text-slate-600', 'border-slate-600/30', 'bg-slate-600'),
$this::STONE => new BadgePalette('text-stone-500', 'border-stone-500/30', 'bg-stone-500'),
$this::STRIPE => new BadgePalette('text-indigo-500', 'border-indigo-500/30', 'bg-indigo-500'),
$this::SUCCESS => new BadgePalette('text-success', 'border-success/30', 'bg-success'),
$this::TEAL => new BadgePalette('text-teal-600', 'border-teal-600/30', 'bg-teal-600'),
$this::VIOLET => new BadgePalette('text-violet-600', 'border-violet-600/30', 'bg-violet-600'),
$this::WARNING => new BadgePalette('text-warning', 'border-warning/30', 'bg-warning'),
$this::YELLOW => new BadgePalette('text-yellow-600', 'border-yellow-600/30', 'bg-yellow-600'),
$this::ZINC => new BadgePalette('text-zinc-500', 'border-zinc-500/30', 'bg-zinc-500'),
};
}
}
+7
View File
@@ -17,5 +17,12 @@ interface BadgeableInterface
* @return BadgeColour Colour of badge to render
*/
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;
}
}
+35 -7
View File
@@ -14,25 +14,47 @@ final class Badge
{
public string $finalClasses;
public ?string $label = null;
public ?string $icon = null;
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.
* These will override the base classes in case
* of conflicts.
* @param ?string $colour specify the colour of an objectless badge
* @param bool $outline if the badge should be rendered as an outline
* @param ?string $colour Manually specify the colour of a badge
* @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;
if (!$obj && !$colour) {
throw new \RuntimeException(sprintf('You must specify either a colour an instance of "%s".', BadgeableInterface::class));
throw new \RuntimeException(sprintf('You must specify either a colour or an instance of "%s".', BadgeableInterface::class));
}
if ($obj && $colour) {
@@ -48,7 +70,7 @@ final class Badge
if (!in_array($colour, $cases)) {
$formattedCases = implode(', ', array_map(fn (string $s) => '"'.$s.'"', $cases));
throw new \RuntimeException(sprintf('"%s" is not a valid Badge colour. Available options are: %s', $colour, $formattedCases));
throw new \RuntimeException(sprintf('"%s" is not a valid badge colour. Available options are: %s.', $colour, $formattedCases));
}
$colour = strtoupper($colour);
@@ -60,7 +82,13 @@ final class Badge
if (true === $outline) {
$classes = sprintf('bg-white %s %s %s %s', $palette->borderColourClass, $palette->textColourClass, $this->baseClasses, $class);
} else {
$classes = sprintf('text-white %s %s %s %s', $palette->borderColourClass, $palette->backgroundColourClass, $this->baseClasses, $class);
$glossyClass = $glossy ? 'bg-origin-border bg-[image:radial-gradient(ellipse_at_-10%_-50%,rgba(255,255,255,0.35),transparent_70%),linear-gradient(to_bottom,rgba(255,255,255,0.05),rgba(0,0,0,0.12))]' : '';
$classes = sprintf('text-white %s %s %s [border-color:transparent] %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));