Skip to content

Commit

Permalink
Merge branch 'symfony:2.x' into 2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
smnandre authored Aug 13, 2024
2 parents 3c5e9dd + e6be7a8 commit 9b3ecbe
Show file tree
Hide file tree
Showing 32 changed files with 457 additions and 45 deletions.
20 changes: 10 additions & 10 deletions src/Autocomplete/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,13 @@ Passing Extra Options to the Ajax-powered Autocomplete
Autocomplete field options are not preserved when the field is rendered on an Ajax call. So, features like exclude some options
based on the current form data are not possible by default. To partially avoid this limitation, the `extra_options` option was added.

::warning
.. warning::

Only scalar values (`string`, `integer`, `float`, `boolean`), `null` and `arrays` (consisted from the same types as mentioned before) can be passed as extra options.
Only scalar values (``string``, ``integer``, ``float``, ``boolean``), ``null`` and ``array`` (consisted from the same types as mentioned before) can be passed as extra options.

Considering the following example, when the form type is rendered for the first time, it will use the `query_builder` defined
while adding a `food` field to the `FoodForm`. However, when the Ajax is used to fetch the results, on the consequent renders,
the default `query_builder` will be used::
Considering the following example, when the form type is rendered for the first time, it will use the ``query_builder`` defined
while adding a ``food`` field to the ``FoodForm``. However, when the Ajax is used to fetch the results, on the consequent renders,
the default ``query_builder`` will be used::

// src/Form/FoodForm.php
// ...
Expand All @@ -323,8 +323,8 @@ the default `query_builder` will be used::
}

If some food can be consisted of other foods, we might want to exclude the "root" food from the list of available foods.
To achieve this, we can remove the `query_builder` option from the above example and pass the `excluded_foods` extra option
to the `FoodAutocompleteField`::
To achieve this, we can remove the ``query_builder`` option from the above example and pass the ``excluded_foods`` extra option
to the ``FoodAutocompleteField``::

// src/Form/FoodForm.php
// ...
Expand All @@ -345,8 +345,8 @@ to the `FoodAutocompleteField`::
}
}

The magic of the `extra_options` is that it will be passed to the `FoodAutocompleteField` every time an Ajax call is made.
So now, we can just use the `excluded_foods` extra option in the default `query_builder` of the `FoodAutocompleteField`::
The magic of the ``extra_options`` is that it will be passed to the ``FoodAutocompleteField`` every time an Ajax call is made.
So now, we can just use the ``excluded_foods`` extra option in the default ``query_builder`` of the ``FoodAutocompleteField``::

// src/Form/FoodAutocompleteField.php
// ...
Expand Down Expand Up @@ -602,7 +602,7 @@ If you need to pass extra options to the autocompleter, you can do so by impleme

.. tip::

If you want to know **why** you might need to use the `extra options` feature, see :ref:`passing-extra-options-to-the-ajax-powered-autocomplete`.
If you want to know **why** you might need to use the ``extra_options`` feature, see :ref:`passing-extra-options-to-the-ajax-powered-autocomplete`.

.. code-block:: diff
Expand Down
2 changes: 1 addition & 1 deletion src/LiveComponent/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,7 @@ To add a custom Stimulus controller to your root component element:

.. code-block:: html+twig

<div {{ attributes.defaults(stimulus_controller('my-controller', { someValue: 'foo' })) }}>
<div {{ attributes.defaults(stimulus_controller('some-custom', { someValue: 'foo' })) }}>

JavaScript Component Hooks
~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
2 changes: 1 addition & 1 deletion src/Map/src/Bridge/Google/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "symfony/ux-google-map",
"type": "symfony-ux-map-bridge",
"description": "Symfony UX Map GoogleMaps Bridge",
"keywords": ["google-maps", "map", "symfony", "ux"],
"keywords": ["symfony-ux", "google-maps", "map"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
Expand Down
2 changes: 1 addition & 1 deletion src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as L from 'leaflet';
class map_controller extends AbstractMapController {
connect() {
L.Marker.prototype.options.icon = L.divIcon({
html: '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" stroke-linecap="round" clip-rule="evenodd" viewBox="0 0 500 820"><defs><linearGradient id="a" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -37.57 37.57 0 416.45 541)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#126FC6"/><stop offset="1" stop-color="#4C9CD1"/></linearGradient><linearGradient id="b" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -19.05 19.05 0 414.48 522.49)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2E6C97"/><stop offset="1" stop-color="#3883B7"/></linearGradient></defs><circle cx="252.31" cy="266.24" r="83.99" fill="#fff"/><path fill="url(#a)" stroke="url(#b)" stroke-width="1.1" d="M416.54 503.61c-6.57 0-12.04 5.7-12.04 11.87 0 2.78 1.56 6.3 2.7 8.74l9.3 17.88 9.26-17.88c1.13-2.43 2.74-5.79 2.74-8.74 0-6.18-5.38-11.87-11.96-11.87Zm0 7.16a4.69 4.69 0 1 1-.02 9.4 4.69 4.69 0 0 1 .02-9.4Z" transform="translate(-7889.1 -9807.44) scale(19.54)"/></svg>',
html: '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" stroke-linecap="round" clip-rule="evenodd" viewBox="0 0 500 820"><defs><linearGradient id="__sf_ux_map_gradient_marker_fill" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -37.57 37.57 0 416.45 541)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#126FC6"/><stop offset="1" stop-color="#4C9CD1"/></linearGradient><linearGradient id="__sf_ux_map_gradient_marker_border" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -19.05 19.05 0 414.48 522.49)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2E6C97"/><stop offset="1" stop-color="#3883B7"/></linearGradient></defs><circle cx="252.31" cy="266.24" r="83.99" fill="#fff"/><path fill="url(#__sf_ux_map_gradient_marker_fill)" stroke="url(#__sf_ux_map_gradient_marker_border)" stroke-width="1.1" d="M416.54 503.61c-6.57 0-12.04 5.7-12.04 11.87 0 2.78 1.56 6.3 2.7 8.74l9.3 17.88 9.26-17.88c1.13-2.43 2.74-5.79 2.74-8.74 0-6.18-5.38-11.87-11.96-11.87Zm0 7.16a4.69 4.69 0 1 1-.02 9.4 4.69 4.69 0 0 1 .02-9.4Z" transform="translate(-7889.1 -9807.44) scale(19.54)"/></svg>',
iconSize: [25, 41],
iconAnchor: [12.5, 41],
popupAnchor: [0, -41],
Expand Down
2 changes: 1 addition & 1 deletion src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default class extends AbstractMapController<
> {
connect(): void {
L.Marker.prototype.options.icon = L.divIcon({
html: '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" stroke-linecap="round" clip-rule="evenodd" viewBox="0 0 500 820"><defs><linearGradient id="a" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -37.57 37.57 0 416.45 541)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#126FC6"/><stop offset="1" stop-color="#4C9CD1"/></linearGradient><linearGradient id="b" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -19.05 19.05 0 414.48 522.49)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2E6C97"/><stop offset="1" stop-color="#3883B7"/></linearGradient></defs><circle cx="252.31" cy="266.24" r="83.99" fill="#fff"/><path fill="url(#a)" stroke="url(#b)" stroke-width="1.1" d="M416.54 503.61c-6.57 0-12.04 5.7-12.04 11.87 0 2.78 1.56 6.3 2.7 8.74l9.3 17.88 9.26-17.88c1.13-2.43 2.74-5.79 2.74-8.74 0-6.18-5.38-11.87-11.96-11.87Zm0 7.16a4.69 4.69 0 1 1-.02 9.4 4.69 4.69 0 0 1 .02-9.4Z" transform="translate(-7889.1 -9807.44) scale(19.54)"/></svg>',
html: '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" stroke-linecap="round" clip-rule="evenodd" viewBox="0 0 500 820"><defs><linearGradient id="__sf_ux_map_gradient_marker_fill" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -37.57 37.57 0 416.45 541)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#126FC6"/><stop offset="1" stop-color="#4C9CD1"/></linearGradient><linearGradient id="__sf_ux_map_gradient_marker_border" x1="0" x2="1" y1="0" y2="0" gradientTransform="matrix(0 -19.05 19.05 0 414.48 522.49)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2E6C97"/><stop offset="1" stop-color="#3883B7"/></linearGradient></defs><circle cx="252.31" cy="266.24" r="83.99" fill="#fff"/><path fill="url(#__sf_ux_map_gradient_marker_fill)" stroke="url(#__sf_ux_map_gradient_marker_border)" stroke-width="1.1" d="M416.54 503.61c-6.57 0-12.04 5.7-12.04 11.87 0 2.78 1.56 6.3 2.7 8.74l9.3 17.88 9.26-17.88c1.13-2.43 2.74-5.79 2.74-8.74 0-6.18-5.38-11.87-11.96-11.87Zm0 7.16a4.69 4.69 0 1 1-.02 9.4 4.69 4.69 0 0 1 .02-9.4Z" transform="translate(-7889.1 -9807.44) scale(19.54)"/></svg>',
iconSize: [25, 41],
iconAnchor: [12.5, 41],
popupAnchor: [0, -41],
Expand Down
2 changes: 1 addition & 1 deletion src/Map/src/Bridge/Leaflet/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "symfony/ux-leaflet-map",
"type": "symfony-ux-map-bridge",
"description": "Symfony UX Map Leaflet Bridge",
"keywords": ["leaflet", "map", "symfony", "ux"],
"keywords": ["symfony-ux", "leaflet", "map"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
Expand Down
4 changes: 4 additions & 0 deletions src/TwigComponent/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 2.20.0

- Add Anonymous Component support for 3rd-party bundles #2019

## 2.17.0

- Add nested attribute support #1405
Expand Down
48 changes: 43 additions & 5 deletions src/TwigComponent/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,42 @@ controls how components are named and where their templates live:
If a component class matches multiple namespaces, the first matched will
be used.

3rd-Party Bundle
~~~~~~~~~~~~~~~~

The flexibility of Twig Components is extended even further when integrated
with third-party bundles, allowing developers to seamlessly include pre-built
components into their projects.

Anonymous Components
--------------------

.. versionadded:: 2.20

The bundle convention for Anonymous components was added in TwigComponents 2.18.

Using a component from a third-party bundle is just as straightforward as using
one from your own application. Once the bundle is installed and configured, you
can reference its components directly within your Twig templates:

.. code-block:: html+twig

<twig:Shadcn:Button type="primary">
Click me
</twig:Shadcn:Button>

Here, the component name is composed of the bundle's Twig namespace ``Shadcn``, followed
by a colon, and then the component path Button.

.. note::

You can discover the Twig namespace of every registered bundle by inspecting the
``bin/console debug:twig`` command.

The component must be located in the bundle's ``templates/components/`` directory. For
example, the component referenced as ``<twig:Shadcn:Button>`` should have its template
file at ``templates/components/Button.html.twig`` within the Shadcn bundle.

Debugging Components
--------------------

Expand All @@ -1635,13 +1671,14 @@ that live in ``templates/components/``:
$ php bin/console debug:twig-component
+---------------+-----------------------------+------------------------------------+------+
| Component | Class | Template | Live |
| Component | Class | Template | Type |
+---------------+-----------------------------+------------------------------------+------+
| Coucou | App\Components\Alert | components/Coucou.html.twig | |
| RandomNumber | App\Components\RandomNumber | components/RandomNumber.html.twig | X |
| RandomNumber | App\Components\RandomNumber | components/RandomNumber.html.twig | Live |
| Test | App\Components\foo\Test | components/foo/Test.html.twig | |
| Button | Anonymous component | components/Button.html.twig | |
| foo:Anonymous | Anonymous component | components/foo/Anonymous.html.twig | |
| Button | | components/Button.html.twig | Anon |
| foo:Anonymous | | components/foo/Anonymous.html.twig | Anon |
| Acme:Button | | @Acme/components/Button.html.twig | Anon |
+---------------+-----------------------------+------------------------------------+------+
Pass the name of some component as an argument to print its details:
Expand All @@ -1654,9 +1691,10 @@ Pass the name of some component as an argument to print its details:
| Property | Value |
+---------------------------------------------------+-----------------------------------+
| Component | RandomNumber |
| Live | X |
| Class | App\Components\RandomNumber |
| Template | components/RandomNumber.html.twig |
| Type | Live |
+---------------------------------------------------+-----------------------------------+
| Properties (type / name / default value if exist) | string $name = toto |
| | string $type = test |
| Live Properties | int $max = 1000 |
Expand Down
40 changes: 37 additions & 3 deletions src/TwigComponent/src/Command/TwigComponentDebugCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Symfony\UX\TwigComponent\ComponentMetadata;
use Symfony\UX\TwigComponent\Twig\PropsNode;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;

#[AsCommand(name: 'debug:twig-component', description: 'Display components and them usages for an application')]
class TwigComponentDebugCommand extends Command
Expand Down Expand Up @@ -148,13 +149,46 @@ private function findComponents(): array
*/
private function findAnonymousComponents(): array
{
$componentsDir = $this->twigTemplatesPath.'/'.$this->anonymousDirectory;
$dirs = [$componentsDir => FilesystemLoader::MAIN_NAMESPACE];
$twigLoader = $this->twig->getLoader();
if ($twigLoader instanceof FilesystemLoader) {
foreach ($twigLoader->getNamespaces() as $namespace) {
if (str_starts_with($namespace, '!')) {
continue; // ignore parent convention namespaces
}

foreach ($twigLoader->getPaths($namespace) as $path) {
if (FilesystemLoader::MAIN_NAMESPACE === $namespace) {
$componentsDir = $path.'/'.$this->anonymousDirectory;
} else {
$componentsDir = $path.'/components';
}

if (!is_dir($componentsDir)) {
continue;
}

$dirs[$componentsDir] = $namespace;
}
}
}

$components = [];
$anonymousPath = $this->twigTemplatesPath.'/'.$this->anonymousDirectory;
$finderTemplates = new Finder();
$finderTemplates->files()->in($anonymousPath)->notPath('/_')->name('*.html.twig');
$finderTemplates->files()
->in(array_keys($dirs))
->notPath('/_')
->name('*.html.twig')
;
foreach ($finderTemplates as $template) {
$component = str_replace('/', ':', $template->getRelativePathname());
$component = substr($component, 0, -10);
$component = substr($component, 0, -10); // remove file extension ".html.twig"

if (isset($dirs[$template->getPath()]) && FilesystemLoader::MAIN_NAMESPACE !== $dirs[$template->getPath()]) {
$component = $dirs[$template->getPath()].':'.$component;
}

$components[$component] = $component;
}

Expand Down
10 changes: 10 additions & 0 deletions src/TwigComponent/src/ComponentTemplateFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ public function findAnonymousComponentTemplate(string $name): ?string
return $template;
}

$parts = explode('/', $componentPath, 2);
if (\count($parts) < 2) {
return null;
}

$template = '@'.$parts[0].'/components/'.$parts[1].'.html.twig';
if ($loader->exists($template)) {
return $template;
}

return null;
}
}
22 changes: 22 additions & 0 deletions src/TwigComponent/tests/Fixtures/Bundle/AcmeBundle/AcmeBundle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\TwigComponent\Tests\Fixtures\Bundle\AcmeBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class AcmeBundle extends Bundle
{
public function getPath(): string
{
return __DIR__;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 2 additions & 0 deletions src/TwigComponent/tests/Fixtures/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\UX\TwigComponent\Tests\Fixtures\Bundle\AcmeBundle\AcmeBundle;
use Symfony\UX\TwigComponent\Tests\Fixtures\Component\ComponentB;
use Symfony\UX\TwigComponent\TwigComponentBundle;

Expand All @@ -32,6 +33,7 @@ public function registerBundles(): iterable
yield new FrameworkBundle();
yield new TwigBundle();
yield new TwigComponentBundle();
yield new AcmeBundle();
}

protected function configureContainer(ContainerConfigurator $c): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,21 @@ public function testWithAnonymousComponent(): void
$this->assertStringContainsString('primary = true', $display);
}

public function testWithBundleAnonymousComponent(): void
{
$commandTester = $this->createCommandTester();
$commandTester->execute(['name' => 'Acme:Button']);

$commandTester->assertCommandIsSuccessful();

$display = $commandTester->getDisplay();

$this->tableDisplayCheck($display);
$this->assertStringContainsString('Acme:Button', $display);
$this->assertStringContainsString('@Acme/components/Button.html.twig', $display);
$this->assertStringContainsString('Anonymous', $display);
}

public function testWithoutPublicProps(): void
{
$commandTester = $this->createCommandTester();
Expand Down
9 changes: 9 additions & 0 deletions src/TwigComponent/tests/Integration/ComponentFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ public function testAnonymous(): void
$this->factory()->metadataFor('anonymous:AButton');
}

public function testLoadingAnonymousComponentFromBundle(): void
{
$metadata = $this->factory()->metadataFor('Acme:Button');

$this->assertSame('@Acme/components/Button.html.twig', $metadata->getTemplate());
$this->assertSame('Acme:Button', $metadata->getName());
$this->assertNull($metadata->get('class'));
}

public function testAutoNamingInSubDirectory(): void
{
$metadata = $this->factory()->metadataFor('SubDirectory:ComponentInSubDirectory');
Expand Down
2 changes: 2 additions & 0 deletions ux.symfony.com/assets/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@

// Layouts
@import "layouts/container";
@import "layouts/cluster";
@import "layouts/grid";
@import "layouts/section";

// Components
@import "components/Badge";
@import "components/Banner";
@import "components/Browser";
@import "components/Changelog";
Expand Down
50 changes: 50 additions & 0 deletions ux.symfony.com/assets/styles/components/_Badge.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// -----------------------------------------------------------------
// Badge
// -----------------------------------------------------------------

.Badge {
background: #1D1F23;
padding: .2rem .75rem;
border-radius: .5rem;
display: inline-flex;
flex-wrap: nowrap;
align-items: center;
border: 1px solid #141415;
font-size: .8rem;
gap: .5rem;
position: relative;
}

.Badge__label {
font-stretch: condensed;
text-transform: uppercase;
color: #fff;
font-weight: lighter;
font-size: smaller;
display: flex;
align-items: center;
gap: .5rem;

&:after {
content: ":";
opacity: .5;
margin-left: -.25rem;
}
}

.Badge__icon {
width: 1em;
height: 1em;
margin-block-start: -0.125em;
}

.Badge__value {
color: #81ADB5;
font-weight: normal;

a::after {
content: "";
position: absolute;
inset: 0;
}
}
Loading

0 comments on commit 9b3ecbe

Please sign in to comment.