From f5bf31b5b9bc7aa4ac5cc3ea462fb40d5bc6a2b8 Mon Sep 17 00:00:00 2001 From: seb-jean Date: Mon, 25 May 2026 19:40:56 +0200 Subject: [PATCH] [Toolkit] Finalize support for Blocks, add Shadcn's login-01 and login-02 blocks --- src/Toolkit/CHANGELOG.md | 2 + src/Toolkit/kits/shadcn/login-01/README.md | 15 +++++ .../kits/shadcn/login-01/manifest.json | 17 ++++++ .../templates/components/LoginForm.html.twig | 56 ++++++++++++++++++ src/Toolkit/kits/shadcn/login-02/README.md | 34 +++++++++++ .../kits/shadcn/login-02/manifest.json | 17 ++++++ .../templates/components/LoginForm.html.twig | 59 +++++++++++++++++++ src/Toolkit/src/Kit/KitContextRunner.php | 51 +++++++++++----- 8 files changed, 237 insertions(+), 14 deletions(-) create mode 100644 src/Toolkit/kits/shadcn/login-01/README.md create mode 100644 src/Toolkit/kits/shadcn/login-01/manifest.json create mode 100644 src/Toolkit/kits/shadcn/login-01/templates/components/LoginForm.html.twig create mode 100644 src/Toolkit/kits/shadcn/login-02/README.md create mode 100644 src/Toolkit/kits/shadcn/login-02/manifest.json create mode 100644 src/Toolkit/kits/shadcn/login-02/templates/components/LoginForm.html.twig diff --git a/src/Toolkit/CHANGELOG.md b/src/Toolkit/CHANGELOG.md index 6e7482661b0..7441af408d2 100644 --- a/src/Toolkit/CHANGELOG.md +++ b/src/Toolkit/CHANGELOG.md @@ -2,6 +2,8 @@ ## 3.5.0 +- Add support for blocks: ready-to-use page sections built from a kit's components, installed and previewed as a unit +- [Shadcn] Add the `login-01` and `login-02` login blocks - [Shadcn][Flowbite] Merge component classes with `attributes.defaults({ class: '...'|tailwind_classes })` instead of `tailwind_merge` (consumer classes now override component variants) - [Shadcn] Fix Field/Label, InputGroup and Pagination silently dropping their own base classes when forwarding to a child component (their checked/disabled/nested/dark styles now apply) - Remove the now-obsolete `ClassMergeSpacingChecker` linter diff --git a/src/Toolkit/kits/shadcn/login-01/README.md b/src/Toolkit/kits/shadcn/login-01/README.md new file mode 100644 index 00000000000..3207f6ecc56 --- /dev/null +++ b/src/Toolkit/kits/shadcn/login-01/README.md @@ -0,0 +1,15 @@ +# LoginForm + +A simple login form centered in a card. + +```twig {"preview":true} +
+
+ +
+
+``` + +## Installation + +::: installation diff --git a/src/Toolkit/kits/shadcn/login-01/manifest.json b/src/Toolkit/kits/shadcn/login-01/manifest.json new file mode 100644 index 00000000000..ab62691cc59 --- /dev/null +++ b/src/Toolkit/kits/shadcn/login-01/manifest.json @@ -0,0 +1,17 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "block", + "name": "LoginForm", + "description": "A simple login form centered in a card.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": { + "recipe": ["button", "card", "input", "label"], + "composer": [ + "twig/html-extra:^3.24.0", + "symfony/ux-twig-component:^3.5", + "tales-from-a-dev/twig-tailwind-extra:^1.3.0" + ] + } +} diff --git a/src/Toolkit/kits/shadcn/login-01/templates/components/LoginForm.html.twig b/src/Toolkit/kits/shadcn/login-01/templates/components/LoginForm.html.twig new file mode 100644 index 00000000000..b30754ab5f7 --- /dev/null +++ b/src/Toolkit/kits/shadcn/login-01/templates/components/LoginForm.html.twig @@ -0,0 +1,56 @@ +{# @block content Optional content to override the entire form. #} +
+ {%- block content -%} + + + Login + + Enter your email below to login to your account + + + +
+
+
+ Email + +
+
+ + +
+ + Login + + + Login with Google + +
+
+ Don't have an account? + + Sign up + +
+
+
+
+ {%- endblock -%} +
diff --git a/src/Toolkit/kits/shadcn/login-02/README.md b/src/Toolkit/kits/shadcn/login-02/README.md new file mode 100644 index 00000000000..187d0038ad0 --- /dev/null +++ b/src/Toolkit/kits/shadcn/login-02/README.md @@ -0,0 +1,34 @@ +# LoginForm + +A two-column login page with a cover image. + +```twig {"preview":true} +
+
+ +
+
+ +
+
+
+ +
+``` + +## Installation + +::: installation diff --git a/src/Toolkit/kits/shadcn/login-02/manifest.json b/src/Toolkit/kits/shadcn/login-02/manifest.json new file mode 100644 index 00000000000..fddde00330a --- /dev/null +++ b/src/Toolkit/kits/shadcn/login-02/manifest.json @@ -0,0 +1,17 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "block", + "name": "LoginForm", + "description": "A two-column login page with a cover image.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": { + "recipe": ["button", "field", "input"], + "composer": [ + "twig/html-extra:^3.24.0", + "symfony/ux-twig-component:^3.5", + "tales-from-a-dev/twig-tailwind-extra:^1.3.0" + ] + } +} diff --git a/src/Toolkit/kits/shadcn/login-02/templates/components/LoginForm.html.twig b/src/Toolkit/kits/shadcn/login-02/templates/components/LoginForm.html.twig new file mode 100644 index 00000000000..860d0f626f8 --- /dev/null +++ b/src/Toolkit/kits/shadcn/login-02/templates/components/LoginForm.html.twig @@ -0,0 +1,59 @@ +{# @block content Optional content to override the entire form. #} +{%- block content -%} +
+ +
+

Login to your account

+

+ Enter your email below to login to your account +

+
+ + Email + + + + + + + + Login + + Or continue with + + + + + + Login with GitHub + + + Don't have an account? + + Sign up + + + +
+
+{%- endblock -%} diff --git a/src/Toolkit/src/Kit/KitContextRunner.php b/src/Toolkit/src/Kit/KitContextRunner.php index cb7ac15d00a..d29718bf639 100644 --- a/src/Toolkit/src/Kit/KitContextRunner.php +++ b/src/Toolkit/src/Kit/KitContextRunner.php @@ -11,7 +11,7 @@ namespace Symfony\UX\Toolkit\Kit; -use Symfony\UX\Toolkit\Recipe\RecipeType; +use Symfony\UX\Toolkit\Recipe\Recipe; use Symfony\UX\TwigComponent\ComponentFactory; use Symfony\UX\TwigComponent\ComponentTemplateFinderInterface; use Twig\Loader\ChainLoader; @@ -42,10 +42,10 @@ public function __construct( * * @return TResult */ - public function runForKit(Kit $kit, callable $callback): mixed + public function runForKit(Kit $kit, callable $callback, ?Recipe $prioritizedRecipe = null): mixed { - $resetTwig = $this->contextualizeTwig($kit); - $resetComponentFactory = $this->contextualizeComponentFactory($kit); + $resetTwig = $this->contextualizeTwig($kit, $prioritizedRecipe); + $resetComponentFactory = $this->contextualizeComponentFactory($kit, $prioritizedRecipe); try { return $callback($kit); @@ -58,12 +58,18 @@ public function runForKit(Kit $kit, callable $callback): mixed /** * @return callable(): void */ - private function contextualizeTwig(Kit $kit): callable + private function contextualizeTwig(Kit $kit, ?Recipe $prioritizedRecipe): callable { $initialTwigLoader = $this->twig->getLoader(); $loaders = []; - foreach ($kit->getRecipes(type: RecipeType::Component) as $recipe) { + if (null !== $prioritizedRecipe) { + $loaders[] = new FilesystemLoader($prioritizedRecipe->absolutePath); + } + foreach ($kit->getRecipes() as $recipe) { + if (null !== $prioritizedRecipe && $recipe->name === $prioritizedRecipe->name) { + continue; + } $loaders[] = new FilesystemLoader($recipe->absolutePath); } $loaders[] = $initialTwigLoader; @@ -76,7 +82,7 @@ private function contextualizeTwig(Kit $kit): callable /** * @return callable(): void */ - private function contextualizeComponentFactory(Kit $kit): callable + private function contextualizeComponentFactory(Kit $kit, ?Recipe $prioritizedRecipe): callable { $reflComponentFactory = new \ReflectionClass($this->componentFactory); @@ -86,7 +92,7 @@ private function contextualizeComponentFactory(Kit $kit): callable $reflComponentTemplateFinder = $reflComponentFactory->getProperty('componentTemplateFinder'); $initialComponentTemplateFinder = $reflComponentTemplateFinder->getValue($this->componentFactory); - $reflComponentTemplateFinder->setValue($this->componentFactory, $this->createComponentTemplateFinder($kit)); + $reflComponentTemplateFinder->setValue($this->componentFactory, $this->createComponentTemplateFinder($kit, $prioritizedRecipe)); return function () use ($reflConfig, $initialConfig, $reflComponentTemplateFinder, $initialComponentTemplateFinder): void { $reflConfig->setValue($this->componentFactory, $initialConfig); @@ -94,18 +100,35 @@ private function contextualizeComponentFactory(Kit $kit): callable }; } - private function createComponentTemplateFinder(Kit $kit): ComponentTemplateFinderInterface + private function createComponentTemplateFinder(Kit $kit, ?Recipe $prioritizedRecipe): ComponentTemplateFinderInterface { - return self::$componentTemplateFinders[$kit->manifest->name] ??= new class($kit) implements ComponentTemplateFinderInterface { - public function __construct(private readonly Kit $kit) - { + $cacheKey = $kit->manifest->name.($prioritizedRecipe ? '/'.$prioritizedRecipe->name : ''); + + return self::$componentTemplateFinders[$cacheKey] ??= new class($kit, $prioritizedRecipe) implements ComponentTemplateFinderInterface { + public function __construct( + private readonly Kit $kit, + private readonly ?Recipe $prioritizedRecipe, + ) { } public function findAnonymousComponentTemplate(string $name): ?string { - foreach ($this->kit->getRecipes(type: RecipeType::Component) as $recipe) { + $templateSuffix = 'templates/components/'.str_replace(':', '/', $name).'.html.twig'; + + if (null !== $this->prioritizedRecipe) { + foreach ($this->prioritizedRecipe->getFiles() as $file) { + if (str_ends_with($file->sourceRelativePathName, $templateSuffix)) { + return $file->sourceRelativePathName; + } + } + } + + foreach ($this->kit->getRecipes() as $recipe) { + if (null !== $this->prioritizedRecipe && $recipe->name === $this->prioritizedRecipe->name) { + continue; + } foreach ($recipe->getFiles() as $file) { - if (str_ends_with($file->sourceRelativePathName, 'templates/components/'.str_replace(':', '/', $name).'.html.twig')) { + if (str_ends_with($file->sourceRelativePathName, $templateSuffix)) { return $file->sourceRelativePathName; } }