Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81213d8f36 | |||
| 3b6521bfce | |||
| 2c93518654 | |||
| cea8ed8d95 | |||
| 52adc81dbd | |||
| f54c745de0 | |||
| 2687a584b9 | |||
| 423b696525 | |||
| b994b83fdb | |||
| 12169001d2 | |||
| cc7a2d5cd4 | |||
| 1bd216f4a5 | |||
| 3fb9714010 | |||
| 1e8ac58578 | |||
| d7ca3d9285 | |||
| 197f9a109e | |||
| 67465e9790 | |||
| d0a84f135d | |||
| bf035399ee | |||
| 40f9aaceef | |||
| a7aabcebef | |||
| fd6bf6ad4f | |||
| 78afab2450 | |||
| df76af9cdf | |||
| 25d5441184 | |||
| 5046edb4c9 | |||
| 6a9cd627e2 | |||
| 23e02068c9 | |||
| 949d268b84 | |||
| 5f1404d77c | |||
| 93258abd38 | |||
| b4cf626468 | |||
| fa04d7e463 | |||
| 9e85f5f9d6 | |||
| 9cb7bf23a6 | |||
| 1b9751324b | |||
| c84f2b0973 | |||
| 4ecf0f2feb | |||
| 80d24dc948 | |||
| bf2f473038 |
@@ -5,3 +5,4 @@ composer.lock
|
|||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
/var
|
/var
|
||||||
.php-cs-fixer.cache
|
.php-cs-fixer.cache
|
||||||
|
/dev/var
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [x.x.x] - xxxx-xx-xx
|
||||||
|
|
||||||
|
## [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
|
## [1.1.0] - 2025-11-13
|
||||||
- Add new badge colours
|
- Add new badge colours
|
||||||
- Tweak error message
|
- Tweak error message
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Create badges from objects or as standalone elements.
|
Create badges from objects or as standalone elements.
|
||||||
|
|
||||||
IMAGE HERE
|

|
||||||
|
|
||||||
```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`):
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -5,3 +5,5 @@ services:
|
|||||||
dockerfile: Containerfile
|
dockerfile: Containerfile
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/code
|
- ./:/code
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
|||||||
+5
-1
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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-full max-w-max text-xs px-2 py-1 border min-w-max')->end()
|
||||||
->end()
|
->end()
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 400 KiB |
@@ -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 --service-ports php sh -c "rm -rf dev/var/cache && php -S 0.0.0.0:8000 -t dev/public"
|
||||||
|
|
||||||
|
|||||||
+41
-28
@@ -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/30', '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/30', '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/30', '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/30', '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/30', 'bg-stone-700'),
|
||||||
$this::DEFAULT => new BadgePalette('text-primary', 'border-primary', 'bg-primary'),
|
$this::CYAN => new BadgePalette('text-cyan-600', 'border-cyan-600/30', 'bg-cyan-600'),
|
||||||
$this::EMERALD => new BadgePalette('text-emerald-600', 'border-emerald-600', 'bg-emerald-600'),
|
$this::DEFAULT => new BadgePalette('text-primary', 'border-primary/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/30', '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/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::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::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'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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-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));
|
$this->finalClasses = $merger->merge(trim($classes));
|
||||||
|
|||||||
Reference in New Issue
Block a user