[Toolkit] Updated documentation in order to preview components

This commit is contained in:
Jean-François Lépine
2025-03-07 07:17:58 +01:00
committed by Hugo Alliaume
parent 400dcb4511
commit 288570f61b
66 changed files with 1992 additions and 222 deletions

View File

@@ -26,7 +26,7 @@
"symfony/mercure-bundle": "^0.3.9",
"symfony/monolog-bundle": "^3.10",
"symfony/notifier": "7.2.*",
"symfony/runtime": "7.2.*",
"symfony/runtime": "^7.2",
"symfony/serializer": "7.2.*",
"symfony/stimulus-bundle": "2.x-dev",
"symfony/translation": "7.2.*",
@@ -46,6 +46,7 @@
"symfony/ux-svelte": "2.x-dev",
"symfony/ux-swup": "2.x-dev",
"symfony/ux-toggle-password": "2.x-dev",
"symfony/ux-toolkit": "2.x-dev",
"symfony/ux-translator": "2.x-dev",
"symfony/ux-turbo": "2.x-dev",
"symfony/ux-twig-component": "2.x-dev",
@@ -55,6 +56,7 @@
"symfony/yaml": "7.2.*",
"symfonycasts/dynamic-forms": "^0.1.2",
"symfonycasts/sass-bundle": "0.8.*",
"tales-from-a-dev/twig-tailwind-extra": "^0.3.0",
"tempest/highlight": "^2.11.2",
"twbs/bootstrap": "^5.3.3",
"twig/extra-bundle": "^3.17",

576
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,4 +32,6 @@ return [
Symfonycasts\SassBundle\SymfonycastsSassBundle::class => ['all' => true],
Symfony\UX\Icons\UXIconsBundle::class => ['all' => true],
Symfony\UX\Map\UXMapBundle::class => ['all' => true],
Symfony\UX\Toolkit\UxToolkitBundle::class => ['all' => true],
TalesFromADev\Twig\Extra\Tailwind\Bridge\Symfony\Bundle\TalesFromADevTwigExtraTailwindBundle::class => ['all' => true],
];

View File

@@ -0,0 +1,4 @@
when@dev:
ux_toolkit:
theme: default
prefix: null

View File

@@ -42,15 +42,15 @@ So here you can see we have an `Alert` component that itself uses an Icon compon
Or you can compose with the following syntax:
```twig
<twig:Card>
<twig:Alert>
<twig:ux:icon name="info"/>
<twig:Button>
<twig:ux:icon name="close" />
</twig:Button>
</twig:Card>
</twig:Alert>
```
So here we have a `Card` component, and we provide the content of this component with two other components.
So here we have a `Alert` component, and we provide the content of this component with two other components.
### Independence

View File

@@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Controller\UxPackage;
use App\Service\ToolkitComponentService;
use App\Service\UxPackageRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\UX\Toolkit\Registry\Registry;
use Symfony\UX\Toolkit\Registry\RegistryItemType;
class ToolkitController extends AbstractController
{
#[Route('/toolkit', name: 'app_toolkit')]
public function index(UxPackageRepository $packageRepository, Registry $registry): Response
{
$package = $packageRepository->find('toolkit');
return $this->render('ux_packages/toolkit.html.twig', [
'components' => $registry->all(),
'package' => $package,
]);
}
#[Route('/components/{currentComponent}', name: 'app_toolkit_components', defaults: ['currentComponent' => ''])]
public function components(
UxPackageRepository $packageRepository,
ToolkitComponentService $toolkitComponentService,
string $currentComponent
): Response
{
$package = $packageRepository->find('toolkit');
$registry = $toolkitComponentService->getRegistry();
$component = $registry->get($currentComponent);
if (null == $component) {
// get the first non-example component
$components = array_filter($registry->all(), function ($component) {
if($component->type !== RegistryItemType::Component) {
return null;
}
return $component;
});
$component = reset($components);
}
if (null === $component) {
throw $this->createNotFoundException('No component found');
}
// get all examples for this component
$examples = array_filter($registry->all(), function ($component) use ($currentComponent) {
if($component->type !== RegistryItemType::Example) {
return null;
}
if ($component->parentName !== $currentComponent) {
return null;
}
return $component;
});
return $this->render('ux_packages/toolkit/components.html.twig', [
'components' => $registry->all(),
'currentComponent' => $component,
'examples' => $examples,
'package' => $package,
]);
}
#[Route('/components/{currentComponent}/preview', name: 'app_toolkit_component_preview')]
public function componentPreview(
ToolkitComponentService $toolkitComponentService,
string $currentComponent,
): Response
{
$currentComponentFromRegistry = $toolkitComponentService->getRegistry()->get($currentComponent, RegistryItemType::Example);
if (null === $currentComponentFromRegistry) {
throw $this->createNotFoundException('Example not found');
}
$html = $toolkitComponentService->preview($currentComponentFromRegistry->name, RegistryItemType::Example);
return $this->render('ux_packages/toolkit/preview.html.twig', [
'component' => $currentComponentFromRegistry,
'html' => $html,
// in the future, we'll change the framework dynamically
'cssFramework' => 'tailwindcss',
]);
}
}

View File

@@ -29,6 +29,7 @@ class UxPackage
private ?string $createString = null,
private ?string $imageFileName = null,
private ?string $composerName = null,
private bool $onlyInDevMode = false,
) {
}
@@ -79,6 +80,10 @@ class UxPackage
public function getComposerRequireCommand(): string
{
if ($this->onlyInDevMode) {
return 'composer require --dev '.$this->getComposerName();
}
return 'composer require '.$this->getComposerName();
}

View File

@@ -0,0 +1,140 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Service;
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\UX\Toolkit\Registry\Registry;
use Symfony\UX\Toolkit\Registry\RegistryFactory;
use Symfony\UX\Toolkit\Registry\RegistryItem;
use Symfony\UX\Toolkit\Registry\RegistryItemType;
use Symfony\UX\TwigComponent\ComponentFactory;
use Symfony\UX\TwigComponent\ComponentProperties;
use Symfony\UX\TwigComponent\ComponentRenderer;
use Symfony\UX\TwigComponent\ComponentStack;
use Symfony\UX\TwigComponent\ComponentTemplateFinder;
use Symfony\UX\TwigComponent\DependencyInjection\TwigComponentExtension;
use Twig\Environment;
final class ToolkitComponentService
{
public function __construct(
private readonly RegistryFactory $registryFactory,
private readonly Environment $twig,
private readonly string $manifest = __DIR__ . '/../../vendor/symfony/ux-toolkit/registry/default',
) {}
public function getRegistry(): Registry
{
return $this->registryFactory->create((new Finder())->files()->in($this->manifest));
}
public function get(string $componentName): ?RegistryItem
{
$registry = $this->getRegistry();
foreach ($registry->all() as $component) {
if ($component->name === $componentName && $component->type === RegistryItemType::Component) {
return $component;
}
}
return null;
}
private function getWorkdir(): string {
// Put the code in a temporary file. We can image in the future use another way to pass the code to the component,
// but today the ComponentFactory need a folder
// We do it only once, for all visitors
$filesystem = new Filesystem();
$workdir = sys_get_temp_dir() . '/uxcomponent';
if (!$filesystem->exists($workdir)) {
$filesystem->mkdir($workdir);
// we put all components in this folder
foreach ($this->getRegistry()->all() as $component) {
$componentFile = $workdir . '/' . $component->name . '.twig';
file_put_contents($componentFile, $component->code);
}
}
return $workdir;
}
public function getComponentTwigPath(string $componentName): string
{
return $this->getWorkdir() . '/' . $componentName . '.twig';
}
/**
* This method allow to render a dynamic component, without using the auto-wired TwigComponentExtension
*
* Actually, the extension compile components. If we want to render a dynamic component, we need to do it manually
* This should be improved in the future.
*
* @param string $componentName
* @return string
*/
public function preview(string $componentName, RegistryItemType $type = RegistryItemType::Component): string
{
$registry = $this->getRegistry();
if (!$registry->has($componentName, $type)) {
throw new \InvalidArgumentException(sprintf('Component "%s" not found.', $componentName));
}
$currentComponentFromRegistry = $registry->get($componentName, $type);
// Add this path to twig
$loader = $this->twig->getLoader();
$loader->addPath($this->getWorkdir());
// Component finder use compiled values. We need to construct new one
$componentFactory = new ComponentFactory(
new ComponentTemplateFinder($this->twig),
new ServiceLocator(function() {
return [];
}, []),
new PropertyAccessor(),
new EventDispatcher(),
[],
[],
);
if ($type === RegistryItemType::Component) {
// Preview a component
$mounted = $componentFactory->create($currentComponentFromRegistry->name);
$componentProperties = new ComponentProperties(new PropertyAccessor());
$componentStack = new ComponentStack();
$renderer = new ComponentRenderer(
$this->twig,
new EventDispatcher(),
$componentFactory,
$componentProperties,
$componentStack,
);
return $renderer->render($mounted);
}
// Preview an example
return $this->twig->render($currentComponentFromRegistry->name . '.twig');
}
}

View File

@@ -231,6 +231,20 @@ class UxPackageRepository
'Switch the visibility of a password field',
),
new UxPackage(
'toolkit',
'Toolkit',
'app_toolkit',
'#BE0404',
'linear-gradient(142deg,rgb(60, 230, 236) -15%,rgb(77, 97, 214) 95%)',
'Build your Design System.',
'Collection of components and templates that you can use to build your pages.',
null,
null,
null,
true
),
(new UxPackage(
'typed',
'Typed',

View File

@@ -678,6 +678,15 @@
"symfonycasts/sass-bundle": {
"version": "v0.1.0"
},
"tales-from-a-dev/twig-tailwind-extra": {
"version": "0.3",
"recipe": {
"repo": "github.com/symfony/recipes-contrib",
"branch": "main",
"version": "0.2",
"ref": "7243ab070ed66198eb82c026684e9b9773e7b64a"
}
},
"theseer/tokenizer": {
"version": "1.2.1"
},

View File

@@ -1,4 +1,6 @@
<div class="alert alert-{{ type }} alert-dismissible" role="alert">
<twig:ux:icon name="{{ this.icon }}" class="Icon bi" />
{{ message }}
<div {{ attributes.without('class') }}
class="{{ (' ' ~ attributes.render('class'))|tailwind_merge }}"
>
<twig:Button>Dependency test</twig:Button>
{% block content %}Alert{% endblock %}
</div>

View File

@@ -0,0 +1,9 @@
{%- props ratio = (4/3) -%}
<div
style="width:100%; position: relative; padding-bottom: calc((1/({{ ratio }})) * 100%);"
{{ attributes.defaults({}).without('style') }}
>
<div style="position: absolute; left:0; right:0; top:0; bottom: 0">
{% block content %}{% endblock %}
</div>
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
variants: {},
compoundVariants: []
) -%}
<span
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</span>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'flex h-full w-full items-center justify-center rounded-full bg-muted',
variants: {},
compoundVariants: []
) -%}
<span
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</span>

View File

@@ -0,0 +1,11 @@
{%- props -%}
{%- set style = html_cva(
base: 'aspect-square h-full w-full',
variants: {},
compoundVariants: []
) -%}
<img
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
/>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'flex h-full w-full items-center justify-center rounded-full bg-muted',
variants: {},
compoundVariants: []
) -%}
<span
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</span>

View File

@@ -0,0 +1,11 @@
{%- props -%}
{%- set style = html_cva(
base: 'aspect-square h-full w-full',
variants: {},
compoundVariants: []
) -%}
<img
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
/>

View File

@@ -1,19 +1,34 @@
<div {{ attributes.defaults({
class: 'Badge',
}) }}>
<span class="Badge__label">
{%- if icon %}
<twig:ux:Icon name="{{ icon }}" class="Badge__icon" />
{% endif -%}
{{- label -}}
</span>
<span class="Badge__value">
{%- if url %}
<a href="{{ url }}" class="Badge__link">
{{- value -}}
</a>
{% else %}
{{ value }}
{% endif -%}
</span>
{%- props variant = 'default', outline = false -%}
{%- set style = html_cva(
base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
},
outline: {
true: "text-foreground bg-white",
}
},
compoundVariants: [{
variant: ['default'],
outline: ['true'],
class: 'border-primary',
}, {
variant: ['secondary'],
outline: ['true'],
class: 'border-secondary',
}, {
variant: ['destructive'],
outline: ['true'],
class: 'border-destructive',
},]
) -%}
<div
class="{{ style.apply({variant, outline}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: '',
variants: {},
compoundVariants: []
) -%}
<span
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</span>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: '',
variants: {},
compoundVariants: []
) -%}
<span
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</span>

View File

@@ -0,0 +1,20 @@
{%- props -%}
{%- set style = html_cva(
base: 'flex h-9 w-9 items-center justify-center',
variants: {},
compoundVariants: []
) -%}
<span
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
role="presentation"
aria-hidden="true"
>
{% set _block = block('content') %}
{% if content is defined and content is not empty %}
{% block content %}{% endblock %}
{% else %}
<span class="h-4 w-4">...</span>
{% endif %}
</span>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'inline-flex items-center gap-1.5',
variants: {},
compoundVariants: []
) -%}
<li
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</li>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'transition-colors hover:text-foreground',
variants: {},
compoundVariants: []
) -%}
<a
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</a>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
variants: {},
compoundVariants: []
) -%}
<ol
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</ol>

View File

@@ -0,0 +1,18 @@
{%- props
disabled = false
-%}
{%- set style = html_cva(
base: 'font-normal text-foreground',
variants: {},
compoundVariants: []
) -%}
<span
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
role="link"
aria-disabled="{{ disabled ? 'true' : 'false' }}"
aria-current="page"
>
{% block content %}{% endblock %}
</span>

View File

@@ -0,0 +1,20 @@
{%- props -%}
{%- set style = html_cva(
base: '[&>svg]:w-3.5 [&>svg]:h-3.5',
variants: {},
compoundVariants: []
) -%}
<li
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
role="presentation"
aria-hidden="true"
>
{% set _block = block('content') %}
{% if content is defined and content is not empty %}
{% block content %}{% endblock %}
{% else %}
<twig:ux:Icon name="mdi:chevron-right" />
{% endif %}
</li>

View File

@@ -0,0 +1,20 @@
{%- props -%}
{%- set style = html_cva(
base: 'flex h-9 w-9 items-center justify-center',
variants: {},
compoundVariants: []
) -%}
<span
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
role="presentation"
aria-hidden="true"
>
{% set _block = block('content') %}
{% if content is defined and content is not empty %}
{% block content %}{% endblock %}
{% else %}
<span class="h-4 w-4">...</span>
{% endif %}
</span>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'inline-flex items-center gap-1.5',
variants: {},
compoundVariants: []
) -%}
<li
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</li>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'transition-colors hover:text-foreground',
variants: {},
compoundVariants: []
) -%}
<a
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</a>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
variants: {},
compoundVariants: []
) -%}
<ol
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</ol>

View File

@@ -0,0 +1,18 @@
{%- props
disabled = false
-%}
{%- set style = html_cva(
base: 'font-normal text-foreground',
variants: {},
compoundVariants: []
) -%}
<span
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
role="link"
aria-disabled="{{ disabled ? 'true' : 'false' }}"
aria-current="page"
>
{% block content %}{% endblock %}
</span>

View File

@@ -0,0 +1,20 @@
{%- props -%}
{%- set style = html_cva(
base: '[&>svg]:w-3.5 [&>svg]:h-3.5',
variants: {},
compoundVariants: []
) -%}
<li
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
role="presentation"
aria-hidden="true"
>
{% set _block = block('content') %}
{% if content is defined and content is not empty %}
{% block content %}{% endblock %}
{% else %}
<twig:ux:Icon name="mdi:chevron-right" />
{% endif %}
</li>

View File

@@ -0,0 +1,42 @@
{%- props variant = 'default', outline = false, size = 'default' -%}
{%- set style = html_cva(
base: 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
outline: {
true: "text-foreground bg-white",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
compoundVariants: [{
variant: ['default'],
outline: ['true'],
class: 'border-primary',
}, {
variant: ['secondary'],
outline: ['true'],
class: 'border-secondary',
}, {
variant: ['destructive'],
outline: ['true'],
class: 'border-destructive',
},]
) -%}
<button
class="{{ style.apply({variant, outline, size}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</button>

View File

@@ -1,33 +1,13 @@
{% props name, image, url, description, tags, lazyload = true %}
{%- props -%}
{%- set style = html_cva(
base: 'rounded-xl border bg-card text-card-foreground shadow',
variants: {},
compoundVariants: []
) -%}
<article class="DemoCard">
<div class="DemoCard__media">
<img class="DemoCard__image"
src="{{ image }}"
width="640"
height="360"
alt="{{ name }}"
{% if lazyload %}
loading="lazy"
{% endif %}
>
</div>
<div class="DemoCard__content">
<h4 class="DemoCard__title">
<a href="{{ url }}" class="stretched-link">
{{- name -}}
</a>
</h4>
<p class="DemoCard__description">
{{- description -}}
</p>
<p class="DemoCard__tags">
{% for tag in tags %}
<span class="Tag">{{ tag }}</span>
{% endfor %}
</p>
</div>
</article>
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'p-6 pt-0',
variants: {},
compoundVariants: []
) -%}
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'text-sm text-muted-foreground',
variants: {},
compoundVariants: []
) -%}
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'flex items-center p-6 pt-0',
variants: {},
compoundVariants: []
) -%}
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'flex flex-col space-y-1.5 p-6',
variants: {},
compoundVariants: []
) -%}
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'font-semibold leading-none tracking-tight',
variants: {},
compoundVariants: []
) -%}
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'p-6 pt-0',
variants: {},
compoundVariants: []
) -%}
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'text-sm text-muted-foreground',
variants: {},
compoundVariants: []
) -%}
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'flex items-center p-6 pt-0',
variants: {},
compoundVariants: []
) -%}
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'flex flex-col space-y-1.5 p-6',
variants: {},
compoundVariants: []
) -%}
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'font-semibold leading-none tracking-tight',
variants: {},
compoundVariants: []
) -%}
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,34 @@
{% props
code = '',
language = 'twig',
highlight = true,
%}
<div
class="IconModal__code"
data-controller="clipboarder"
>
{% set _code -%}
{% block content -%}
{{ code ? code|trim|raw }}
{%- endblock -%}
{%- endset %}
{% if highlight %}
<pre
class="language-{{ language }}"
style="--height: auto"
data-clipboarder-target="source"
><code>{{ _code ? _code|trim|raw|highlight(language) : code|highlight(language) }}</code>
</pre>
{% else %}
<pre
style="--height: auto"
data-clipboarder-target="source"
><code>{{ _code ? _code|trim|raw : code }}</code></pre>
{% endif %}
<button class="btn btn-sm IconModalcopy" data-action="clipboarder#copy" data-clipboarder-target="button">
<twig:ux:icon name="copy" />
</button>
</div>

View File

@@ -0,0 +1,33 @@
{% props name, image, url, description, tags, lazyload = true %}
<article class="DemoCard">
<div class="DemoCard__media">
<img class="DemoCard__image"
src="{{ image }}"
width="640"
height="360"
alt="{{ name }}"
{% if lazyload %}
loading="lazy"
{% endif %}
>
</div>
<div class="DemoCard__content">
<h4 class="DemoCard__title">
<a href="{{ url }}" class="stretched-link">
{{- name -}}
</a>
</h4>
<p class="DemoCard__description">
{{- description -}}
</p>
<p class="DemoCard__tags">
{% for tag in tags %}
<span class="Tag">{{ tag }}</span>
{% endfor %}
</p>
</div>
</article>

View File

@@ -2,7 +2,7 @@
<div class="DocsLink_content">
{% if isSmall %}
<p class="d-flex align-items-center">
<twig:ux:icon name="mdi:book-open-variant-outline" class="Icon me-2" />
<twig:ux:icon name="{{ icon|default('mdi:book-open-variant-outline') }}" class="Icon me-2" />
<a href="{{ url }}" class="DocsLink_link"
rel="{{ isExternal ? 'external noopened noreferrer' }}"

View File

@@ -0,0 +1,68 @@
{%- props
xs = 0,
sm = 0,
md = 0,
lg = 0,
xl = 0,
align = '',
justify = '',
direction = '',
gap = 0,
-%}
{% set xsSize = false %}{% if xs > 0 %}{% set xsSize = "true" %}{% endif %}
{% set smSize = false %}{% if sm > 0 %}{% set smSize = "true" %}{% endif %}
{% set mdSize = false %}{% if md > 0 %}{% set mdSize = "true" %}{% endif %}
{% set lgSize = false %}{% if lg > 0 %}{% set lgSize = "true" %}{% endif %}
{% set xlSize = false %}{% if xl > 0 %}{% set xlSize = "true" %}{% endif %}
{% set alignClass = '' %}
{% if align == 'start' %}{% set alignClass = 'items-start' %}
{% elseif align == 'center' %}{% set alignClass = 'items-center' %}
{% elseif align == 'end' %}{% set alignClass = 'items-end' %}
{% endif %}
{% set justifyClass = '' %}
{% if justify == 'start' %}{% set justifyClass = 'justify-start' %}
{% elseif justify == 'center' %}{% set justifyClass = 'justify-center' %}
{% elseif justify == 'end' %}{% set justifyClass = 'justify-end' %}
{% elseif justify == 'between' %}{% set justifyClass = 'justify-between' %}
{% elseif justify == 'around' %}{% set justifyClass = 'justify-around' %}
{% elseif justify == 'evenly' %}{% set justifyClass = 'justify-evenly' %}
{% endif %}
{% set directionClass = '' %}
{% if direction == 'row' %}{% set directionClass = 'flex-row' %}
{% elseif direction == 'column' %}{% set directionClass = 'flex-col' %}
{% endif %}
{% set gapClass = '' %}
{% if gap > 0 %}
{% set gapClass = 'gap-' ~ gap %}
{% endif %}
{%- set style = html_cva(
base: 'grid',
variants: {
xsSize: {
"true": 'grid-cols-' ~ xs,
},
smSize: {
"true": 'sm:grid-cols-' ~ sm,
},
mdSize: {
"true": 'md:grid-cols-' ~ md,
},
lgSize: {
"true": 'lg:grid-cols-' ~ lg,
},
xlSize: {
"true": 'xl:grid-cols-' ~ xl,
},
},
) -%}
<div
class="{{ style.apply({xsSize, smSize, mdSize, lgSize, xlSize}, attributes.render('class'))|tailwind_merge }} {{ alignClass }} {{ justifyClass }} {{ directionClass }} {{ gapClass }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,77 @@
{%- props
xs = 0,
sm = 0,
md = 0,
lg = 0,
xl = 0,
xsOffset = 0,
smOffset = 0,
mdOffset = 0,
lgOffset = 0,
xlOffset = 0,
first = false,
last = false,
-%}
{% set xsCols = false %}{% if xs > 0 %}{% set xsCols = "true" %}{% endif %}
{% set smCols = false %}{% if sm > 0 %}{% set smCols = "true" %}{% endif %}
{% set mdCols = false %}{% if md > 0 %}{% set mdCols = "true" %}{% endif %}
{% set lgCols = false %}{% if lg > 0 %}{% set lgCols = "true" %}{% endif %}
{% set xlCols = false %}{% if xl > 0 %}{% set xlCols = "true" %}{% endif %}
{% set xsOffsetCols = false %}{% if xsOffset > 0 %}{% set xsOffsetCols = "true" %}{% endif %}
{% set smOffsetCols = false %}{% if smOffset > 0 %}{% set smOffsetCols = "true" %}{% endif %}
{% set mdOffsetCols = false %}{% if mdOffset > 0 %}{% set mdOffsetCols = "true" %}{% endif %}
{% set lgOffsetCols = false %}{% if lgOffset > 0 %}{% set lgOffsetCols = "true" %}{% endif %}
{% set xlOffsetCols = false %}{% if xlOffset > 0 %}{% set xlOffsetCols = "true" %}{% endif %}
{% set firstClass = false %}{% if first %}{% set firstClass = "true" %}{% endif %}
{% set lastClass = false %}{% if last %}{% set lastClass = "true" %}{% endif %}
{%- set style = html_cva(
base: '',
variants: {
xsCols: {
"true": 'col-span-' ~ xs,
},
smCols: {
"true": 'sm:col-span-' ~ sm,
},
mdCols: {
"true": 'md:col-span-' ~ md,
},
lgCols: {
"true": 'lg:col-span-' ~ lg,
},
xlCols: {
"true": 'xl:col-span-' ~ xl,
},
xsOffsetCols: {
"true": 'col-start-' ~ (xsOffset + 1),
},
smOffsetCols: {
"true": 'sm:col-start-' ~ (smOffset + 1),
},
mdOffsetCols: {
"true": 'md:col-start-' ~ (mdOffset + 1),
},
lgOffsetCols: {
"true": 'lg:col-start-' ~ (lgOffset + 1),
},
xlOffsetCols: {
"true": 'xl:col-start-' ~ (xlOffset + 1),
},
firstClass: {
"true": 'order-first',
},
lastClass: {
"true": 'order-last',
},
},
) -%}
<div
class="{{ style.apply({xsCols, smCols, mdCols, lgCols, xlCols, xsOffsetCols, smOffsetCols, mdOffsetCols, lgOffsetCols, xlOffsetCols, firstClass, lastClass}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,60 @@
{%- props
start = 'auto',
center = false,
end = false,
top = false,
middle = false,
bottom = false,
around = false,
between = false,
reverse = false,
-%}
{% set startClass = false %}{% if start != 'auto' %}{% set startClass = "true" %}{% endif %}
{% set centerClass = false %}{% if center %}{% set centerClass = "true" %}{% endif %}
{% set endClass = false %}{% if end %}{% set endClass = "true" %}{% endif %}
{% set topClass = false %}{% if top %}{% set topClass = "true" %}{% endif %}
{% set middleClass = false %}{% if middle %}{% set middleClass = "true" %}{% endif %}
{% set bottomClass = false %}{% if bottom %}{% set bottomClass = "true" %}{% endif %}
{% set aroundClass = false %}{% if around %}{% set aroundClass = "true" %}{% endif %}
{% set betweenClass = false %}{% if between %}{% set betweenClass = "true" %}{% endif %}
{% set reverseClass = false %}{% if reverse %}{% set reverseClass = "true" %}{% endif %}
{%- set style = html_cva(
base: 'flex flex-wrap',
variants: {
startClass: {
"true": 'justify-start',
},
centerClass: {
"true": 'justify-center',
},
endClass: {
"true": 'justify-end',
},
topClass: {
"true": 'items-start',
},
middleClass: {
"true": 'items-center',
},
bottomClass: {
"true": 'items-end',
},
aroundClass: {
"true": 'justify-around',
},
betweenClass: {
"true": 'justify-between',
},
reverseClass: {
"true": 'flex-row-reverse',
},
},
) -%}
<div
class="{{ style.apply({startClass, centerClass, endClass, topClass, middleClass, bottomClass, aroundClass, betweenClass, reverseClass}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,5 @@
<nav {{ attributes.without('class') }}
class="{{ (' ' ~ attributes.render('class'))|tailwind_merge }}"
>
{% block content %}{% endblock %}
</nav>

View File

@@ -27,6 +27,16 @@
title="Read the docs"
url="{{ package.officialDocsUrl }}"
/>
{% if packageBrowseUrl is defined %}
<twig:DocsLink
icon="mdi:package-variant-closed"
class="ms-3"
size="sm"
title="{{ packageBrowseText|default('Explore') }}"
url="{{ packageBrowseUrl }}"
/>
{% endif %}
</div>
</div>
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'relative w-full overflow-auto',
variants: {},
compoundVariants: []
) -%}
<table
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</table>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: '[&_tr:last-child]:border-0',
variants: {},
compoundVariants: []
) -%}
<tbody
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</tbody>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'text-muted-foreground mt-4 text-sm',
variants: {},
compoundVariants: []
) -%}
<caption
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</caption>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
variants: {},
compoundVariants: []
) -%}
<td
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</td>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',
variants: {},
compoundVariants: []
) -%}
<tfoot
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</tfoot>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'text-muted-foreground h-10 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
variants: {},
compoundVariants: []
) -%}
<th
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</th>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: '[&_tr]:border-b',
variants: {},
compoundVariants: []
) -%}
<div
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</div>

View File

@@ -0,0 +1,13 @@
{%- props -%}
{%- set style = html_cva(
base: 'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
variants: {},
compoundVariants: []
) -%}
<tr
class="{{ style.apply({}, attributes.render('class'))|tailwind_merge }}"
{{ attributes.defaults({}).without('class') }}
>
{% block content %}{% endblock %}
</tr>

View File

@@ -23,7 +23,7 @@
<div class="container-fluid container-xxl px-4 pt-4 px-md-5 pt-md-5">
<div style="display: grid; gap: 3rem; grid-template-columns: 1fr 1fr 1fr;">
{% for cookbook in cookbooks %}
<twig:Card
<twig:CookbookCard
name="{{ cookbook.title }}"
description="{{ cookbook.description }}"
image="{{ asset('images/cookbook/%s-1280x720.png'|format(cookbook.slug)) }}"

View File

@@ -4,7 +4,7 @@
<div class="col-12 col-md-6">
{% component Terminal with {bottomPadding: 20} %}
{% block content %}
composer require {{ package.composerName }}
composer require {% if installOnlyInDevMode|default(false) == true%} --dev {% endif %}{{ package.composerName }}
{% endblock %}
{% endcomponent %}
</div>

View File

@@ -0,0 +1,185 @@
{% extends 'ux_packages/package.html.twig' %}
{% block banner %}
{{ include('_banner.html.twig', {color_back: '#4a5d20'}) }}
{% endblock %}
{% block package_header %}
{% component PackageHeader with {
package: 'toolkit',
eyebrowText: 'Toolkit for your pages and templates',
packageBrowseText: 'Components',
packageBrowseUrl: path('app_toolkit_components'),
} %}
{% block title_header %}
Build your Design System.
{% endblock %}
{% block sub_content %}
The UX Toolkit is a collection of components and templates that you can use to build your pages.
{% endblock %}
{% endcomponent %}
{% endblock %}
{% block demo_title %}UX Toolkit{% endblock %}
{% block demo_content %}
<div class="row">
<div class="col-6">
<twig:CodeBlock
filename="templates/ux_packages/toolkit.html.twig"
:showFilename="false"
targetTwigBlock="card_example"
:showTwigExtends="false"
height="auto"
/>
</div>
<div class="col-6">
{% block card_example %}
<twig:Card>
<twig:CardHeader>
<twig:CardTitle>Symfony is cool</twig:CardTitle>
<twig:CardDescription>Symfony is a set of reusable PHP components...</twig:CardDescription>
</twig:CardHeader>
<twig:CardContent>
... and a PHP framework for web projects
</twig:CardContent>
<twig:CardFooter>
<twig:Button primary class="w-full">Visit symfony.com</twig:Button>
</twig:CardFooter>
</twig:Card>
{% endblock %}
</div>
</div>
<div class="row my-3">
<div class="col-6">
<twig:CodeBlock
filename="templates/ux_packages/toolkit.html.twig"
:showFilename="false"
targetTwigBlock="avatar_example"
:showTwigExtends="false"
height="auto"
/>
</div>
<div class="col-6">
{% block avatar_example %}
<twig:Avatar class="inline-block">
<twig:AvatarImage src="https://github.com/symfony.png" alt="@symfony" />
<twig:AvatarFallback>SF</twig:AvatarFallback>
</twig:Avatar>
<twig:Avatar class="inline-block">
<twig:AvatarFallback class="bg-blue-400">SF</twig:AvatarFallback>
</twig:Avatar>
{% endblock %}
</div>
</div>
{% endblock %}
{% block package_install %}
{% set installOnlyInDevMode = true %}
{{ parent() }}
<section class="container-fluid container-xxl px-4 pt-4 px-md-5 pt-md-5">
<p>
Then install your components with the following command:
</p>
{% component Terminal with {bottomPadding: 20} %}
{% block content %}
php bin/console ux:toolkit:install badge
php bin/console ux:toolkit:install button
php bin/console ux:toolkit:install card
php bin/console ux:toolkit:install ...
{% endblock %}
{% endcomponent %}
<p class="mt-4">
Official components require tailwindcss to be installed in your project. Visit the <a class="text-underline" target="_blank" href="https://tailwindcss.com/docs/installation" target="_blank">Tailwind CSS documentation</a> for more information.
Components are based on <a class="text-underline" target="_blank" href="https://ui.shadcn.com/" >shadcdn/ui</a> design system.
</p>
<p class="mt-4">
If you use component library using <code>html_cva()</code> or <code>tailwind_merge()</code> functions, remember to install them with:
</p>
{% component Terminal with {bottomPadding: 20} %}
{% block content %}
composer require --dev tales-from-a-dev/twig-tailwind-extra twig/extra-bundle
{% endblock %}
{% endcomponent %}
</section>
<section class="container-fluid container-xxl px-4 pt-4 px-md-5 pt-md-5">
<h2 class="ubuntu text-center mt-5">Philosophy</h2>
<div class=" justify-content-center mt-5">
<div class="col-12">
<p>
Toolkit provides ready-to-use components, but freely customizable.
</p>
<p>
Official components are copied into your project in the <code>templates/components</code> folder, like classic TwigComponent.
Feel free to modify the code to suit your needs.
</p>
</div>
<div class="col-12 mt-4">
<p>
It is quite possible to install other component libraries, or even redistribute your own components
(for example, if you want to provide your internal Design System).
</p>
<p>
The choice of the component library is made in the configuration of your project:
</p>
<twig:CodeBlock
filename="config/packages/ux_toolkit.yaml"
:showTwigExtends="false"
language="yaml"
showFilename="false"
:copyButton="true"
:stripExcessHtml="false"
height="{{ height|default('auto') }}"
style="--bg-header: rgba(13, 13, 13, 0.729); --header-opacity: .5;"
/>
<p class="mt-4">
The <code>theme</code> key allows you to choose the component library to use, and can be any GitHub repository.
</p>
</div>
</div>
<div class="col-12 mt-4">
<p>
If you want to distribute your own theme, please <a class="text-underline" target="_blank" href="https://docs.github.com/fr/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/classifying-your-repository-with-topics">
classify your repository</a> with the <code>ux-toolkit</code> topic to be referenced easily.
</p>
<p>
Your repository will be automatically detected by the <a class="text-underline" target="_blank" href="https://github.com/search?q=topic%3Aux-toolkit&type=repositories">GitHub search engine</a>.
</div>
</div>
</section>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
{% endblock %}
{% block stylesheets %}
{# some tailwind classes are overrided by current theme #}
<style type="text/css">
.bg-card {
background-color: var(--color-white);
}
.text-primary-foreground {
color: var(--color-white);
}
button.rounded-md {
border-radius: var(--radius-md);
}
</style>
{% endblock %}

View File

@@ -0,0 +1,107 @@
{% extends 'base.html.twig' %}
{% block javascripts %}
{{ parent() }}
<meta name="turbo-prefetch" content="false">
{% endblock %}
{% set meta = {
title: 'UX Toolkit - Build your Design System.',
description: 'Collection of components and templates that you can use to build your pages.',
canonical: url('app_toolkit_components'),
social: {
title: 'UX Toolkit - Build your Design System.',
description: 'Collection of components and templates that you can use to build your pages.',
image: {
url: absolute_url(asset(package.getSocialImage('1200x675'))),
type: 'image/png',
width: 1200,
height: 675,
alt: package.humanName ~ ' - Component Toolkit',
},
}
} %}
{% block header %}
{{ include('_header.html.twig', {
theme: 'white'
}) }}
{% endblock %}
{% block content %}
{% component PackageHeader with {
package: package.name,
eyebrowText: "Build your Design System.",
installOnlyInDevMode: true
} %}
{% block title_header %}
Build your Design System.
{% endblock %}
{% block sub_content %}
Collection of components and templates that you can use to build your pages.
{% endblock %}
{% endcomponent %}
<section class="container-fluid container-xxl px-4 px-md-5 py-3">
<div class="row">
<div class="col-4">
<h3>Components</h2>
<div>
{% for component in components %}
{% if component.type.value == 'component' and component.parentName is null %}
<a href="{{ url('app_toolkit_components', {currentComponent: component.name}) }}"
class="d-block py-2 px-3 mb-2 rounded-3 text-decoration-none {% if currentComponent.name == component.name %}bg-primary text-white{% else %}text-dark{% endif %}">
{{ component.name }}
</a>
{% endif %}
{% endfor %}
</div>
</div>
<div class="col-8">
<h3>{{ currentComponent.name }}</h3>
<div class="pt-3">
<h4>Installation</h4>
<p>
Run this command once to generate the <code>templates/components/{{ currentComponent.name|lower }}.html.twig</code> file in your project.
</p>
{% component Terminal with {bottomPadding: 20} %}
{% block content %}
php bin/console ux:toolkit:install {{ currentComponent.name }}
{% endblock %}
{% endcomponent %}
</div>
{% if examples is not empty %}
<div class="pt-5">
<h4>Examples</h4>
<p>
Now you can directly use the following examples in your twig templates.
</p>
{% for example in examples %}
<div class="mt-4 mb-3">
<h5>{{ example.name }}</h5>
<iframe src="{{ url('app_toolkit_component_preview', {currentComponent: example.name}) }}"
class="w-100 border-0 rounded-3 shadow-sm p-4"
style="height: 400px;"></iframe>
<div class="mt-3">Your code:</div>
<div class="pt-3">
<twig:Code:CodeBlockEmbed code="{{ example.code|trim }}" language="html" highlight="true" />
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,5 @@
{% extends 'ux_toolkit_preview.html.twig' %}
{% block body %}
{{ html | raw }}
{% endblock %}

View File

@@ -0,0 +1,91 @@
{% extends 'base.html.twig' %}
{% block content %}
{% block body %}{% endblock %}
{% endblock %}
{% block javascripts %}
{% if cssFramework|default('tailwindcss') == 'tailwindcss' %}
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
{% endif %}
{% endblock %}
{% block stylesheets %}
{% if cssFramework|default('tailwindcss') == 'tailwindcss' %}
<style>
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 100% 50%;
--destructive-foreground: 210 40% 98%;
--ring: 215 20.2% 65.1%;
--radius: 0.5rem;
}
}
{# we use CDN for the moment. We compile the syles manually #}
{% set classesToUses = [
'background',
'foreground',
'muted',
'muted-foreground',
'popover',
'popover-foreground',
'border',
'input',
'card',
'card-foreground',
'primary',
'primary-foreground',
'secondary',
'secondary-foreground',
'accent',
'accent-foreground',
'destructive',
'destructive-foreground',
'ring',
] %}
{% for class in classesToUses %}
.{{ class }} { background-color: hsl(var(--{{ class }})); }
.bg-{{ class }} { background-color: hsl(var(--{{ class }})); }
.text-{{ class }} { color: hsl(var(--{{ class }})); }
.hover:{{ class }} { background-color: hsl(var(--{{ class }})); }
.focus:{{ class }} { background-color: hsl(var(--{{ class }})); }
.active:{{ class }} { background-color: hsl(var(--{{ class }})); }
.hover:bg-{{ class }} { background-color: hsl(var(--{{ class }})); }
.focus:bg-{{ class }} { background-color: hsl(var(--{{ class }})); }
.active:bg-{{ class }} { background-color: hsl(var(--{{ class }})); }
.hover:text-{{ class }} { color: hsl(var(--{{ class }})); }
.focus:text-{{ class }} { color: hsl(var(--{{ class }})); }
.active:text-{{ class }} { color: hsl(var(--{{ class }})); }
{% endfor %}
</style>
{% endif %}
{% endblock %}
{% block aside %}
{% endblock %}
{% block meta %}
{% endblock %}
{% block banner %}
{% endblock %}