diff --git a/src/Toolkit/bin/ux-toolkit-kit-debug b/src/Toolkit/bin/ux-toolkit-kit-debug index f10d9edc0f1..70ee0256e8a 100755 --- a/src/Toolkit/bin/ux-toolkit-kit-debug +++ b/src/Toolkit/bin/ux-toolkit-kit-debug @@ -22,6 +22,7 @@ use Symfony\Contracts\Service\ServiceProviderInterface; use Symfony\UX\Toolkit\Command\DebugKitCommand; use Symfony\UX\Toolkit\Kit\KitSynchronizer; use Symfony\UX\Toolkit\Kit\KitFactory; +use Symfony\UX\Toolkit\Recipe\RecipeSynchronizer; use Symfony\UX\Toolkit\Registry\GitHubRegistry; use Symfony\UX\Toolkit\Registry\LocalRegistry; use Symfony\UX\Toolkit\Registry\RegistryFactory; @@ -46,7 +47,8 @@ if (!class_exists(Application::class)) { } $filesystem = new Filesystem(); -$kitFactory = new KitFactory($filesystem, new KitSynchronizer($filesystem)); +$kitSynchronizer = new KitSynchronizer($filesystem, new RecipeSynchronizer($filesystem)); +$kitFactory = new KitFactory($filesystem, $kitSynchronizer); (new Application())->add($command = new DebugKitCommand($kitFactory)) ->getApplication() diff --git a/src/Toolkit/config/services.php b/src/Toolkit/config/services.php index 3406e962806..31ea42ee77d 100644 --- a/src/Toolkit/config/services.php +++ b/src/Toolkit/config/services.php @@ -11,11 +11,13 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\UX\Toolkit\Command\CreateKitCommand; use Symfony\UX\Toolkit\Command\DebugKitCommand; -use Symfony\UX\Toolkit\Command\InstallComponentCommand; +use Symfony\UX\Toolkit\Command\InstallCommand; use Symfony\UX\Toolkit\Kit\KitContextRunner; use Symfony\UX\Toolkit\Kit\KitFactory; use Symfony\UX\Toolkit\Kit\KitSynchronizer; +use Symfony\UX\Toolkit\Recipe\RecipeSynchronizer; use Symfony\UX\Toolkit\Registry\GitHubRegistry; use Symfony\UX\Toolkit\Registry\LocalRegistry; use Symfony\UX\Toolkit\Registry\RegistryFactory; @@ -34,7 +36,13 @@ ]) ->tag('console.command') - ->set('.ux_toolkit.command.install', InstallComponentCommand::class) + ->set('.ux_toolkit.command.create_kit', CreateKitCommand::class) + ->args([ + service('filesystem'), + ]) + ->tag('console.command') + + ->set('.ux_toolkit.command.install', InstallCommand::class) ->args([ service('.ux_toolkit.registry.registry_factory'), service('filesystem'), @@ -75,6 +83,7 @@ ->set('.ux_toolkit.kit.kit_synchronizer', KitSynchronizer::class) ->args([ service('filesystem'), + service('.ux_toolkit.recipe.recipe_synchronizer'), ]) ->set('ux_toolkit.kit.kit_context_runner', KitContextRunner::class) @@ -83,5 +92,7 @@ service('twig'), service('ux.twig_component.component_factory'), ]) + + ->set('.ux_toolkit.recipe.recipe_synchronizer', RecipeSynchronizer::class) ; }; diff --git a/src/Toolkit/doc/index.rst b/src/Toolkit/doc/index.rst index 556ef1e669b..40f8eda1ea7 100644 --- a/src/Toolkit/doc/index.rst +++ b/src/Toolkit/doc/index.rst @@ -7,26 +7,24 @@ to change, or even change drastically. Symfony UX Toolkit provides a set of ready-to-use kits for Symfony applications. It is part of `the Symfony UX initiative`_. -Kits are a nice way to begin a new Symfony application, by providing a set -of `Twig components`_ (based on Tailwind CSS, but fully customizable depending -on your needs). +Kits are a nice way to begin a new Symfony application, they contains +recipes to install nicely-crafter `Twig components`_ (already stylized, +but fully customizable depending on your needs) and more. Please note that the **UX Toolkit is not a library of UI components**, but **a tool to help you build your own UI components**. It uses the same approach than the popular `Shadcn UI`_, and a similar approach than `Tailwind Plus`_. -After installing the UX Toolkit, you can start pulling the components you need -from `UX Toolkit Kits`_, and use them in your project. -They become **your own components**, and **you can customize them as you want**. +After installing the UX Toolkit, you can start installing the recipes you need +from `UX Toolkit Kits`_ and use them in your project. +Files created by the recipes become part of your project, and +you can customize them as you want. Additionally, some `Twig components`_ use ``html_cva`` and ``tailwind_merge``, you can either remove them from your project or install ``twig/html-extra`` and ``tales-from-a-dev/twig-tailwind-extra`` to use them. -Also, we do not force you to use Tailwind CSS at all. You can use whatever -CSS framework you want, but you will need to adapt the UI components to it. - Installation ------------ @@ -37,27 +35,25 @@ Install the UX Toolkit using Composer and Symfony Flex: # The UX Toolkit is a development dependency: $ composer require --dev symfony/ux-toolkit - # If you want to keep `html_cva` and `tailwind_merge` in your Twig components: - $ composer require twig/extra-bundle twig/html-extra:^3.12.0 tales-from-a-dev/twig-tailwind-extra - Usage ----- -You may find a list of components in the `UX Components page`_, with the installation instructions for each of them. +You may find a list of available kits in the `UX Toolkit Kits`_ page, with the installation instructions for each of them. For example, if you want to install a `Button` component, you will find the following instruction: .. code-block:: terminal - $ php bin/console ux:toolkit:install-component Button --kit= + $ php bin/console ux:install Button --kit= -It will create the ``templates/components/Button.html.twig`` file, and you will be able to use the `Button` component like this: +It will create the ``templates/components/Button.html.twig`` file in your project, +and you will be able to use the `Button` component like this: .. code-block:: html+twig Click me -Create your own kit +Create your own Kit ------------------- You have the ability to create and share your own kit with the community, @@ -96,19 +92,55 @@ After creating your kit, the repository should have the following structure: .. code-block:: text . - ├── docs - │ └── components - │ └── Button.twig - ├── manifest.json - └── templates - └── components - └── Button.html.twig - -A kit is composed of: - -- A ``manifest.json`` file, that describes the kit (name, license, homepage, authors, ...), -- A ``templates/components`` directory, that contains the Twig components, -- A ``docs/components`` directory, optional, that contains the documentation for each "root" Twig component. + ├── Button + │   ├── manifest.json + │   └── templates + │   └── components + │   └── Button.html.twig + └── manifest.json + + +A kit is described by a ``manifest.json`` file at the root directory, which contains the metadata of the kit: + +.. code-block:: json + + { + "$schema": "../vendor/symfony/ux-toolkit/schema-kit-v1.json", + "name": "My UX Toolkit Kit", + "description": "A custom kit for Symfony UX Toolkit.", + "homepage": "https://github/com/User/MyUxToolkitKit", + "license": "MIT" + } + +Then, a kit can contain one or more recipes. Each recipe is a directory +with a ``manifest.json`` file and some files to be copied into the project. + +The ``manifest.json`` file of a recipe contains the metadata of the recipe: + +.. code-block:: json + + { + "$schema": "../vendor/symfony/ux-toolkit/schema-kit-recipe-v1.json", + "name": "Button", + "description": "A clickable element that triggers actions or events, supporting various styles and states.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": { + { + "type": "php", + "package": "twig/extra-bundle" + }, + { + "type": "php", + "package": "twig/html-extra:^3.12.0" + }, + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + } + } Using your kit ~~~~~~~~~~~~~~ @@ -117,10 +149,10 @@ Once your kit is published on GitHub, you can use it by specifying the ``--kit`` .. code-block:: terminal - $ php bin/console ux:toolkit:install-component Button --kit=github.com/my-username/my-ux-toolkit-kit + $ php bin/console ux:install Button --kit=github.com/my-username/my-ux-toolkit-kit # or for a specific version - $ php bin/console ux:toolkit:install-component Button --kit=github.com/my-username/my-ux-toolkit-kit:1.0.0 + $ php bin/console ux:install Button --kit=github.com/my-username/my-ux-toolkit-kit:1.0.0 Backward Compatibility promise ------------------------------ diff --git a/src/Toolkit/kits/shadcn/docs/components/Alert.md b/src/Toolkit/kits/shadcn/Alert/EXAMPLES.md similarity index 53% rename from src/Toolkit/kits/shadcn/docs/components/Alert.md rename to src/Toolkit/kits/shadcn/Alert/EXAMPLES.md index 5317941445c..c70e5af2708 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Alert.md +++ b/src/Toolkit/kits/shadcn/Alert/EXAMPLES.md @@ -1,6 +1,6 @@ -# Alert +# Examples -A notification component that displays important messages with an icon, title, and description. +## Default ```twig {"preview":true} @@ -12,29 +12,7 @@ A notification component that displays important messages with an icon, title, a ``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} - - - Heads up! - - You can add components to your app using the cli. - - -``` - -### Destructive +## Destructive ```twig {"preview":true} diff --git a/src/Toolkit/kits/shadcn/templates/components/Separator.meta.json b/src/Toolkit/kits/shadcn/Alert/manifest.json similarity index 54% rename from src/Toolkit/kits/shadcn/templates/components/Separator.meta.json rename to src/Toolkit/kits/shadcn/Alert/manifest.json index 9e08e59b32e..979175eac1d 100644 --- a/src/Toolkit/kits/shadcn/templates/components/Separator.meta.json +++ b/src/Toolkit/kits/shadcn/Alert/manifest.json @@ -1,5 +1,11 @@ { - "$schema": "../../../../schemas/component.json", + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Alert", + "description": "A notification component that displays important messages with an icon, title, and description.", + "copy-files": { + "templates/": "templates/" + }, "dependencies": [ { "type": "php", diff --git a/src/Toolkit/kits/shadcn/templates/components/Alert.html.twig b/src/Toolkit/kits/shadcn/Alert/templates/components/Alert.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Alert.html.twig rename to src/Toolkit/kits/shadcn/Alert/templates/components/Alert.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Alert/Description.html.twig b/src/Toolkit/kits/shadcn/Alert/templates/components/Alert/Description.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Alert/Description.html.twig rename to src/Toolkit/kits/shadcn/Alert/templates/components/Alert/Description.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Alert/Title.html.twig b/src/Toolkit/kits/shadcn/Alert/templates/components/Alert/Title.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Alert/Title.html.twig rename to src/Toolkit/kits/shadcn/Alert/templates/components/Alert/Title.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/AspectRatio.md b/src/Toolkit/kits/shadcn/AspectRatio/EXAMPLES.md similarity index 80% rename from src/Toolkit/kits/shadcn/docs/components/AspectRatio.md rename to src/Toolkit/kits/shadcn/AspectRatio/EXAMPLES.md index 4c8e2d32865..08709d3e68a 100644 --- a/src/Toolkit/kits/shadcn/docs/components/AspectRatio.md +++ b/src/Toolkit/kits/shadcn/AspectRatio/EXAMPLES.md @@ -1,6 +1,6 @@ -# AspectRatio +# Examples -A container that maintains a specific width-to-height ratio for its content. +## Default ```twig {"preview":true,"height":"400px"} @@ -12,17 +12,7 @@ A container that maintains a specific width-to-height ratio for its content. ``` -## Installation - - - -## Usage - - - -## Examples - -### With a 1 / 1 aspect ratio +## With a 1 / 1 aspect ratio ```twig {"preview":true,"height":"400px"} @@ -34,7 +24,7 @@ A container that maintains a specific width-to-height ratio for its content. ``` -### With a 16 / 9 aspect ratio +## With a 16 / 9 aspect ratio ```twig {"preview":true,"height":"400px"} diff --git a/src/Toolkit/kits/shadcn/AspectRatio/manifest.json b/src/Toolkit/kits/shadcn/AspectRatio/manifest.json new file mode 100644 index 00000000000..8666063aa52 --- /dev/null +++ b/src/Toolkit/kits/shadcn/AspectRatio/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "AspectRatio", + "description": "A container that maintains a specific width-to-height ratio for its content.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "twig/extra-bundle" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/AspectRatio.html.twig b/src/Toolkit/kits/shadcn/AspectRatio/templates/components/AspectRatio.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/AspectRatio.html.twig rename to src/Toolkit/kits/shadcn/AspectRatio/templates/components/AspectRatio.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Avatar.md b/src/Toolkit/kits/shadcn/Avatar/EXAMPLES.md similarity index 66% rename from src/Toolkit/kits/shadcn/docs/components/Avatar.md rename to src/Toolkit/kits/shadcn/Avatar/EXAMPLES.md index 022831b0ba9..32900ed7ecf 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Avatar.md +++ b/src/Toolkit/kits/shadcn/Avatar/EXAMPLES.md @@ -1,6 +1,6 @@ -# Avatar +# Examples -A circular element that displays a user's profile image or initials as a fallback. +## Avatar with Image ```twig {"preview":true} @@ -8,25 +8,7 @@ A circular element that displays a user's profile image or initials as a fallbac ``` -## Installation - - - -## Usage - - - -## Examples - -### Avatar with Image - -```twig {"preview":true} - - - -``` - -### Avatar with Text +## Avatar with Text ```twig {"preview":true}
@@ -39,7 +21,7 @@ A circular element that displays a user's profile image or initials as a fallbac
``` -### Avatar Group +## Avatar Group ```twig {"preview":true}
diff --git a/src/Toolkit/kits/shadcn/Avatar/manifest.json b/src/Toolkit/kits/shadcn/Avatar/manifest.json new file mode 100644 index 00000000000..7b3276b3b13 --- /dev/null +++ b/src/Toolkit/kits/shadcn/Avatar/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Avatar", + "description": "A circular element that displays a user's profile image or initials as a fallback.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Avatar.html.twig b/src/Toolkit/kits/shadcn/Avatar/templates/components/Avatar.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Avatar.html.twig rename to src/Toolkit/kits/shadcn/Avatar/templates/components/Avatar.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Avatar/Image.html.twig b/src/Toolkit/kits/shadcn/Avatar/templates/components/Avatar/Image.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Avatar/Image.html.twig rename to src/Toolkit/kits/shadcn/Avatar/templates/components/Avatar/Image.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Avatar/Text.html.twig b/src/Toolkit/kits/shadcn/Avatar/templates/components/Avatar/Text.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Avatar/Text.html.twig rename to src/Toolkit/kits/shadcn/Avatar/templates/components/Avatar/Text.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Badge.md b/src/Toolkit/kits/shadcn/Badge/EXAMPLES.md similarity index 58% rename from src/Toolkit/kits/shadcn/docs/components/Badge.md rename to src/Toolkit/kits/shadcn/Badge/EXAMPLES.md index 68c13efb739..87fc9ae2235 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Badge.md +++ b/src/Toolkit/kits/shadcn/Badge/EXAMPLES.md @@ -1,28 +1,12 @@ -# Badge +# Examples -A small element that displays status, counts, or labels with optional icons. +## Default ```twig {"preview":true} Badge ``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} -Badge -``` - -### Secondary +## Secondary ```twig {"preview":true} @@ -30,7 +14,7 @@ A small element that displays status, counts, or labels with optional icons. ``` -### Outline +## Outline ```twig {"preview":true} @@ -38,7 +22,7 @@ A small element that displays status, counts, or labels with optional icons. ``` -### Destructive +## Destructive ```twig {"preview":true} @@ -46,7 +30,7 @@ A small element that displays status, counts, or labels with optional icons. ``` -### With Icon +## With Icon ```twig {"preview":true} diff --git a/src/Toolkit/kits/shadcn/templates/components/Alert.meta.json b/src/Toolkit/kits/shadcn/Badge/manifest.json similarity index 55% rename from src/Toolkit/kits/shadcn/templates/components/Alert.meta.json rename to src/Toolkit/kits/shadcn/Badge/manifest.json index 9e08e59b32e..2ab4631bbed 100644 --- a/src/Toolkit/kits/shadcn/templates/components/Alert.meta.json +++ b/src/Toolkit/kits/shadcn/Badge/manifest.json @@ -1,5 +1,11 @@ { - "$schema": "../../../../schemas/component.json", + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Badge", + "description": "A small element that displays status, counts, or labels with optional icons.", + "copy-files": { + "templates/": "templates/" + }, "dependencies": [ { "type": "php", diff --git a/src/Toolkit/kits/shadcn/templates/components/Badge.html.twig b/src/Toolkit/kits/shadcn/Badge/templates/components/Badge.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Badge.html.twig rename to src/Toolkit/kits/shadcn/Badge/templates/components/Badge.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Breadcrumb.md b/src/Toolkit/kits/shadcn/Breadcrumb/EXAMPLES.md similarity index 63% rename from src/Toolkit/kits/shadcn/docs/components/Breadcrumb.md rename to src/Toolkit/kits/shadcn/Breadcrumb/EXAMPLES.md index 680e7b8558f..b42da460625 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Breadcrumb.md +++ b/src/Toolkit/kits/shadcn/Breadcrumb/EXAMPLES.md @@ -1,6 +1,6 @@ -# Breadcrumb +# Examples -A navigation element that shows the current page's location in the site hierarchy with clickable links. +## Default ```twig {"preview":true} @@ -24,41 +24,7 @@ A navigation element that shows the current page's location in the site hierarch ``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} - - - - Home - - - - Docs - - - - Components - - - - Breadcrumb - - - -``` - -### Custom Separator +## Custom Separator ```twig {"preview":true} diff --git a/src/Toolkit/kits/shadcn/Breadcrumb/manifest.json b/src/Toolkit/kits/shadcn/Breadcrumb/manifest.json new file mode 100644 index 00000000000..efe56fb0609 --- /dev/null +++ b/src/Toolkit/kits/shadcn/Breadcrumb/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Breadcrumb", + "description": "A navigation element that shows the current page's location in the site hierarchy with clickable links.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb.html.twig b/src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Breadcrumb.html.twig rename to src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Ellipsis.html.twig b/src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/Ellipsis.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Ellipsis.html.twig rename to src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/Ellipsis.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Item.html.twig b/src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/Item.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Item.html.twig rename to src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/Item.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Link.html.twig b/src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/Link.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Link.html.twig rename to src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/Link.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/List.html.twig b/src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/List.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Breadcrumb/List.html.twig rename to src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/List.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Page.html.twig b/src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/Page.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Page.html.twig rename to src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/Page.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Separator.html.twig b/src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/Separator.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Separator.html.twig rename to src/Toolkit/kits/shadcn/Breadcrumb/templates/components/Breadcrumb/Separator.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Button.md b/src/Toolkit/kits/shadcn/Button/EXAMPLES.md similarity index 70% rename from src/Toolkit/kits/shadcn/docs/components/Button.md rename to src/Toolkit/kits/shadcn/Button/EXAMPLES.md index 8dd9530b0ce..18959905a3a 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Button.md +++ b/src/Toolkit/kits/shadcn/Button/EXAMPLES.md @@ -1,6 +1,6 @@ -# Button +# Examples -A clickable element that triggers actions or events, supporting various styles and states. +## Default ```twig {"preview":true} @@ -8,61 +8,43 @@ A clickable element that triggers actions or events, supporting various styles a ``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} - - Click me - -``` - -### Primary +## Primary ```twig {"preview":true} Button ``` -### Secondary +## Secondary ```twig {"preview":true} Outline ``` -### Destructive +## Destructive ```twig {"preview":true} Destructive ``` -### Outline +## Outline ```twig {"preview":true} Outline ``` -### Ghost +## Ghost ```twig {"preview":true} Ghost ``` -### Link +## Link ```twig {"preview":true} Link ``` -### Icon +## Icon ```twig {"preview":true} @@ -70,7 +52,7 @@ A clickable element that triggers actions or events, supporting various styles a ``` -### With Icon +## With Icon ```twig {"preview":true} @@ -78,7 +60,7 @@ A clickable element that triggers actions or events, supporting various styles a ``` -### Loading +## Loading ```twig {"preview":true} diff --git a/src/Toolkit/kits/shadcn/templates/components/Badge.meta.json b/src/Toolkit/kits/shadcn/Button/manifest.json similarity index 54% rename from src/Toolkit/kits/shadcn/templates/components/Badge.meta.json rename to src/Toolkit/kits/shadcn/Button/manifest.json index 9e08e59b32e..5f01925efb3 100644 --- a/src/Toolkit/kits/shadcn/templates/components/Badge.meta.json +++ b/src/Toolkit/kits/shadcn/Button/manifest.json @@ -1,5 +1,11 @@ { - "$schema": "../../../../schemas/component.json", + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Button", + "description": "A clickable element that triggers actions or events, supporting various styles and states.", + "copy-files": { + "templates/": "templates/" + }, "dependencies": [ { "type": "php", diff --git a/src/Toolkit/kits/shadcn/templates/components/Button.html.twig b/src/Toolkit/kits/shadcn/Button/templates/components/Button.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Button.html.twig rename to src/Toolkit/kits/shadcn/Button/templates/components/Button.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Card.md b/src/Toolkit/kits/shadcn/Card/EXAMPLES.md similarity index 70% rename from src/Toolkit/kits/shadcn/docs/components/Card.md rename to src/Toolkit/kits/shadcn/Card/EXAMPLES.md index f482b75f2e4..b20e3291657 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Card.md +++ b/src/Toolkit/kits/shadcn/Card/EXAMPLES.md @@ -1,6 +1,6 @@ -# Card +# Examples -A container that groups related content and actions into a box with optional header, content, and footer sections. +## Default ```twig {"preview":true,"height":"300px"} @@ -18,35 +18,7 @@ A container that groups related content and actions into a box with optional hea ``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true,"height":"300px"} - - - Card Title - Card Description - - -

Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.

-
- - Cancel - Action - -
-``` - -### With Notifications +## With Notifications ```twig {"preview":true,"height":"400px"} {% set notifications = [ diff --git a/src/Toolkit/kits/shadcn/Card/manifest.json b/src/Toolkit/kits/shadcn/Card/manifest.json new file mode 100644 index 00000000000..6de056e54f2 --- /dev/null +++ b/src/Toolkit/kits/shadcn/Card/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Card", + "description": "A container that groups related content and actions into a box with optional header, content, and footer sections.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Card.html.twig b/src/Toolkit/kits/shadcn/Card/templates/components/Card.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Card.html.twig rename to src/Toolkit/kits/shadcn/Card/templates/components/Card.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Card/Content.html.twig b/src/Toolkit/kits/shadcn/Card/templates/components/Card/Content.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Card/Content.html.twig rename to src/Toolkit/kits/shadcn/Card/templates/components/Card/Content.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Card/Description.html.twig b/src/Toolkit/kits/shadcn/Card/templates/components/Card/Description.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Card/Description.html.twig rename to src/Toolkit/kits/shadcn/Card/templates/components/Card/Description.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Card/Footer.html.twig b/src/Toolkit/kits/shadcn/Card/templates/components/Card/Footer.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Card/Footer.html.twig rename to src/Toolkit/kits/shadcn/Card/templates/components/Card/Footer.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Card/Header.html.twig b/src/Toolkit/kits/shadcn/Card/templates/components/Card/Header.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Card/Header.html.twig rename to src/Toolkit/kits/shadcn/Card/templates/components/Card/Header.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Card/Title.html.twig b/src/Toolkit/kits/shadcn/Card/templates/components/Card/Title.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Card/Title.html.twig rename to src/Toolkit/kits/shadcn/Card/templates/components/Card/Title.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Checkbox.md b/src/Toolkit/kits/shadcn/Checkbox/EXAMPLES.md similarity index 55% rename from src/Toolkit/kits/shadcn/docs/components/Checkbox.md rename to src/Toolkit/kits/shadcn/Checkbox/EXAMPLES.md index 40da5fee4d0..a1694a6b54e 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Checkbox.md +++ b/src/Toolkit/kits/shadcn/Checkbox/EXAMPLES.md @@ -1,6 +1,6 @@ -# Checkbox +# Examples -A form control that allows the user to toggle between checked and unchecked states. +## Default ```twig {"preview":true}
@@ -11,28 +11,7 @@ A form control that allows the user to toggle between checked and unchecked stat
``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} -
- - -
-``` - -### With Label Component +## With Label Component ```twig {"preview":true}
@@ -41,7 +20,7 @@ A form control that allows the user to toggle between checked and unchecked stat
``` -### Disabled +## Disabled ```twig {"preview":true}
diff --git a/src/Toolkit/kits/shadcn/Checkbox/manifest.json b/src/Toolkit/kits/shadcn/Checkbox/manifest.json new file mode 100644 index 00000000000..97f8c3443da --- /dev/null +++ b/src/Toolkit/kits/shadcn/Checkbox/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Checkbox", + "description": "A form control that allows the user to toggle between checked and unchecked states.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Checkbox.html.twig b/src/Toolkit/kits/shadcn/Checkbox/templates/components/Checkbox.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Checkbox.html.twig rename to src/Toolkit/kits/shadcn/Checkbox/templates/components/Checkbox.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Input.md b/src/Toolkit/kits/shadcn/Input/EXAMPLES.md similarity index 68% rename from src/Toolkit/kits/shadcn/docs/components/Input.md rename to src/Toolkit/kits/shadcn/Input/EXAMPLES.md index de0b816a9d6..7e7d65b676e 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Input.md +++ b/src/Toolkit/kits/shadcn/Input/EXAMPLES.md @@ -1,28 +1,12 @@ -# Input +# Examples -A form control that allows users to enter text, numbers, or select files. +## Default ```twig {"preview":true} ``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} - -``` - -### File +## File ```twig {"preview":true}
@@ -31,13 +15,13 @@ A form control that allows users to enter text, numbers, or select files.
``` -### Disabled +## Disabled ```twig {"preview":true} ``` -### With Label +## With Label ```twig {"preview":true}
@@ -46,7 +30,7 @@ A form control that allows users to enter text, numbers, or select files.
``` -### With Button +## With Button ```twig {"preview":true}
diff --git a/src/Toolkit/kits/shadcn/Input/manifest.json b/src/Toolkit/kits/shadcn/Input/manifest.json new file mode 100644 index 00000000000..5c778e58dcf --- /dev/null +++ b/src/Toolkit/kits/shadcn/Input/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Input", + "description": "A form control that allows users to enter text, numbers, or select files.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Input.html.twig b/src/Toolkit/kits/shadcn/Input/templates/components/Input.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Input.html.twig rename to src/Toolkit/kits/shadcn/Input/templates/components/Input.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Label.md b/src/Toolkit/kits/shadcn/Label/EXAMPLES.md similarity index 62% rename from src/Toolkit/kits/shadcn/docs/components/Label.md rename to src/Toolkit/kits/shadcn/Label/EXAMPLES.md index ea41f854987..8fd5b0fa634 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Label.md +++ b/src/Toolkit/kits/shadcn/Label/EXAMPLES.md @@ -1,6 +1,6 @@ -# Label +# Examples -A text element that identifies form controls and other content. +## Default ```twig {"preview":true}
@@ -9,26 +9,7 @@ A text element that identifies form controls and other content.
``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} -
- - Accept terms and conditions -
-``` - -### With Input +## With Input ```twig {"preview":true}
@@ -37,7 +18,7 @@ A text element that identifies form controls and other content.
``` -### Required Field +## Required Field ```twig {"preview":true}
diff --git a/src/Toolkit/kits/shadcn/Label/manifest.json b/src/Toolkit/kits/shadcn/Label/manifest.json new file mode 100644 index 00000000000..a52d0baeea1 --- /dev/null +++ b/src/Toolkit/kits/shadcn/Label/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Label", + "description": "A text element that identifies form controls and other content.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Label.html.twig b/src/Toolkit/kits/shadcn/Label/templates/components/Label.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Label.html.twig rename to src/Toolkit/kits/shadcn/Label/templates/components/Label.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Pagination.md b/src/Toolkit/kits/shadcn/Pagination/EXAMPLES.md similarity index 65% rename from src/Toolkit/kits/shadcn/docs/components/Pagination.md rename to src/Toolkit/kits/shadcn/Pagination/EXAMPLES.md index 0efbea5159f..cb2032c738c 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Pagination.md +++ b/src/Toolkit/kits/shadcn/Pagination/EXAMPLES.md @@ -1,6 +1,6 @@ -# Pagination +# Examples -A navigation component that displays page numbers and controls for moving between pages. +## Default ```twig {"preview":true} @@ -27,44 +27,7 @@ A navigation component that displays page numbers and controls for moving betwee ``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} - - - - - - - 1 - - - 2 - - - 3 - - - - - - - - - -``` - -### Symmetric +## Symmetric ```twig {"preview":true} diff --git a/src/Toolkit/kits/shadcn/Pagination/manifest.json b/src/Toolkit/kits/shadcn/Pagination/manifest.json new file mode 100644 index 00000000000..5a456dc3b93 --- /dev/null +++ b/src/Toolkit/kits/shadcn/Pagination/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Pagination", + "description": "A navigation component that displays page numbers and controls for moving between pages.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination.html.twig b/src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Pagination.html.twig rename to src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Content.html.twig b/src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Content.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Pagination/Content.html.twig rename to src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Content.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Ellipsis.html.twig b/src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Ellipsis.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Pagination/Ellipsis.html.twig rename to src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Ellipsis.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Item.html.twig b/src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Item.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Pagination/Item.html.twig rename to src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Item.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Link.html.twig b/src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Link.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Pagination/Link.html.twig rename to src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Link.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Next.html.twig b/src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Next.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Pagination/Next.html.twig rename to src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Next.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Previous.html.twig b/src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Previous.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Pagination/Previous.html.twig rename to src/Toolkit/kits/shadcn/Pagination/templates/components/Pagination/Previous.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Progress.md b/src/Toolkit/kits/shadcn/Progress/EXAMPLES.md similarity index 65% rename from src/Toolkit/kits/shadcn/docs/components/Progress.md rename to src/Toolkit/kits/shadcn/Progress/EXAMPLES.md index c2c89234a73..077c9c45a3b 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Progress.md +++ b/src/Toolkit/kits/shadcn/Progress/EXAMPLES.md @@ -1,28 +1,12 @@ -# Progress +# Examples -A visual indicator that shows the completion status of a task or operation. +## Default ```twig {"preview":true} ``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} - -``` - -### With Label +## With Label ```twig {"preview":true}
@@ -34,7 +18,7 @@ A visual indicator that shows the completion status of a task or operation.
``` -### Different Values +## Different Values ```twig {"preview":true}
diff --git a/src/Toolkit/kits/shadcn/Progress/manifest.json b/src/Toolkit/kits/shadcn/Progress/manifest.json new file mode 100644 index 00000000000..eff5fcf874c --- /dev/null +++ b/src/Toolkit/kits/shadcn/Progress/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Progress", + "description": "A visual indicator that shows the completion status of a task or operation.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Progress.html.twig b/src/Toolkit/kits/shadcn/Progress/templates/components/Progress.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Progress.html.twig rename to src/Toolkit/kits/shadcn/Progress/templates/components/Progress.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Select.md b/src/Toolkit/kits/shadcn/Select/EXAMPLES.md similarity index 63% rename from src/Toolkit/kits/shadcn/docs/components/Select.md rename to src/Toolkit/kits/shadcn/Select/EXAMPLES.md index 91941c0fafe..1548d4e11cd 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Select.md +++ b/src/Toolkit/kits/shadcn/Select/EXAMPLES.md @@ -1,6 +1,6 @@ -# Select +# Examples -A dropdown control that allows users to choose from a list of options. +## Default ```twig {"preview":true} @@ -10,27 +10,7 @@ A dropdown control that allows users to choose from a list of options. ``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} - - - - - -``` - -### With Label +## With Label ```twig {"preview":true}
@@ -43,7 +23,7 @@ A dropdown control that allows users to choose from a list of options.
``` -### Disabled +## Disabled ```twig {"preview":true} diff --git a/src/Toolkit/kits/shadcn/Select/manifest.json b/src/Toolkit/kits/shadcn/Select/manifest.json new file mode 100644 index 00000000000..66e2a60ff79 --- /dev/null +++ b/src/Toolkit/kits/shadcn/Select/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Select", + "description": "A dropdown control that allows users to choose from a list of options.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Select.html.twig b/src/Toolkit/kits/shadcn/Select/templates/components/Select.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Select.html.twig rename to src/Toolkit/kits/shadcn/Select/templates/components/Select.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Separator.md b/src/Toolkit/kits/shadcn/Separator/EXAMPLES.md similarity index 53% rename from src/Toolkit/kits/shadcn/docs/components/Separator.md rename to src/Toolkit/kits/shadcn/Separator/EXAMPLES.md index 038197b45df..ecc16b66b15 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Separator.md +++ b/src/Toolkit/kits/shadcn/Separator/EXAMPLES.md @@ -1,9 +1,9 @@ -# Separator +# Examples -A visual divider that creates space between content elements, available in horizontal and vertical orientations. +## Default ```twig {"preview":true} -
+

Symfony UX

@@ -21,38 +21,7 @@ A visual divider that creates space between content elements, available in horiz

``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} -
-
-

Symfony UX

-

- Symfony UX initiative: a JavaScript ecosystem for Symfony -

-
- -
-
Blog
- -
Docs
- -
Source
-
-
-``` - -### Vertical +## Vertical ```twig {"preview":true}
diff --git a/src/Toolkit/kits/shadcn/templates/components/Button.meta.json b/src/Toolkit/kits/shadcn/Separator/manifest.json similarity index 52% rename from src/Toolkit/kits/shadcn/templates/components/Button.meta.json rename to src/Toolkit/kits/shadcn/Separator/manifest.json index 9e08e59b32e..6dd248fd1b1 100644 --- a/src/Toolkit/kits/shadcn/templates/components/Button.meta.json +++ b/src/Toolkit/kits/shadcn/Separator/manifest.json @@ -1,5 +1,11 @@ { - "$schema": "../../../../schemas/component.json", + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Separator", + "description": "A visual divider that creates space between content elements, available in horizontal and vertical orientations.", + "copy-files": { + "templates/": "templates/" + }, "dependencies": [ { "type": "php", diff --git a/src/Toolkit/kits/shadcn/templates/components/Separator.html.twig b/src/Toolkit/kits/shadcn/Separator/templates/components/Separator.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Separator.html.twig rename to src/Toolkit/kits/shadcn/Separator/templates/components/Separator.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Skeleton.md b/src/Toolkit/kits/shadcn/Skeleton/EXAMPLES.md similarity index 52% rename from src/Toolkit/kits/shadcn/docs/components/Skeleton.md rename to src/Toolkit/kits/shadcn/Skeleton/EXAMPLES.md index 4c16a2c64bc..219e712f765 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Skeleton.md +++ b/src/Toolkit/kits/shadcn/Skeleton/EXAMPLES.md @@ -1,6 +1,6 @@ -# Skeleton +# Examples -A placeholder element that displays a loading state with an animated background. +## User ```twig {"preview":true}
@@ -12,29 +12,7 @@ A placeholder element that displays a loading state with an animated background.
``` -## Installation - - - -## Usage - - - -## Examples - -### User - -```twig {"preview":true} -
- -
- - -
-
-``` - -### Card +## Card ```twig {"preview":true,"height":"250px"}
diff --git a/src/Toolkit/kits/shadcn/Skeleton/manifest.json b/src/Toolkit/kits/shadcn/Skeleton/manifest.json new file mode 100644 index 00000000000..b1ac224d6f4 --- /dev/null +++ b/src/Toolkit/kits/shadcn/Skeleton/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Skeleton", + "description": "A placeholder element that displays a loading state with an animated background.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Skeleton.html.twig b/src/Toolkit/kits/shadcn/Skeleton/templates/components/Skeleton.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Skeleton.html.twig rename to src/Toolkit/kits/shadcn/Skeleton/templates/components/Skeleton.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Switch.md b/src/Toolkit/kits/shadcn/Switch/EXAMPLES.md similarity index 76% rename from src/Toolkit/kits/shadcn/docs/components/Switch.md rename to src/Toolkit/kits/shadcn/Switch/EXAMPLES.md index 17fa899f49f..2de3e723739 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Switch.md +++ b/src/Toolkit/kits/shadcn/Switch/EXAMPLES.md @@ -1,6 +1,6 @@ -# Switch +# Examples -A toggle control that switches between on and off states. +## Default ```twig {"preview":true}
@@ -9,26 +9,7 @@ A toggle control that switches between on and off states.
``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} -
- - Airplane Mode -
-``` - -### Form +## Form ```twig {"preview":true,"height":"300px"}
diff --git a/src/Toolkit/kits/shadcn/Switch/manifest.json b/src/Toolkit/kits/shadcn/Switch/manifest.json new file mode 100644 index 00000000000..83fb86f9f2f --- /dev/null +++ b/src/Toolkit/kits/shadcn/Switch/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Switch", + "description": "A toggle control that switches between on and off states.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Switch.html.twig b/src/Toolkit/kits/shadcn/Switch/templates/components/Switch.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Switch.html.twig rename to src/Toolkit/kits/shadcn/Switch/templates/components/Switch.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Table.md b/src/Toolkit/kits/shadcn/Table/EXAMPLES.md similarity index 55% rename from src/Toolkit/kits/shadcn/docs/components/Table.md rename to src/Toolkit/kits/shadcn/Table/EXAMPLES.md index 302d6bc91e2..2ca30788e80 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Table.md +++ b/src/Toolkit/kits/shadcn/Table/EXAMPLES.md @@ -1,47 +1,6 @@ -# Table +# Examples -A structured grid element that organizes data into rows and columns, supporting headers, captions, and footers. - -```twig {"preview":true,"height":"400px"} -{%- set invoices = [ - { invoice: "INV001", paymentStatus: "Paid", totalAmount: "$250.00", paymentMethod: "Credit Card" }, - { invoice: "INV002", paymentStatus: "Pending", totalAmount: "$150.00", paymentMethod: "PayPal" }, - { invoice: "INV003", paymentStatus: "Unpaid", totalAmount: "$350.00", paymentMethod: "Bank Transfer" }, -] -%} - - A list of your recent invoices. - - - Invoice - Status - Method - Amount - - - - {% for invoice in invoices %} - - {{ invoice.invoice }} - {{ invoice.paymentStatus }} - {{ invoice.paymentMethod }} - {{ invoice.totalAmount }} - - {% endfor %} - - -``` - -## Installation - - - -## Usage - - - -## Examples - -### Basic Table +## Basic Table ```twig {"preview":true,"height":"550px"} {%- set invoices = [ diff --git a/src/Toolkit/kits/shadcn/Table/manifest.json b/src/Toolkit/kits/shadcn/Table/manifest.json new file mode 100644 index 00000000000..a4d3c86b851 --- /dev/null +++ b/src/Toolkit/kits/shadcn/Table/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Table", + "description": "A structured grid element that organizes data into rows and columns, supporting headers, captions, and footers.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Table.html.twig b/src/Toolkit/kits/shadcn/Table/templates/components/Table.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Table.html.twig rename to src/Toolkit/kits/shadcn/Table/templates/components/Table.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Body.html.twig b/src/Toolkit/kits/shadcn/Table/templates/components/Table/Body.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Table/Body.html.twig rename to src/Toolkit/kits/shadcn/Table/templates/components/Table/Body.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Caption.html.twig b/src/Toolkit/kits/shadcn/Table/templates/components/Table/Caption.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Table/Caption.html.twig rename to src/Toolkit/kits/shadcn/Table/templates/components/Table/Caption.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Cell.html.twig b/src/Toolkit/kits/shadcn/Table/templates/components/Table/Cell.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Table/Cell.html.twig rename to src/Toolkit/kits/shadcn/Table/templates/components/Table/Cell.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Footer.html.twig b/src/Toolkit/kits/shadcn/Table/templates/components/Table/Footer.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Table/Footer.html.twig rename to src/Toolkit/kits/shadcn/Table/templates/components/Table/Footer.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Head.html.twig b/src/Toolkit/kits/shadcn/Table/templates/components/Table/Head.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Table/Head.html.twig rename to src/Toolkit/kits/shadcn/Table/templates/components/Table/Head.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Header.html.twig b/src/Toolkit/kits/shadcn/Table/templates/components/Table/Header.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Table/Header.html.twig rename to src/Toolkit/kits/shadcn/Table/templates/components/Table/Header.html.twig diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Row.html.twig b/src/Toolkit/kits/shadcn/Table/templates/components/Table/Row.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Table/Row.html.twig rename to src/Toolkit/kits/shadcn/Table/templates/components/Table/Row.html.twig diff --git a/src/Toolkit/kits/shadcn/docs/components/Textarea.md b/src/Toolkit/kits/shadcn/Textarea/EXAMPLES.md similarity index 56% rename from src/Toolkit/kits/shadcn/docs/components/Textarea.md rename to src/Toolkit/kits/shadcn/Textarea/EXAMPLES.md index b9babc362c9..fb343e533f0 100644 --- a/src/Toolkit/kits/shadcn/docs/components/Textarea.md +++ b/src/Toolkit/kits/shadcn/Textarea/EXAMPLES.md @@ -1,28 +1,12 @@ -# Textarea +# Examples -A form control for entering multiple lines of text. +## Default ```twig {"preview":true} ``` -## Installation - - - -## Usage - - - -## Examples - -### Default - -```twig {"preview":true} - -``` - -### With Label +## With Label ```twig {"preview":true}
@@ -31,7 +15,7 @@ A form control for entering multiple lines of text.
``` -### Disabled +## Disabled ```twig {"preview":true} diff --git a/src/Toolkit/kits/shadcn/Textarea/manifest.json b/src/Toolkit/kits/shadcn/Textarea/manifest.json new file mode 100644 index 00000000000..37cf9414e23 --- /dev/null +++ b/src/Toolkit/kits/shadcn/Textarea/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "Textarea", + "description": "A form control for entering multiple lines of text.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] +} diff --git a/src/Toolkit/kits/shadcn/templates/components/Textarea.html.twig b/src/Toolkit/kits/shadcn/Textarea/templates/components/Textarea.html.twig similarity index 100% rename from src/Toolkit/kits/shadcn/templates/components/Textarea.html.twig rename to src/Toolkit/kits/shadcn/Textarea/templates/components/Textarea.html.twig diff --git a/src/Toolkit/kits/shadcn/manifest.json b/src/Toolkit/kits/shadcn/manifest.json index 467d20a2786..03571bdbcc3 100644 --- a/src/Toolkit/kits/shadcn/manifest.json +++ b/src/Toolkit/kits/shadcn/manifest.json @@ -1,4 +1,5 @@ { + "$schema": "../../schema-kit-v1.json", "name": "Shadcn UI", "description": "Component based on the Shadcn UI library, one of the most popular design systems in JavaScript world.", "license": "MIT", diff --git a/src/Toolkit/kits/shadcn/templates/components/Alert/Description.meta.json b/src/Toolkit/kits/shadcn/templates/components/Alert/Description.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Alert/Description.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Alert/Title.meta.json b/src/Toolkit/kits/shadcn/templates/components/Alert/Title.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Alert/Title.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/AspectRatio.meta.json b/src/Toolkit/kits/shadcn/templates/components/AspectRatio.meta.json deleted file mode 100644 index 3c7c094fde0..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/AspectRatio.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "twig/extra-bundle" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Avatar.meta.json b/src/Toolkit/kits/shadcn/templates/components/Avatar.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Avatar.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Avatar/Image.meta.json b/src/Toolkit/kits/shadcn/templates/components/Avatar/Image.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Avatar/Image.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Avatar/Text.meta.json b/src/Toolkit/kits/shadcn/templates/components/Avatar/Text.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Avatar/Text.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb.meta.json b/src/Toolkit/kits/shadcn/templates/components/Breadcrumb.meta.json deleted file mode 100644 index 079eea5bb20..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb.meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Ellipsis.meta.json b/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Ellipsis.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Ellipsis.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Item.meta.json b/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Item.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Item.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Link.meta.json b/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Link.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Link.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/List.meta.json b/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/List.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/List.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Page.meta.json b/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Page.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Page.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Separator.meta.json b/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Separator.meta.json deleted file mode 100644 index 19987b2a1b8..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Breadcrumb/Separator.meta.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "symfony/ux-icons" - }, - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Card.meta.json b/src/Toolkit/kits/shadcn/templates/components/Card.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Card.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Card/Content.meta.json b/src/Toolkit/kits/shadcn/templates/components/Card/Content.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Card/Content.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Card/Description.meta.json b/src/Toolkit/kits/shadcn/templates/components/Card/Description.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Card/Description.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Card/Footer.meta.json b/src/Toolkit/kits/shadcn/templates/components/Card/Footer.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Card/Footer.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Card/Header.meta.json b/src/Toolkit/kits/shadcn/templates/components/Card/Header.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Card/Header.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Card/Title.meta.json b/src/Toolkit/kits/shadcn/templates/components/Card/Title.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Card/Title.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Checkbox.meta.json b/src/Toolkit/kits/shadcn/templates/components/Checkbox.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Checkbox.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Input.meta.json b/src/Toolkit/kits/shadcn/templates/components/Input.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Input.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Label.meta.json b/src/Toolkit/kits/shadcn/templates/components/Label.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Label.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination.meta.json b/src/Toolkit/kits/shadcn/templates/components/Pagination.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Pagination.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Content.meta.json b/src/Toolkit/kits/shadcn/templates/components/Pagination/Content.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Pagination/Content.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Ellipsis.meta.json b/src/Toolkit/kits/shadcn/templates/components/Pagination/Ellipsis.meta.json deleted file mode 100644 index 19987b2a1b8..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Pagination/Ellipsis.meta.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "symfony/ux-icons" - }, - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Item.meta.json b/src/Toolkit/kits/shadcn/templates/components/Pagination/Item.meta.json deleted file mode 100644 index d39de6e07e6..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Pagination/Item.meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Link.meta.json b/src/Toolkit/kits/shadcn/templates/components/Pagination/Link.meta.json deleted file mode 100644 index d39de6e07e6..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Pagination/Link.meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Next.meta.json b/src/Toolkit/kits/shadcn/templates/components/Pagination/Next.meta.json deleted file mode 100644 index 19987b2a1b8..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Pagination/Next.meta.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "symfony/ux-icons" - }, - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Pagination/Previous.meta.json b/src/Toolkit/kits/shadcn/templates/components/Pagination/Previous.meta.json deleted file mode 100644 index 19987b2a1b8..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Pagination/Previous.meta.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "symfony/ux-icons" - }, - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Progress.meta.json b/src/Toolkit/kits/shadcn/templates/components/Progress.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Progress.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Select.meta.json b/src/Toolkit/kits/shadcn/templates/components/Select.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Select.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Skeleton.meta.json b/src/Toolkit/kits/shadcn/templates/components/Skeleton.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Skeleton.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Switch.meta.json b/src/Toolkit/kits/shadcn/templates/components/Switch.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Switch.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Table.meta.json b/src/Toolkit/kits/shadcn/templates/components/Table.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Table.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Body.meta.json b/src/Toolkit/kits/shadcn/templates/components/Table/Body.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Table/Body.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Caption.meta.json b/src/Toolkit/kits/shadcn/templates/components/Table/Caption.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Table/Caption.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Cell.meta.json b/src/Toolkit/kits/shadcn/templates/components/Table/Cell.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Table/Cell.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Footer.meta.json b/src/Toolkit/kits/shadcn/templates/components/Table/Footer.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Table/Footer.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Head.meta.json b/src/Toolkit/kits/shadcn/templates/components/Table/Head.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Table/Head.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Header.meta.json b/src/Toolkit/kits/shadcn/templates/components/Table/Header.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Table/Header.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Table/Row.meta.json b/src/Toolkit/kits/shadcn/templates/components/Table/Row.meta.json deleted file mode 100644 index 99e25114927..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Table/Row.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/kits/shadcn/templates/components/Textarea.meta.json b/src/Toolkit/kits/shadcn/templates/components/Textarea.meta.json deleted file mode 100644 index d50410b06b3..00000000000 --- a/src/Toolkit/kits/shadcn/templates/components/Textarea.meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "../../../../schemas/component.json", - "dependencies": [ - { - "type": "php", - "package": "tales-from-a-dev/twig-tailwind-extra" - } - ] -} diff --git a/src/Toolkit/schema-kit-recipe-v1.json b/src/Toolkit/schema-kit-recipe-v1.json new file mode 100644 index 00000000000..039d546f4cb --- /dev/null +++ b/src/Toolkit/schema-kit-recipe-v1.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Kit recipe schema", + "description": "Schema for describing a Kit recipe for the Symfony UX Toolkit.", + "type": "object", + "required": ["name", "description"], + "properties": { + "type": { + "type": "string", + "enum": ["component"] + }, + "name": { + "type": "string", + "description": "The name of the component" + }, + "description": { + "description": "A brief description of the component", + "type": "string" + }, + "copy-files": { + "description": "Relative files or directories to copy into the project", + "type": "object" + }, + "examples-dir": { + "description": "Relative path to the directory containing example files for the component, must end with a slash", + "type": "string", + "pattern": "^.+/$" + }, + "dependencies": { + "description": "List of dependencies required by the component", + "type": "array", + "items": { + "oneOf": [ + { + "description": "A dependency on a PHP package", + "type": "object", + "required": ["type", "package"], + "properties": { + "type": { + "type": "string", + "enum": ["php"] + }, + "package": { + "type": "string", + "description": "Package name and optional version constraint (e.g., 'vendor/package:^1.0')" + } + } + }, + { + "description": "A dependency on a Recipe", + "type": "object", + "required": ["type", "name"], + "properties": { + "type": { + "type": "string", + "enum": ["recipe"] + }, + "name": { + "type": "string", + "description": "The name of the Recipe this component depends on, e.g., 'Button'" + } + } + } + ] + } + } + } +} diff --git a/src/Toolkit/schema-kit-v1.json b/src/Toolkit/schema-kit-v1.json new file mode 100644 index 00000000000..beb912556d9 --- /dev/null +++ b/src/Toolkit/schema-kit-v1.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Kit schema", + "description": "Schema for describing a Kit for the Symfony UX Toolkit.", + "type": "object", + "required": ["name", "description", "license", "homepage"], + "properties": { + "name": { + "type": "string", + "description": "The name of the kit." + }, + "description": { + "type": "string", + "description": "A brief description of the kit." + }, + "license": { + "type": "string", + "description": "The license under which the kit is distributed." + }, + "homepage": { + "type": "string", + "format": "uri", + "description": "The homepage URL for the kit, typically where users can find more information or documentation." + } + } +} diff --git a/src/Toolkit/schemas/component.json b/src/Toolkit/schemas/component.json deleted file mode 100644 index 85749807f22..00000000000 --- a/src/Toolkit/schemas/component.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Component Meta Schema", - "type": "object", - "required": ["dependencies"], - "properties": { - "dependencies": { - "type": "array", - "description": "List of dependencies required by the component", - "items": { - "oneOf": [ - { - "type": "object", - "required": ["type", "package"], - "properties": { - "type": { - "type": "string", - "enum": ["php"], - "description": "PHP package dependency" - }, - "package": { - "type": "string", - "description": "Package name and optional version constraint" - } - } - } - ] - } - } - } -} diff --git a/src/Toolkit/src/Assert.php b/src/Toolkit/src/Assert.php index 70dcb33cc4d..7bcebdcc6f0 100644 --- a/src/Toolkit/src/Assert.php +++ b/src/Toolkit/src/Assert.php @@ -55,11 +55,4 @@ public static function phpPackageName(string $name): void throw new \InvalidArgumentException(\sprintf('Invalid PHP package name "%s".', $name)); } } - - public static function stimulusControllerName(string $name): void - { - if (1 !== preg_match('/^[a-z][a-z0-9-]*[a-z0-9]$/', $name)) { - throw new \InvalidArgumentException(\sprintf('Invalid Stimulus controller name "%s".', $name)); - } - } } diff --git a/src/Toolkit/src/Asset/Component.php b/src/Toolkit/src/Asset/Component.php deleted file mode 100644 index 8e6ee0868af..00000000000 --- a/src/Toolkit/src/Asset/Component.php +++ /dev/null @@ -1,95 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\Asset; - -use Symfony\UX\Toolkit\Assert; -use Symfony\UX\Toolkit\Dependency\ComponentDependency; -use Symfony\UX\Toolkit\Dependency\DependencyInterface; -use Symfony\UX\Toolkit\Dependency\PhpPackageDependency; -use Symfony\UX\Toolkit\Dependency\StimulusControllerDependency; -use Symfony\UX\Toolkit\File\ComponentMeta; -use Symfony\UX\Toolkit\File\Doc; -use Symfony\UX\Toolkit\File\File; - -/** - * @internal - * - * @author Hugo Alliaume - */ -final class Component -{ - /** - * @param non-empty-string $name - * @param list $files - */ - public function __construct( - public readonly string $name, - public readonly array $files, - public ?Doc $doc = null, - public ?ComponentMeta $meta = null, - private array $dependencies = [], - ) { - Assert::componentName($name); - - if ([] === $files) { - throw new \InvalidArgumentException(\sprintf('The component "%s" must have at least one file.', $name)); - } - - foreach ($this->meta?->dependencies ?? [] as $dependency) { - $this->addDependency($dependency); - } - } - - public function addDependency(DependencyInterface $dependency): void - { - foreach ($this->dependencies as $i => $existingDependency) { - if ($existingDependency instanceof PhpPackageDependency && $existingDependency->name === $dependency->name) { - if ($existingDependency->isHigherThan($dependency)) { - return; - } - - $this->dependencies[$i] = $dependency; - - return; - } - - if ($existingDependency instanceof ComponentDependency && $existingDependency->name === $dependency->name) { - return; - } - - if ($existingDependency instanceof StimulusControllerDependency && $existingDependency->name === $dependency->name) { - return; - } - } - - $this->dependencies[] = $dependency; - } - - /** - * @return list - */ - public function getDependencies(): array - { - return $this->dependencies; - } - - public function hasDependency(DependencyInterface $dependency): bool - { - foreach ($this->dependencies as $existingDependency) { - if ($existingDependency->isEquivalentTo($dependency)) { - return true; - } - } - - return false; - } -} diff --git a/src/Toolkit/src/Asset/StimulusController.php b/src/Toolkit/src/Asset/StimulusController.php deleted file mode 100644 index 94db7e19a89..00000000000 --- a/src/Toolkit/src/Asset/StimulusController.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\Asset; - -use Symfony\UX\Toolkit\Assert; -use Symfony\UX\Toolkit\File\File; - -/** - * @internal - * - * @author Hugo Alliaume - */ -class StimulusController -{ - /** - * @param non-empty-string $name - * @param list $files - */ - public function __construct( - public readonly string $name, - public readonly array $files, - ) { - Assert::stimulusControllerName($this->name); - - if ([] === $files) { - throw new \InvalidArgumentException(\sprintf('Stimulus controller "%s" has no files.', $name)); - } - } -} diff --git a/src/Toolkit/src/Command/CreateKitCommand.php b/src/Toolkit/src/Command/CreateKitCommand.php index 1ab720be848..e9b51460d47 100644 --- a/src/Toolkit/src/Command/CreateKitCommand.php +++ b/src/Toolkit/src/Command/CreateKitCommand.php @@ -78,11 +78,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int // Create the kit $this->filesystem->dumpFile('manifest.json', json_encode([ + '$schema' => '../vendor/symfony/ux-toolkit/schema-kit-v1.json', 'name' => $kitName, + 'description' => 'A custom kit for Symfony UX Toolkit.', 'homepage' => $kitHomepage, 'license' => $kitLicense, ], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); - $this->filesystem->dumpFile('templates/components/Button.html.twig', <<filesystem->dumpFile( + 'Button/templates/components/Button.html.twig', + << TWIG ); - $this->filesystem->dumpFile('docs/components/Button.md', << - Click me - -``` - -## Examples - -### Button with Variants - -```twig -Default -Secondary -``` - -MARKDOWN - ); - $this->filesystem->dumpFile('docs/components/Button.meta.json', json_encode([ - '$schema' => '../vendor/symfony/ux-toolkit/schemas/component.schema.json', - 'dependencies' => (object) [], + $this->filesystem->dumpFile('Button/manifest.json', json_encode([ + '$schema' => '../vendor/symfony/ux-toolkit/schema-kit-recipe-v1.json', + 'name' => 'Button', + 'description' => 'A clickable element that triggers actions or events, supporting various styles and states.', + 'copy-files' => [ + 'templates/' => 'templates/', + ], + 'dependencies' => [ + ['type' => 'php', 'package' => 'twig/extra-bundle'], + ['type' => 'php', 'package' => 'twig/html-extra:^3.12.0'], + ['type' => 'php', 'package' => 'tales-from-a-dev/twig-tailwind-extra'], + ], ], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); - $io->success('Your kit has been scaffolded, enjoy!'); + $io->success('Your kit has been created successfully, happy coding!'); return self::SUCCESS; } diff --git a/src/Toolkit/src/Command/DebugKitCommand.php b/src/Toolkit/src/Command/DebugKitCommand.php index 6a79cb84d71..423403bfd95 100644 --- a/src/Toolkit/src/Command/DebugKitCommand.php +++ b/src/Toolkit/src/Command/DebugKitCommand.php @@ -45,7 +45,8 @@ protected function configure(): void { $this ->addArgument('kit-path', InputArgument::OPTIONAL, 'The path to the kit to debug', '.') - ->setHelp(<<<'EOF' + ->setHelp( + <<<'EOF' To debug a Kit in the current directory: php %command.full_name% @@ -64,28 +65,28 @@ protected function execute(InputInterface $input, OutputInterface $output): int $kitPath = Path::makeAbsolute($kitPath, getcwd()); $kit = $this->kitFactory->createKitFromAbsolutePath($kitPath); - $io->title(\sprintf('Kit "%s"', $kit->name)); + $io->title(\sprintf('Kit "%s"', $kit->manifest->name)); $io->definitionList( - ['Name' => $kit->name], - ['Homepage' => $kit->homepage], - ['License' => $kit->license], + ['Name' => $kit->manifest->name], + ['Homepage' => $kit->manifest->homepage], + ['License' => $kit->manifest->license], new TableSeparator(), - ['Path' => $kit->path], + ['Path' => $kit->absolutePath], ); - $io->section('Components'); - foreach ($kit->getComponents() as $component) { + $io->section('Recipes'); + foreach ($kit->getRecipes() as $recipe) { (new Table($io)) - ->setHeaderTitle(\sprintf('Component: "%s"', $component->name)) + ->setHeaderTitle(\sprintf('Recipe: "%s"', $recipe->manifest->name)) ->setHorizontal() ->setHeaders([ 'File(s)', 'Dependencies', ]) ->addRow([ - implode("\n", $component->files), - implode("\n", $component->getDependencies()), + implode("\n", iterator_to_array($recipe->getFiles())), + implode("\n", $recipe->manifest->dependencies), ]) ->setColumnWidth(1, 80) ->setColumnMaxWidth(1, 80) diff --git a/src/Toolkit/src/Command/InstallComponentCommand.php b/src/Toolkit/src/Command/InstallCommand.php similarity index 53% rename from src/Toolkit/src/Command/InstallComponentCommand.php rename to src/Toolkit/src/Command/InstallCommand.php index 6746a6d5272..5fd88dde172 100644 --- a/src/Toolkit/src/Command/InstallComponentCommand.php +++ b/src/Toolkit/src/Command/InstallCommand.php @@ -20,10 +20,10 @@ use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Path; -use Symfony\UX\Toolkit\Asset\Component; -use Symfony\UX\Toolkit\File\File; +use Symfony\UX\Toolkit\File; use Symfony\UX\Toolkit\Installer\Installer; use Symfony\UX\Toolkit\Kit\Kit; +use Symfony\UX\Toolkit\Recipe\Recipe; use Symfony\UX\Toolkit\Registry\LocalRegistry; use Symfony\UX\Toolkit\Registry\RegistryFactory; @@ -34,13 +34,12 @@ * @internal */ #[AsCommand( - name: 'ux:toolkit:install-component', - description: 'Install a new UX Component (e.g. Alert) in your project', + name: 'ux:install', + description: 'Install a new UX Toolkit recipe in your project', )] -class InstallComponentCommand extends Command +class InstallCommand extends Command { private SymfonyStyle $io; - private bool $isInteractive; public function __construct( private readonly RegistryFactory $registryFactory, @@ -52,30 +51,27 @@ public function __construct( protected function configure(): void { $this - ->addArgument('component', InputArgument::OPTIONAL, 'The component name (Ex: Button)') - ->addOption('kit', 'k', InputOption::VALUE_OPTIONAL, 'The kit name (Ex: shadcn, or github.com/user/my-ux-toolkit-kit)') + ->addArgument('recipe', InputArgument::OPTIONAL, 'The recipe name (Ex: Button)') + ->addOption('kit', 'k', InputOption::VALUE_OPTIONAL, 'The kit name (Ex: "shadcn", or "github.com/user/my-ux-toolkit-kit")') ->addOption( 'destination', 'd', InputOption::VALUE_OPTIONAL, 'The destination directory', - Path::join('templates', 'components') + getcwd(), ) - ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force the component installation, even if the component already exists') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force the recipe installation, even if the files already exists') ->setHelp( <<%command.name% command will install a new UX Component in your project. +The %command.name% command will install a new UX Recipe in your project. -To install a component from your current kit, use: +To install a recipe, use: php %command.full_name% Button -To install a component from an official UX Toolkit kit, use the --kit option: +To install a recipe from a specific Kit (either official or external), use the --kit option: php %command.full_name% Button --kit=shadcn - -To install a component from an external GitHub kit, use the --kit option: - php %command.full_name% Button --kit=https://github.com/user/my-kit php %command.full_name% Button --kit=https://github.com/user/my-kit:branch EOF @@ -92,7 +88,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new SymfonyStyle($input, $output); $kitName = $input->getOption('kit'); - $componentName = $input->getArgument('component'); + $recipeName = $input->getArgument('recipe'); // If the kit name is not explicitly provided, we need to suggest one if (null === $kitName) { @@ -102,18 +98,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($availableKitNames as $availableKitName) { $kit = $this->registryFactory->getForKit($availableKitName)->getKit($availableKitName); - if (null === $componentName) { + if (null === $recipeName) { $availableKits[] = $kit; - } elseif (null !== $kit->getComponent($componentName)) { + } elseif (null !== $kit->getRecipe(name: $recipeName)) { $availableKits[] = $kit; } } // If more than one kit is available, we ask the user which one to use if (($availableKitsCount = \count($availableKits)) > 1) { - $kitName = $io->choice(null === $componentName ? 'Which kit do you want to use?' : \sprintf('The component "%s" exists in multiple kits. Which one do you want to use?', $componentName), array_map(fn (Kit $kit) => $kit->name, $availableKits)); + $kitName = $io->choice(null === $recipeName ? 'Which kit do you want to use?' : \sprintf('The recipe "%s" exists in multiple kits. Which one do you want to use?', $recipeName), array_map(fn (Kit $kit) => $kit->manifest->name, $availableKits)); foreach ($availableKits as $availableKit) { - if ($availableKit->name === $kitName) { + if ($availableKit->manifest->name === $kitName) { $kit = $availableKit; break; } @@ -121,9 +117,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int } elseif (1 === $availableKitsCount) { $kit = $availableKits[0]; } else { - $io->error(null === $componentName + $io->error( + null === $recipeName ? 'It seems that no local kits are available and it should not happens. Please open an issue on https://github.com/symfony/ux to report this.' - : \sprintf("The component \"%s\" does not exist in any local kits.\n\nYou can try to run one of the following commands to interactively install components:\n%s\n\nOr you can try one of the community kits https://github.com/search?q=topic:ux-toolkit&type=repositories", $componentName, implode("\n", array_map(fn (string $availableKitName) => \sprintf('$ bin/console %s --kit %s', $this->getName(), $availableKitName), $availableKitNames))) + : \sprintf("The recipe \"%s\" does not exist in any official kits.\n\nYou can try to run one of the following commands to interactively install recipes:\n%s\n\nOr you can try one of the community kits https://github.com/search?q=topic:ux-toolkit&type=repositories", $recipeName, implode("\n", array_map(fn (string $availableKitName) => \sprintf('$ bin/console %s --kit %s', $this->getName(), $availableKitName), $availableKitNames))) ); return Command::FAILURE; @@ -133,26 +130,26 @@ protected function execute(InputInterface $input, OutputInterface $output): int $kit = $registry->getKit($kitName); } - if (null === $componentName) { - // Ask for the component name if not provided - $componentName = $io->choice('Which component do you want to install?', array_map(fn (Component $component) => $component->name, $this->getAvailableComponents($kit))); - $component = $kit->getComponent($componentName); - } elseif (null === $component = $kit->getComponent($componentName)) { - // Suggest alternatives if component does not exist - $message = \sprintf('The component "%s" does not exist.', $componentName); + if (null === $recipeName) { + // Ask for the recipe name if not provided + $recipeName = $io->choice('Which recipe do you want to install?', array_map(fn (Recipe $recipe) => $recipe->manifest->name, $kit->getRecipes())); + $recipe = $kit->getRecipe(name: $recipeName); + } elseif (null === $recipe = $kit->getRecipe($recipeName)) { + // Suggest alternatives if recipe does not exist + $message = \sprintf('The recipe "%s" does not exist.', $recipeName); - $alternativeComponents = $this->getAlternativeComponents($kit, $componentName); - $alternativeComponentsCount = \count($alternativeComponents); + $alternativeRecipes = $this->getAlternativeRecipes($kit, $recipeName); + $alternativeRecipesCount = \count($alternativeRecipes); - if (1 === $alternativeComponentsCount && $input->isInteractive()) { + if (1 === $alternativeRecipesCount && $input->isInteractive()) { $io->warning($message); - if ($io->confirm(\sprintf('Do you want to install the component "%s" instead?', $alternativeComponents[0]->name))) { - $component = $alternativeComponents[0]; + if ($io->confirm(\sprintf('Do you want to install the recipe "%s" instead?', $alternativeRecipes[0]->manifest->name))) { + $recipe = $alternativeRecipes[0]; } else { return Command::FAILURE; } - } elseif ($alternativeComponentsCount > 0) { - $io->warning(\sprintf('%s'."\n".'Possible alternatives: "%s"', $message, implode('", "', array_map(fn (Component $c) => $c->name, $alternativeComponents)))); + } elseif ($alternativeRecipesCount > 0) { + $io->warning(\sprintf('%s'."\n".'Possible alternatives: "%s"', $message, implode('", "', array_map(fn (Recipe $c) => $c->manifest->name, $alternativeRecipes)))); return Command::FAILURE; } else { @@ -162,20 +159,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } - $io->writeln(\sprintf('Installing component %s from the %s kit...', $component->name, $kit->name)); + $io->writeln(\sprintf('Installing recipe %s from the %s kit...', $recipe->manifest->name, $kit->manifest->name)); $installer = new Installer($this->filesystem, fn (string $question) => $this->io->confirm($question, $input->isInteractive())); - $installationReport = $installer->installComponent($kit, $component, $destinationPath = $input->getOption('destination'), $input->getOption('force')); + $installationReport = $installer->installRecipe($kit, $recipe, $destinationPath = $input->getOption('destination'), $input->getOption('force')); if ([] === $installationReport->newFiles) { - $this->io->warning('The component has not been installed.'); + $this->io->warning('The recipe has not been installed.'); return Command::SUCCESS; } - $this->io->success('The component has been installed.'); + $this->io->success('The recipe has been installed.'); $this->io->writeln('The following file(s) have been added to your project:'); - $this->io->listing(array_map(fn (File $file) => Path::join($destinationPath, $file->relativePathName), $installationReport->newFiles)); + $this->io->listing(array_map(fn (File $file) => Path::join($destinationPath, $file->sourceRelativePathName), $installationReport->newFiles)); if ([] !== $installationReport->suggestedPhpPackages) { $this->io->writeln(\sprintf('Run composer require %s to install the required PHP dependencies.', implode(' ', $installationReport->suggestedPhpPackages))); @@ -186,34 +183,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int } /** - * @return list - */ - private function getAvailableComponents(Kit $kit): array - { - $availableComponents = []; - - foreach ($kit->getComponents() as $component) { - if (str_contains($component->name, ':')) { - continue; - } - - $availableComponents[] = $component; - } - - return $availableComponents; - } - - /** - * @return list + * @return list */ - private function getAlternativeComponents(Kit $kit, string $componentName): array + private function getAlternativeRecipes(Kit $kit, string $recipeName): array { $alternative = []; - foreach ($kit->getComponents() as $component) { - $lev = levenshtein($componentName, $component->name, 2, 5, 10); - if ($lev <= 8 || str_contains($component->name, $componentName)) { - $alternative[] = $component; + foreach ($kit->getRecipes() as $recipe) { + $lev = levenshtein($recipeName, $recipe->manifest->name, 2, 5, 10); + if ($lev <= 8 || str_contains($recipe->manifest->name, $recipeName)) { + $alternative[] = $recipe; } } diff --git a/src/Toolkit/src/Dependency/ComponentDependency.php b/src/Toolkit/src/Dependency/ComponentDependency.php deleted file mode 100644 index d5eb860b3a0..00000000000 --- a/src/Toolkit/src/Dependency/ComponentDependency.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\Dependency; - -use Symfony\UX\Toolkit\Assert; - -/** - * Represents a dependency on a component. - * - * @internal - * - * @author Hugo Alliaume - */ -final class ComponentDependency implements DependencyInterface -{ - /** - * @param non-empty-string $name The name of the component, e.g. "Table" or "Table:Body" - */ - public function __construct( - public string $name, - ) { - Assert::componentName($this->name); - } - - public function isEquivalentTo(DependencyInterface $dependency): bool - { - if (!$dependency instanceof self) { - return false; - } - - return $this->name === $dependency->name; - } - - public function __toString(): string - { - return $this->name; - } -} diff --git a/src/Toolkit/src/Dependency/DependencyInterface.php b/src/Toolkit/src/Dependency/DependencyInterface.php index c4089c4b5cd..7a3bcad0354 100644 --- a/src/Toolkit/src/Dependency/DependencyInterface.php +++ b/src/Toolkit/src/Dependency/DependencyInterface.php @@ -21,4 +21,6 @@ interface DependencyInterface extends \Stringable { public function isEquivalentTo(self $dependency): bool; + + public function toDebug(): string; } diff --git a/src/Toolkit/src/Dependency/PhpPackageDependency.php b/src/Toolkit/src/Dependency/PhpPackageDependency.php index ab365d1886c..28cf9044804 100644 --- a/src/Toolkit/src/Dependency/PhpPackageDependency.php +++ b/src/Toolkit/src/Dependency/PhpPackageDependency.php @@ -50,8 +50,13 @@ public function isHigherThan(self $dependency): bool return $this->constraintVersion->isHigherThan($dependency->constraintVersion); } + public function toDebug(): string + { + return \sprintf('PHP package "%s"', $this->__toString()); + } + public function __toString(): string { - return $this->name.($this->constraintVersion ? ':^'.$this->constraintVersion : ''); + return $this->name.(null !== $this->constraintVersion ? ':'.$this->constraintVersion : ''); } } diff --git a/src/Toolkit/src/Dependency/StimulusControllerDependency.php b/src/Toolkit/src/Dependency/RecipeDependency.php similarity index 75% rename from src/Toolkit/src/Dependency/StimulusControllerDependency.php rename to src/Toolkit/src/Dependency/RecipeDependency.php index 6fd8733c1ec..aa087074893 100644 --- a/src/Toolkit/src/Dependency/StimulusControllerDependency.php +++ b/src/Toolkit/src/Dependency/RecipeDependency.php @@ -11,24 +11,21 @@ namespace Symfony\UX\Toolkit\Dependency; -use Symfony\UX\Toolkit\Assert; - /** - * Represents a dependency on a Stimulus controller. - * - * @internal + * Represents a dependency on a recipe. * * @author Hugo Alliaume + * + * @internal */ -final class StimulusControllerDependency implements DependencyInterface +class RecipeDependency implements DependencyInterface { /** * @param non-empty-string $name */ public function __construct( - public string $name, + public readonly string $name, ) { - Assert::stimulusControllerName($this->name); } public function isEquivalentTo(DependencyInterface $dependency): bool @@ -40,6 +37,11 @@ public function isEquivalentTo(DependencyInterface $dependency): bool return $this->name === $dependency->name; } + public function toDebug(): string + { + return \sprintf('Recipe "%s"', $this->__toString()); + } + public function __toString(): string { return $this->name; diff --git a/src/Toolkit/src/File.php b/src/Toolkit/src/File.php new file mode 100644 index 00000000000..3c5ee69f82d --- /dev/null +++ b/src/Toolkit/src/File.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit; + +use Symfony\Component\Filesystem\Path; + +/** + * @internal + * + * @author Hugo Alliaume + */ +final class File implements \Stringable +{ + /** + * @throws \InvalidArgumentException + */ + public function __construct( + public readonly string $sourceRelativePathName, + public readonly string $destinationRelativePathName, + ) { + if (!Path::isRelative($this->sourceRelativePathName)) { + throw new \InvalidArgumentException(\sprintf('The source path "%s" must be relative.', $this->sourceRelativePathName)); + } + + if (!Path::isRelative($this->destinationRelativePathName)) { + throw new \InvalidArgumentException(\sprintf('The destination path "%s" must be relative.', $this->destinationRelativePathName)); + } + } + + public function __toString(): string + { + return $this->sourceRelativePathName; + } +} diff --git a/src/Toolkit/src/File/ComponentMeta.php b/src/Toolkit/src/File/ComponentMeta.php deleted file mode 100644 index 9048ce1dd5d..00000000000 --- a/src/Toolkit/src/File/ComponentMeta.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\File; - -use Symfony\UX\Toolkit\Dependency\DependencyInterface; -use Symfony\UX\Toolkit\Dependency\PhpPackageDependency; -use Symfony\UX\Toolkit\Dependency\Version; - -/** - * @author Hugo Alliaume - * - * @internal - */ -final class ComponentMeta -{ - public static function fromJson(string $json): self - { - $data = json_decode($json, true, flags: \JSON_THROW_ON_ERROR); - unset($data['$schema']); - - $dependencies = []; - foreach ($data['dependencies'] ?? [] as $i => $dependency) { - if (!isset($dependency['type'])) { - throw new \InvalidArgumentException(\sprintf('The dependency type is missing for dependency #%d, add "type" key.', $i)); - } - - if ('php' === $dependency['type']) { - $package = $dependency['package'] ?? throw new \InvalidArgumentException(\sprintf('The package name is missing for dependency #%d.', $i)); - if (str_contains($package, ':')) { - [$name, $version] = explode(':', $package, 2); - $dependencies[] = new PhpPackageDependency($name, new Version($version)); - } else { - $dependencies[] = new PhpPackageDependency($package); - } - } else { - throw new \InvalidArgumentException(\sprintf('The dependency type "%s" is not supported.', $dependency['type'])); - } - } - unset($data['dependencies']); - - if ([] !== $unused = array_keys($data)) { - throw new \InvalidArgumentException(\sprintf('The following key(s) are not supported: "%s".', implode('", "', $unused))); - } - - return new self( - $dependencies - ); - } - - /** - * @param list $dependencies - */ - private function __construct( - public readonly array $dependencies, - ) { - } -} diff --git a/src/Toolkit/src/File/File.php b/src/Toolkit/src/File/File.php deleted file mode 100644 index dec39e05729..00000000000 --- a/src/Toolkit/src/File/File.php +++ /dev/null @@ -1,50 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\File; - -use Symfony\Component\Filesystem\Path; - -/** - * @internal - * - * @author Hugo Alliaume - */ -final class File implements \Stringable -{ - /** - * @param non-empty-string $relativePathNameToKit relative path from the kit root directory, example "templates/components/Table/Body.html.twig" - * @param non-empty-string $relativePathName relative path name, without any prefix, example "Table/Body.html.twig" - * - * @throws \InvalidArgumentException - */ - public function __construct( - public readonly string $relativePathNameToKit, - public readonly string $relativePathName, - ) { - if (!Path::isRelative($relativePathNameToKit)) { - throw new \InvalidArgumentException(\sprintf('The path to the kit "%s" must be relative.', $relativePathNameToKit)); - } - - if (!Path::isRelative($relativePathName)) { - throw new \InvalidArgumentException(\sprintf('The path name "%s" must be relative.', $relativePathName)); - } - - if (!str_ends_with($relativePathNameToKit, $relativePathName)) { - throw new \InvalidArgumentException(\sprintf('The relative path name "%s" must be a subpath of the relative path to the kit "%s".', $relativePathName, $relativePathNameToKit)); - } - } - - public function __toString(): string - { - return $this->relativePathNameToKit; - } -} diff --git a/src/Toolkit/src/Installer/InstallationReport.php b/src/Toolkit/src/Installer/InstallationReport.php index 975fbe1bd75..2844367e28e 100644 --- a/src/Toolkit/src/Installer/InstallationReport.php +++ b/src/Toolkit/src/Installer/InstallationReport.php @@ -14,7 +14,7 @@ namespace Symfony\UX\Toolkit\Installer; use Symfony\UX\Toolkit\Dependency\PhpPackageDependency; -use Symfony\UX\Toolkit\File\File; +use Symfony\UX\Toolkit\File; /** * Represents the output after an installation. diff --git a/src/Toolkit/src/Installer/Installer.php b/src/Toolkit/src/Installer/Installer.php index 816576e549f..4c0a6e05096 100644 --- a/src/Toolkit/src/Installer/Installer.php +++ b/src/Toolkit/src/Installer/Installer.php @@ -15,9 +15,8 @@ use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Path; -use Symfony\UX\Toolkit\Asset\Component; -use Symfony\UX\Toolkit\File\File; use Symfony\UX\Toolkit\Kit\Kit; +use Symfony\UX\Toolkit\Recipe\Recipe; final class Installer { @@ -33,9 +32,9 @@ public function __construct( $this->poolResolver = new PoolResolver(); } - public function installComponent(Kit $kit, Component $component, string $destinationPath, bool $force): InstallationReport + public function installRecipe(Kit $kit, Recipe $recipe, string $destinationPath, bool $force): InstallationReport { - $pool = $this->poolResolver->resolveForComponent($kit, $component); + $pool = $this->poolResolver->resolveForRecipe($kit, $recipe); $output = $this->handlePool($pool, $kit, $destinationPath, $force); return $output; @@ -48,30 +47,29 @@ private function handlePool(Pool $pool, Kit $kit, string $destinationPath, bool { $installedFiles = []; - foreach ($pool->getFiles() as $file) { - if ($this->installFile($kit, $file, $destinationPath, $force)) { - $installedFiles[] = $file; + foreach ($pool->getFiles() as $recipeAbsolutePath => $files) { + foreach ($files as $file) { + $sourceAbsolutePathName = Path::join($recipeAbsolutePath, $file->sourceRelativePathName); + $destinationAbsolutePathName = Path::join($destinationPath, $file->destinationRelativePathName); + + if ($this->copyFile($kit, $sourceAbsolutePathName, $destinationAbsolutePathName, $force)) { + $installedFiles[] = $file; + } } } return new InstallationReport(newFiles: $installedFiles, suggestedPhpPackages: $pool->getPhpPackageDependencies()); } - /** - * @param non-empty-string $destinationPath - */ - private function installFile(Kit $kit, File $file, string $destinationPath, bool $force): bool + private function copyFile(Kit $kit, string $sourceAbsolutePathName, string $destinationAbsolutePathName, bool $force): bool { - $componentPath = Path::join($kit->path, $file->relativePathNameToKit); - $componentDestinationPath = Path::join($destinationPath, $file->relativePathName); - - if ($this->filesystem->exists($componentDestinationPath) && !$force) { - if (!($this->askConfirmation)(\sprintf('File "%s" already exists. Do you want to overwrite it?', $componentDestinationPath))) { + if ($this->filesystem->exists($destinationAbsolutePathName) && !$force) { + if (!($this->askConfirmation)(\sprintf('File "%s" already exists. Do you want to overwrite it?', $destinationAbsolutePathName))) { return false; } } - $this->filesystem->copy($componentPath, $componentDestinationPath, $force); + $this->filesystem->copy($sourceAbsolutePathName, $destinationAbsolutePathName, $force); return true; } diff --git a/src/Toolkit/src/Installer/Pool.php b/src/Toolkit/src/Installer/Pool.php index 545b8c25f26..80550bf27c0 100644 --- a/src/Toolkit/src/Installer/Pool.php +++ b/src/Toolkit/src/Installer/Pool.php @@ -14,7 +14,8 @@ namespace Symfony\UX\Toolkit\Installer; use Symfony\UX\Toolkit\Dependency\PhpPackageDependency; -use Symfony\UX\Toolkit\File\File; +use Symfony\UX\Toolkit\File; +use Symfony\UX\Toolkit\Recipe\Recipe; /** * Represents a pool of files and dependencies to be installed. @@ -35,13 +36,13 @@ final class Pool */ private array $phpPackageDependencies = []; - public function addFile(File $file): void + public function addFile(Recipe $recipe, File $file): void { - $this->files[$file->relativePathName] ??= $file; + $this->files[$recipe->absolutePath][$file->destinationRelativePathName] ??= $file; } /** - * @return array + * @return array> */ public function getFiles(): array { diff --git a/src/Toolkit/src/Installer/PoolResolver.php b/src/Toolkit/src/Installer/PoolResolver.php index 385663aa60b..6f45618ffeb 100644 --- a/src/Toolkit/src/Installer/PoolResolver.php +++ b/src/Toolkit/src/Installer/PoolResolver.php @@ -13,49 +13,49 @@ namespace Symfony\UX\Toolkit\Installer; -use Symfony\UX\Toolkit\Asset\Component; -use Symfony\UX\Toolkit\Dependency\ComponentDependency; use Symfony\UX\Toolkit\Dependency\PhpPackageDependency; -use Symfony\UX\Toolkit\Dependency\StimulusControllerDependency; +use Symfony\UX\Toolkit\Dependency\RecipeDependency; use Symfony\UX\Toolkit\Kit\Kit; +use Symfony\UX\Toolkit\Recipe\Recipe; +/** + * @author Hugo Alliaume + * + * @internal + */ final class PoolResolver { - public function resolveForComponent(Kit $kit, Component $component): Pool + public function resolveForRecipe(Kit $kit, Recipe $recipe): Pool { $pool = new Pool(); // Process the component and its dependencies - $componentsStack = [$component]; - $visitedComponents = new \SplObjectStorage(); + $recipesStack = [$recipe]; + $visitedRecipes = new \SplObjectStorage(); - while (!empty($componentsStack)) { - $currentComponent = array_pop($componentsStack); + while (!empty($recipesStack)) { + $currentRecipe = array_pop($recipesStack); // Skip circular references - if ($visitedComponents->contains($currentComponent)) { + if ($visitedRecipes->contains($currentRecipe)) { continue; } - $visitedComponents->attach($currentComponent); + $visitedRecipes->attach($currentRecipe); - foreach ($currentComponent->files as $file) { - $pool->addFile($file); + foreach ($currentRecipe->getFiles() as $file) { + $pool->addFile($currentRecipe, $file); } - foreach ($currentComponent->getDependencies() as $dependency) { - if ($dependency instanceof ComponentDependency) { - $componentsStack[] = $kit->getComponent($dependency->name); - } elseif ($dependency instanceof PhpPackageDependency) { + foreach ($currentRecipe->manifest->dependencies as $dependency) { + if ($dependency instanceof PhpPackageDependency) { $pool->addPhpPackageDependency($dependency); - } elseif ($dependency instanceof StimulusControllerDependency) { - if (null === $stimulusController = $kit->getStimulusController($dependency->name)) { - throw new \RuntimeException(\sprintf('Stimulus controller "%s" not found.', $dependency->name)); + } elseif ($dependency instanceof RecipeDependency) { + if (null === $recipeDependency = $kit->getRecipe($dependency->name)) { + throw new \LogicException(\sprintf('The recipe "%s" has a dependency on unregistered recipe "%s".', $currentRecipe->manifest->name, $dependency->name)); } - foreach ($stimulusController->files as $file) { - $pool->addFile($file); - } + $recipesStack[] = $recipeDependency; } else { throw new \RuntimeException(\sprintf('Unknown dependency type: "%s"', $dependency::class)); } diff --git a/src/Toolkit/src/Kit/Kit.php b/src/Toolkit/src/Kit/Kit.php index dcf69290581..04b326c507a 100644 --- a/src/Toolkit/src/Kit/Kit.php +++ b/src/Toolkit/src/Kit/Kit.php @@ -12,9 +12,8 @@ namespace Symfony\UX\Toolkit\Kit; use Symfony\Component\Filesystem\Path; -use Symfony\UX\Toolkit\Assert; -use Symfony\UX\Toolkit\Asset\Component; -use Symfony\UX\Toolkit\Asset\StimulusController; +use Symfony\UX\Toolkit\Recipe\Recipe; +use Symfony\UX\Toolkit\Recipe\RecipeType; /** * @internal @@ -24,86 +23,53 @@ final class Kit { /** - * @param non-empty-string $path - * @param non-empty-string $name - * @param non-empty-string|null $homepage - * @param non-empty-string|null $license - * @param list $components - * @param list $stimulusControllers + * @var list */ - public function __construct( - public readonly string $path, - public readonly string $name, - public readonly ?string $homepage = null, - public readonly ?string $license = null, - public readonly ?string $description = null, - public readonly ?string $uxIcon = null, - public ?string $installAsMarkdown = null, - private array $components = [], - private array $stimulusControllers = [], - ) { - Assert::kitName($this->name); - - if (!Path::isAbsolute($this->path)) { - throw new \InvalidArgumentException(\sprintf('Kit path "%s" is not absolute.', $this->path)); - } - - if (null !== $this->homepage && !filter_var($this->homepage, \FILTER_VALIDATE_URL)) { - throw new \InvalidArgumentException(\sprintf('Invalid homepage URL "%s".', $this->homepage)); - } - } + private array $recipes = []; /** - * @throws \InvalidArgumentException if the component is already registered in the kit + * @param non-empty-string $absolutePath + * + * @throws \InvalidArgumentException */ - public function addComponent(Component $component): void - { - foreach ($this->components as $existingComponent) { - if ($existingComponent->name === $component->name) { - throw new \InvalidArgumentException(\sprintf('Component "%s" is already registered in the kit.', $component->name)); - } + public function __construct( + public readonly string $absolutePath, + public readonly KitManifest $manifest, + public ?string $installAsMarkdown = null, + ) { + if (!Path::isAbsolute($this->absolutePath)) { + throw new \InvalidArgumentException(\sprintf('Kit path "%s" is not absolute.', $this->absolutePath)); } - - $this->components[] = $component; - } - - public function getComponents(): array - { - return $this->components; } - public function getComponent(string $name): ?Component + public function addRecipe(Recipe $recipe): void { - foreach ($this->components as $component) { - if ($component->name === $name) { - return $component; + foreach ($this->recipes as $existingRecipe) { + if ($existingRecipe->manifest->name === $recipe->manifest->name) { + throw new \InvalidArgumentException(\sprintf('Recipe "%s" is already registered in the kit.', $recipe->manifest->name)); } } - return null; + $this->recipes[] = $recipe; } - public function addStimulusController(StimulusController $stimulusController): void + /** + * @return array + */ + public function getRecipes(?RecipeType $type = null): array { - foreach ($this->stimulusControllers as $existingStimulusController) { - if ($existingStimulusController->name === $stimulusController->name) { - throw new \InvalidArgumentException(\sprintf('Stimulus controller "%s" is already registered in the kit.', $stimulusController->name)); - } + if (null !== $type) { + $this->recipes = array_filter($this->recipes, fn (Recipe $recipe) => $recipe->manifest->type === $type); } - $this->stimulusControllers[] = $stimulusController; - } - - public function getStimulusControllers(): array - { - return $this->stimulusControllers; + return $this->recipes; } - public function getStimulusController(string $name): ?StimulusController + public function getRecipe(string $name, ?RecipeType $type = null): ?Recipe { - foreach ($this->stimulusControllers as $stimulusController) { - if ($stimulusController->name === $name) { - return $stimulusController; + foreach ($this->recipes as $recipe) { + if ($recipe->manifest->name === $name && (null === $type || $recipe->manifest->type === $type)) { + return $recipe; } } diff --git a/src/Toolkit/src/Kit/KitContextRunner.php b/src/Toolkit/src/Kit/KitContextRunner.php index e55667c19bb..38962e7dbc1 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\Component\Filesystem\Path; +use Symfony\UX\Toolkit\Recipe\RecipeType; use Symfony\UX\TwigComponent\ComponentFactory; use Symfony\UX\TwigComponent\ComponentTemplateFinderInterface; use Twig\Loader\ChainLoader; @@ -55,10 +55,11 @@ private function contextualizeServicesForKit(Kit $kit): callable { // Configure Twig $initialTwigLoader = $this->twig->getLoader(); - $this->twig->setLoader(new ChainLoader([ - new FilesystemLoader(Path::join($kit->path, 'templates/components')), - $initialTwigLoader, - ])); + $loaders = []; + foreach ($kit->getRecipes(type: RecipeType::Component) as $recipe) { + $loaders[] = new FilesystemLoader($recipe->absolutePath); + } + $this->twig->setLoader(new ChainLoader([...$loaders, $initialTwigLoader])); // Configure Twig Components $reflComponentFactory = new \ReflectionClass($this->componentFactory); @@ -82,22 +83,22 @@ private function createComponentTemplateFinder(Kit $kit): ComponentTemplateFinde { static $instances = []; - return $instances[$kit->name] ?? new class($kit) implements ComponentTemplateFinderInterface { + return $instances[$kit->manifest->name] ?? new class($kit) implements ComponentTemplateFinderInterface { public function __construct(private readonly Kit $kit) { } public function findAnonymousComponentTemplate(string $name): ?string { - if (null === $component = $this->kit->getComponent($name)) { - throw new \RuntimeException(\sprintf('Component "%s" does not exist in kit "%s".', $name, $this->kit->name)); - } - - foreach ($component->files as $file) { - return $file->relativePathName; + foreach ($this->kit->getRecipes(type: RecipeType::Component) as $recipe) { + foreach ($recipe->getFiles() as $file) { + if (str_ends_with($file->sourceRelativePathName, str_replace(':', \DIRECTORY_SEPARATOR, $name).'.html.twig')) { + return $file->sourceRelativePathName; + } + } } - throw new \LogicException(\sprintf('No Twig files found for component "%s" in kit "%s", it should not happens.', $name, $this->kit->name)); + throw new \LogicException(\sprintf('No Twig files found for component "%s" in kit "%s", it should not happens.', $name, $this->kit->manifest->name)); } }; } diff --git a/src/Toolkit/src/Kit/KitFactory.php b/src/Toolkit/src/Kit/KitFactory.php index 3e20df1c6ca..e127b02a19d 100644 --- a/src/Toolkit/src/Kit/KitFactory.php +++ b/src/Toolkit/src/Kit/KitFactory.php @@ -29,7 +29,6 @@ public function __construct( /** * @throws \InvalidArgumentException if the manifest file is missing a required key - * @throws \JsonException if the manifest file is not valid JSON */ public function createKitFromAbsolutePath(string $absolutePath): Kit { @@ -45,15 +44,13 @@ public function createKitFromAbsolutePath(string $absolutePath): Kit throw new \InvalidArgumentException(\sprintf('File "%s" not found.', $manifestPath)); } - $manifest = json_decode(file_get_contents($manifestPath), true, flags: \JSON_THROW_ON_ERROR); + try { + $manifest = KitManifest::fromJson(file_get_contents($manifestPath)); + } catch (\JsonException $e) { + throw new \RuntimeException(\sprintf('Unable to parse "%s"', $manifestPath), previous: $e); + } - $kit = new Kit( - path: $absolutePath, - name: $manifest['name'] ?? throw new \InvalidArgumentException('Manifest file is missing "name" key.'), - homepage: $manifest['homepage'] ?? throw new \InvalidArgumentException('Manifest file is missing "homepage" key.'), - license: $manifest['license'] ?? throw new \InvalidArgumentException('Manifest file is missing "license" key.'), - description: $manifest['description'] ?? null, - ); + $kit = new Kit(absolutePath: $absolutePath, manifest: $manifest); $this->kitSynchronizer->synchronize($kit); diff --git a/src/Toolkit/src/Kit/KitManifest.php b/src/Toolkit/src/Kit/KitManifest.php new file mode 100644 index 00000000000..321806f3c29 --- /dev/null +++ b/src/Toolkit/src/Kit/KitManifest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Kit; + +use Symfony\UX\Toolkit\Assert; + +/** + * @author Hugo Alliaume + */ +final class KitManifest +{ + public function __construct( + public readonly string $name, + public readonly string $description, + public readonly string $license, + public readonly string $homepage, + public ?string $installAsMarkdown = null, + ) { + Assert::kitName($this->name); + + if (!filter_var($this->homepage, \FILTER_VALIDATE_URL)) { + throw new \InvalidArgumentException(\sprintf('Invalid homepage URL "%s".', $this->homepage)); + } + } + + /** + * @throws \JsonException + * @throws \InvalidArgumentException + */ + public static function fromJson(string $json): self + { + $data = json_decode($json, true, flags: \JSON_THROW_ON_ERROR); + + return new self( + name: $data['name'] ?? throw new \InvalidArgumentException('Property "name" is required.'), + description: $data['description'] ?? throw new \InvalidArgumentException('Property "description" is required.'), + license: $data['license'] ?? throw new \InvalidArgumentException('Property "license" is required.'), + homepage: $data['homepage'] ?? throw new \InvalidArgumentException('Property "homepage" is required.') + ); + } +} diff --git a/src/Toolkit/src/Kit/KitSynchronizer.php b/src/Toolkit/src/Kit/KitSynchronizer.php index 0f13f92168f..951ba70fab0 100644 --- a/src/Toolkit/src/Kit/KitSynchronizer.php +++ b/src/Toolkit/src/Kit/KitSynchronizer.php @@ -14,14 +14,7 @@ use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Path; use Symfony\Component\Finder\Finder; -use Symfony\UX\Toolkit\Asset\Component; -use Symfony\UX\Toolkit\Asset\StimulusController; -use Symfony\UX\Toolkit\Dependency\ComponentDependency; -use Symfony\UX\Toolkit\Dependency\PhpPackageDependency; -use Symfony\UX\Toolkit\Dependency\StimulusControllerDependency; -use Symfony\UX\Toolkit\File\ComponentMeta; -use Symfony\UX\Toolkit\File\Doc; -use Symfony\UX\Toolkit\File\File; +use Symfony\UX\Toolkit\Recipe\RecipeSynchronizer; /** * @internal @@ -30,176 +23,36 @@ */ final class KitSynchronizer { - /** - * @see https://regex101.com/r/WasRGf/1 - */ - private const RE_TWIG_COMPONENT_REFERENCES = '/[a-zA-Z0-9:_-]+)/'; - - /** - * @see https://regex101.com/r/inIBID/1 - */ - private const RE_STIMULUS_CONTROLLER_REFERENCES = '/data-controller=(["\'])(?P.+?)\1/'; - - private const UX_COMPONENTS_PACKAGES = [ - 'ux:icon' => 'symfony/ux-icons', - 'ux:map' => 'symfony/ux-map', - ]; - public function __construct( private readonly Filesystem $filesystem, + private readonly RecipeSynchronizer $recipeSynchronizer, ) { } public function synchronize(Kit $kit): void { - $this->synchronizeComponents($kit); - $this->synchronizeStimulusControllers($kit); - $this->synchronizeDocumentation($kit); - } - - private function synchronizeComponents(Kit $kit): void - { - $componentsPath = Path::join('templates', 'components'); - $finder = (new Finder()) - ->in($kit->path) - ->files() - ->path($componentsPath) - ->sortByName() - ->name('*.html.twig') - ; - - foreach ($finder as $file) { - $relativePathNameToKit = Path::normalize($file->getRelativePathname()); - $relativePathName = str_replace($componentsPath.'/', '', $relativePathNameToKit); - $componentName = $this->extractComponentName($relativePathName); - - $meta = null; - if ($this->filesystem->exists($metaJsonFile = Path::join($file->getPath(), str_replace('.html.twig', '.meta.json', $file->getBasename())))) { - $metaJson = file_get_contents($metaJsonFile) ?: throw new \RuntimeException(\sprintf('Unable to get contents from file "%s".', $metaJsonFile)); - try { - $meta = ComponentMeta::fromJson($metaJson); - } catch (\Throwable $e) { - throw new \RuntimeException(\sprintf('Unable to parse component "%s" meta from JSON file "%s".', $componentName, $metaJsonFile), previous: $e); - } - } - - $component = new Component( - name: $componentName, - files: [new File( - relativePathNameToKit: $relativePathNameToKit, - relativePathName: $relativePathName, - )], - meta: $meta, - ); - - $kit->addComponent($component); - } - - foreach ($kit->getComponents() as $component) { - $this->resolveComponentDependencies($kit, $component); - } - } - - private function resolveComponentDependencies(Kit $kit, Component $component): void - { - // Find dependencies based on component name - foreach ($kit->getComponents() as $otherComponent) { - if ($component->name === $otherComponent->name) { - continue; - } - - // Find components with the component name as a prefix - if (str_starts_with($otherComponent->name, $component->name.':')) { - $component->addDependency(new ComponentDependency($otherComponent->name)); - } + if ($this->filesystem->exists($installMd = Path::join($kit->absolutePath, 'INSTALL.md'))) { + $kit->installAsMarkdown = file_get_contents($installMd); } - // Find dependencies based on file content - foreach ($component->files as $file) { - if (!$this->filesystem->exists($filePath = Path::join($kit->path, $file->relativePathNameToKit))) { - throw new \RuntimeException(\sprintf('File "%s" not found', $filePath)); - } - - $fileContent = file_get_contents($filePath); - - if (str_contains($fileContent, 'name) { - continue; - } - - if (null !== $package = self::UX_COMPONENTS_PACKAGES[strtolower($componentReferenceName)] ?? null) { - if (!$component->hasDependency(new PhpPackageDependency($package))) { - throw new \RuntimeException(\sprintf('Component "%s" uses "%s" UX Twig component, but the composer package "%s" is not listed as a dependency in meta file.', $component->name, $componentReferenceName, $package)); - } - } elseif (null === $componentReference = $kit->getComponent($componentReferenceName)) { - throw new \RuntimeException(\sprintf('Component "%s" not found in component "%s" (file "%s")', $componentReferenceName, $component->name, $file->relativePathNameToKit)); - } else { - $component->addDependency(new ComponentDependency($componentReference->name)); - } - } - } - - if (str_contains($fileContent, 'data-controller=') && preg_match_all(self::RE_STIMULUS_CONTROLLER_REFERENCES, $fileContent, $matches)) { - $controllersName = array_filter(array_map(fn (string $name) => trim($name), explode(' ', $matches['controllersName'][0]))); - foreach ($controllersName as $controllerReferenceName) { - $component->addDependency(new StimulusControllerDependency($controllerReferenceName)); - } - } - } + $this->synchronizeRecipes($kit); } - private function synchronizeStimulusControllers(Kit $kit): void + private function synchronizeRecipes(Kit $kit): void { - $controllersPath = Path::join('assets', 'controllers'); $finder = (new Finder()) - ->in($kit->path) + ->in($kit->absolutePath) ->files() - ->path($controllersPath) + ->depth('== 1') ->sortByName() - ->name('*.js') - ; - - foreach ($finder as $file) { - $relativePathNameToKit = Path::normalize($file->getRelativePathname()); - $relativePathName = str_replace($controllersPath.'/', '', $relativePathNameToKit); - $controllerName = $this->extractStimulusControllerName($relativePathName); - $controller = new StimulusController( - name: $controllerName, - files: [new File( - relativePathNameToKit: $relativePathNameToKit, - relativePathName: $relativePathName, - )], - ); - - $kit->addStimulusController($controller); - } - } + ->name('manifest.json'); - private function synchronizeDocumentation(Kit $kit): void - { - // Read INSTALL.md if exists - $fileInstall = Path::join($kit->path, 'INSTALL.md'); - if ($this->filesystem->exists($fileInstall)) { - $kit->installAsMarkdown = file_get_contents($fileInstall); + if (!$finder->hasResults()) { + throw new \RuntimeException(\sprintf('No recipes found at "%s".', $kit->absolutePath)); } - // Iterate over Component and find their documentation - foreach ($kit->getComponents() as $component) { - $docPath = Path::join($kit->path, 'docs', 'components', $component->name.'.md'); - if ($this->filesystem->exists($docPath)) { - $component->doc = new Doc(file_get_contents($docPath)); - } + foreach ($finder as $manifestFile) { + $this->recipeSynchronizer->synchronizeRecipe($kit, $manifestFile); } } - - private static function extractComponentName(string $pathnameRelativeToKit): string - { - return str_replace(['.html.twig', '/'], ['', ':'], $pathnameRelativeToKit); - } - - private static function extractStimulusControllerName(string $pathnameRelativeToKit): string - { - return str_replace(['_controller.js', '-controller.js', '/', '_'], ['', '', '--', '-'], $pathnameRelativeToKit); - } } diff --git a/src/Toolkit/src/Recipe/Recipe.php b/src/Toolkit/src/Recipe/Recipe.php new file mode 100644 index 00000000000..c0504fa3970 --- /dev/null +++ b/src/Toolkit/src/Recipe/Recipe.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Recipe; + +use Symfony\Component\Filesystem\Path; +use Symfony\Component\Finder\Finder; +use Symfony\UX\Toolkit\File; + +/** + * @author Hugo Alliaume + * + * @internal + */ +final class Recipe +{ + /** + * @param non-empty-string $absolutePath + */ + public function __construct( + public readonly string $absolutePath, + public readonly RecipeManifest $manifest, + ) { + if (!Path::isAbsolute($this->absolutePath)) { + throw new \InvalidArgumentException(\sprintf('Kit path "%s" is not absolute.', $this->absolutePath)); + } + } + + /** + * @return iterable + */ + public function getFiles(): iterable + { + foreach ($this->manifest->copyFiles as $source => $destination) { + $finder = (new Finder())->in(Path::join($this->absolutePath, $source))->sortByName()->files(); + + foreach ($finder as $file) { + yield new File(Path::join($source, $file->getRelativePathname()), Path::join($destination, $file->getRelativePathname())); + } + } + } +} diff --git a/src/Toolkit/src/Recipe/RecipeManifest.php b/src/Toolkit/src/Recipe/RecipeManifest.php new file mode 100644 index 00000000000..3cde06f4f68 --- /dev/null +++ b/src/Toolkit/src/Recipe/RecipeManifest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Recipe; + +use Symfony\Component\Filesystem\Path; +use Symfony\UX\Toolkit\Dependency\DependencyInterface; +use Symfony\UX\Toolkit\Dependency\PhpPackageDependency; +use Symfony\UX\Toolkit\Dependency\RecipeDependency; +use Symfony\UX\Toolkit\Dependency\Version; + +/** + * @author Hugo Alliaume + * + * @internal + */ +final class RecipeManifest +{ + /** + * @param non-empty-string $name + * @param non-empty-string $description + * @param array $copyFiles + * @param list $dependencies + */ + public function __construct( + public readonly RecipeType $type, + public readonly string $name, + public readonly string $description, + public readonly array $copyFiles, + public readonly array $dependencies = [], + ) { + foreach ($this->copyFiles as $source => $destination) { + if (!Path::isRelative($source)) { + throw new \InvalidArgumentException(\sprintf('Copy file source "%s" must be a relative path.', $source)); + } + if (!Path::isRelative($destination)) { + throw new \InvalidArgumentException(\sprintf('Copy file destination "%s" must be a relative path.', $destination)); + } + } + } + + /** + * @throws \JsonException + * @throws \InvalidArgumentException + */ + public static function fromJson(string $json): self + { + $data = json_decode($json, true, flags: \JSON_THROW_ON_ERROR); + + $dependencies = []; + foreach ($data['dependencies'] ?? [] as $i => $dependency) { + if (!\is_array($dependency)) { + throw new \InvalidArgumentException('Each dependency must be an associative array.'); + } + if (!isset($dependency['type'])) { + throw new \InvalidArgumentException(\sprintf('The dependency type is missing for dependency #%d, add "type" key.', $i)); + } + + if ('php' === $dependency['type']) { + $package = $dependency['package'] ?? throw new \InvalidArgumentException(\sprintf('The package name is missing for dependency #%d, add "package" key.', $i)); + if (str_contains($package, ':')) { + [$name, $version] = explode(':', $package, 2); + $dependencies[] = new PhpPackageDependency($name, new Version($version)); + } else { + $dependencies[] = new PhpPackageDependency($package); + } + } elseif ('recipe' === $dependency['type']) { + $name = $dependency['name'] ?? throw new \InvalidArgumentException(\sprintf('The recipe name is missing for dependency #%d, add "name" key.', $i)); + $dependencies[] = new RecipeDependency($name); + } else { + throw new \InvalidArgumentException(\sprintf('The dependency type "%s" is not supported.', $dependency['type'])); + } + } + + $type = $data['type'] ?? throw new \InvalidArgumentException('Property "type" is required.'); + if (null === $type = RecipeType::tryFrom($type)) { + throw new \InvalidArgumentException(\sprintf('The recipe type "%s" is not supported.', $data['type'])); + } + + return new self( + type: $type, + name: $data['name'] ?? throw new \InvalidArgumentException('Property "name" is required.'), + description: $data['description'] ?? throw new \InvalidArgumentException('Property "description" is required.'), + copyFiles: $data['copy-files'] ?? throw new \InvalidArgumentException('Property "copy-files" is required.'), + dependencies: $dependencies, + ); + } +} diff --git a/src/Toolkit/src/Recipe/RecipeSynchronizer.php b/src/Toolkit/src/Recipe/RecipeSynchronizer.php new file mode 100644 index 00000000000..d3e06a970bb --- /dev/null +++ b/src/Toolkit/src/Recipe/RecipeSynchronizer.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Recipe; + +use Symfony\Component\Finder\SplFileInfo; +use Symfony\UX\Toolkit\Kit\Kit; + +/** + * @author Hugo Alliaume + * + * @internal + */ +final class RecipeSynchronizer +{ + public function synchronizeRecipe(Kit $kit, SplFileInfo $manifestFile): void + { + try { + $manifest = RecipeManifest::fromJson($manifestFile->getContents()); + } catch (\JsonException|\InvalidArgumentException $e) { + throw new \RuntimeException(\sprintf('Unable to parse manifest file "%s": "%s"', $manifestFile->getPathname(), $e->getMessage()), previous: $e); + } + + $recipe = new Recipe( + absolutePath: $manifestFile->getPath(), + manifest: $manifest, + ); + + $kit->addRecipe($recipe); + } +} diff --git a/src/Toolkit/src/File/Doc.php b/src/Toolkit/src/Recipe/RecipeType.php similarity index 59% rename from src/Toolkit/src/File/Doc.php rename to src/Toolkit/src/Recipe/RecipeType.php index 1c66f4b619e..34e80582e80 100644 --- a/src/Toolkit/src/File/Doc.php +++ b/src/Toolkit/src/Recipe/RecipeType.php @@ -9,20 +9,14 @@ * file that was distributed with this source code. */ -namespace Symfony\UX\Toolkit\File; +namespace Symfony\UX\Toolkit\Recipe; /** - * @internal - * * @author Hugo Alliaume + * + * @internal */ -final class Doc +enum RecipeType: string { - /** - * @param non-empty-string $markdownContent - */ - public function __construct( - public readonly string $markdownContent, - ) { - } + case Component = 'component'; } diff --git a/src/Toolkit/src/Registry/LocalRegistry.php b/src/Toolkit/src/Registry/LocalRegistry.php index dc8577ce955..7dc40553717 100644 --- a/src/Toolkit/src/Registry/LocalRegistry.php +++ b/src/Toolkit/src/Registry/LocalRegistry.php @@ -45,7 +45,7 @@ public function getKit(string $kitName): Kit return $this->kitFactory->createKitFromAbsolutePath($kitDir); } - throw new \RuntimeException(\sprintf('Unable to find the kit "%s" in the following directories: "%s"', $kitName, implode('", "', $possibleKitDirs))); + throw new \InvalidArgumentException(\sprintf('Kit "%s" does not exist.', $kitName)); } /** @@ -54,7 +54,7 @@ public function getKit(string $kitName): Kit public static function getAvailableKitsName(): array { $availableKitsName = []; - $finder = (new Finder())->directories()->in(self::$kitsDir)->depth(0); + $finder = (new Finder())->directories()->in(self::$kitsDir)->sortByName()->depth(0); foreach ($finder as $directory) { $kitName = $directory->getRelativePathname(); diff --git a/src/Toolkit/tests/AssertTest.php b/src/Toolkit/tests/AssertTest.php index adafc226b28..37998129e63 100644 --- a/src/Toolkit/tests/AssertTest.php +++ b/src/Toolkit/tests/AssertTest.php @@ -177,45 +177,4 @@ public static function provideInvalidPhpPackageNames(): iterable yield ['twig/html-extra/']; yield ['twig/html-extra/twig']; } - - /** - * @dataProvider provideValidStimulusControllerNames - */ - public function testValidStimulusControllerName(string $name) - { - $this->expectNotToPerformAssertions(); - - Assert::stimulusControllerName($name); - } - - public static function provideValidStimulusControllerNames(): iterable - { - yield ['my-controller']; - yield ['users--list-item']; - yield ['controller']; - yield ['controller-with-numbers-123']; - } - - /** - * @dataProvider provideInvalidStimulusControllerNames - */ - public function testInvalidStimulusControllerName(string $name) - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage(\sprintf('Invalid Stimulus controller name "%s".', $name)); - - Assert::stimulusControllerName($name); - } - - public static function provideInvalidStimulusControllerNames(): iterable - { - yield ['']; - yield ['my_controller']; - yield ['my-controller-']; - yield ['-my-controller']; - yield ['my-controller/qsd']; - yield ['my-controller@qsd']; - yield ['my-controller.qsd']; - yield ['my-controller:qsd']; - } } diff --git a/src/Toolkit/tests/Asset/ComponentTest.php b/src/Toolkit/tests/Asset/ComponentTest.php deleted file mode 100644 index 4ef9ebdabff..00000000000 --- a/src/Toolkit/tests/Asset/ComponentTest.php +++ /dev/null @@ -1,120 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\Tests\Asset; - -use PHPUnit\Framework\TestCase; -use Symfony\UX\Toolkit\Asset\Component; -use Symfony\UX\Toolkit\Dependency\ComponentDependency; -use Symfony\UX\Toolkit\Dependency\PhpPackageDependency; -use Symfony\UX\Toolkit\Dependency\Version; -use Symfony\UX\Toolkit\File\File; - -final class ComponentTest extends TestCase -{ - public function testCanBeInstantiated() - { - $component = new Component('Button', [ - new File('templates/components/Button/Button.html.twig', 'Button.html.twig'), - ]); - - $this->assertSame('Button', $component->name); - $this->assertCount(1, $component->files); - $this->assertInstanceOf(File::class, $component->files[0]); - $this->assertNull($component->doc); - $this->assertCount(0, $component->getDependencies()); - } - - public function testShouldFailIfComponentNameIsInvalid() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid component name "foobar".'); - - new Component('foobar', [ - new File('templates/components/Button/Button.html.twig', 'Button.html.twig'), - ]); - } - - public function testShouldFailIfComponentHasNoFiles() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The component "Button" must have at least one file.'); - - new Component('Button', []); - } - - public function testCanAddAndGetDependencies() - { - $component = new Component('Button', [ - new File('templates/components/Button/Button.html.twig', 'Button.html.twig'), - ]); - - $component->addDependency($dependency1 = new ComponentDependency('Icon')); - $component->addDependency($dependency2 = new ComponentDependency('Label')); - $component->addDependency($dependency3 = new PhpPackageDependency('symfony/twig-component', new Version('2.24.0'))); - - self::assertCount(3, $component->getDependencies()); - self::assertEquals([$dependency1, $dependency2, $dependency3], $component->getDependencies()); - } - - public function testShouldNotAddDuplicateComponentDependencies() - { - $component = new Component('Button', [ - new File('templates/components/Button/Button.html.twig', 'Button.html.twig'), - ]); - - $component->addDependency($dependency1 = new ComponentDependency('Icon')); - $component->addDependency($dependency2 = new ComponentDependency('Label')); - $component->addDependency($dependency3 = new ComponentDependency('Icon')); - $component->addDependency($dependency4 = new PhpPackageDependency('symfony/twig-component', new Version('2.24.0'))); - - self::assertCount(3, $component->getDependencies()); - self::assertEquals([$dependency1, $dependency2, $dependency4], $component->getDependencies()); - } - - public function testShouldReplacePhpPackageDependencyIfVersionIsHigher() - { - $component = new Component('Button', [ - new File('templates/components/Button/Button.html.twig', 'Button.html.twig'), - ]); - - $component->addDependency($dependency1 = new ComponentDependency('Icon')); - $component->addDependency($dependency2 = new ComponentDependency('Label')); - $component->addDependency($dependency3 = new PhpPackageDependency('symfony/twig-component', new Version('2.24.0'))); - - self::assertCount(3, $component->getDependencies()); - self::assertEquals([$dependency1, $dependency2, $dependency3], $component->getDependencies()); - - $component->addDependency($dependency4 = new PhpPackageDependency('symfony/twig-component', new Version('2.25.0'))); - - self::assertCount(3, $component->getDependencies()); - self::assertEquals([$dependency1, $dependency2, $dependency4], $component->getDependencies()); - } - - public function testShouldNotReplacePhpPackageDependencyIfVersionIsLower() - { - $component = new Component('Button', [ - new File('templates/components/Button/Button.html.twig', 'Button.html.twig'), - ]); - - $component->addDependency($dependency1 = new ComponentDependency('Icon')); - $component->addDependency($dependency2 = new ComponentDependency('Label')); - $component->addDependency($dependency3 = new PhpPackageDependency('symfony/twig-component', new Version('2.24.0'))); - - self::assertCount(3, $component->getDependencies()); - self::assertEquals([$dependency1, $dependency2, $dependency3], $component->getDependencies()); - - $component->addDependency(new PhpPackageDependency('symfony/twig-component', new Version('2.23.0'))); - - self::assertCount(3, $component->getDependencies()); - self::assertEquals([$dependency1, $dependency2, $dependency3], $component->getDependencies()); - } -} diff --git a/src/Toolkit/tests/Asset/StimulusControllerTest.php b/src/Toolkit/tests/Asset/StimulusControllerTest.php deleted file mode 100644 index 495336e111e..00000000000 --- a/src/Toolkit/tests/Asset/StimulusControllerTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\Tests\Asset; - -use PHPUnit\Framework\TestCase; -use Symfony\UX\Toolkit\Asset\StimulusController; -use Symfony\UX\Toolkit\File\File; - -final class StimulusControllerTest extends TestCase -{ - public function testCanBeInstantiated() - { - $stimulusController = new StimulusController('clipboard', [ - new File('assets/controllers/clipboard_controller.js', 'clipboard_controller.js'), - ]); - - $this->assertSame('clipboard', $stimulusController->name); - } - - public function testShouldFailIfStimulusControllerNameIsInvalid() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid Stimulus controller name "invalid_controller".'); - - new StimulusController('invalid_controller', [new File('assets/controllers/invalid_controller.js', 'invalid_controller.js')]); - } - - public function testShouldFailIfStimulusControllerHasNoFiles() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Stimulus controller "clipboard" has no files.'); - - new StimulusController('clipboard', []); - } -} diff --git a/src/Toolkit/tests/Command/CreateKitCommandTest.php b/src/Toolkit/tests/Command/CreateKitCommandTest.php new file mode 100644 index 00000000000..7f0bf75d2ca --- /dev/null +++ b/src/Toolkit/tests/Command/CreateKitCommandTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Tests\Command; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Filesystem\Filesystem; +use Zenstruck\Console\Test\InteractsWithConsole; + +class CreateKitCommandTest extends KernelTestCase +{ + use InteractsWithConsole; + + private string $cwd; + private Filesystem $filesystem; + private string $tmpDir; + + protected function setUp(): void + { + $this->cwd = getcwd(); + + $this->filesystem = new Filesystem(); + $this->tmpDir = $this->filesystem->tempnam(sys_get_temp_dir(), 'ux_toolkit_github_'); + $this->filesystem->remove($this->tmpDir); + $this->filesystem->mkdir($this->tmpDir); + + chdir($this->tmpDir); + } + + protected function tearDown(): void + { + chdir($this->cwd); + $this->filesystem->remove($this->tmpDir); + } + + public function testShouldBeAbleToCreateAKit() + { + $this->bootKernel(); + $this->consoleCommand('ux:toolkit:create-kit') + ->addInput('MyKit') + ->addInput('http://example.com') + ->addInput('MIT') + ->execute() + ->assertSuccessful() + ->assertOutputContains('Your kit has been created successfully, happy coding!') + ; + + $this->assertStringEqualsFile( + $this->tmpDir.'/manifest.json', + <<<'JSON' + { + "$schema": "../vendor/symfony/ux-toolkit/schema-kit-v1.json", + "name": "MyKit", + "description": "A custom kit for Symfony UX Toolkit.", + "homepage": "http://example.com", + "license": "MIT" + } + JSON + ); + $this->assertStringEqualsFile( + $this->tmpDir.'/Button/manifest.json', + <<<'JSON' + { + "$schema": "../vendor/symfony/ux-toolkit/schema-kit-recipe-v1.json", + "name": "Button", + "description": "A clickable element that triggers actions or events, supporting various styles and states.", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "php", + "package": "twig/extra-bundle" + }, + { + "type": "php", + "package": "twig/html-extra:^3.12.0" + }, + { + "type": "php", + "package": "tales-from-a-dev/twig-tailwind-extra" + } + ] + } + JSON + ); + $this->assertStringEqualsFile( + $this->tmpDir.'/Button/templates/components/Button.html.twig', + <<<'TWIG' + {% props type = 'button', variant = 'default' %} + {%- set style = html_cva( + base: 'inline-flex items-center', + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + }, + }, + ) -%} + + + TWIG + ); + } +} diff --git a/src/Toolkit/tests/Command/DebugKitCommandTest.php b/src/Toolkit/tests/Command/DebugKitCommandTest.php index 752a91fef84..86a414faffa 100644 --- a/src/Toolkit/tests/Command/DebugKitCommandTest.php +++ b/src/Toolkit/tests/Command/DebugKitCommandTest.php @@ -31,24 +31,24 @@ public function testShouldBeAbleToDebug() ->assertOutputContains('License MIT') // Components details ->assertOutputContains(implode(\PHP_EOL, [ - '+--------------+----------------------- Component: "Avatar" --------------------------------------+', + '+--------------+------------------------ Recipe: "Avatar" ----------------------------------------+', '| File(s) | templates/components/Avatar.html.twig |', + '| | templates/components/Avatar/Image.html.twig |', + '| | templates/components/Avatar/Text.html.twig |', '| Dependencies | tales-from-a-dev/twig-tailwind-extra |', - '| | Avatar:Image |', - '| | Avatar:Text |', '+--------------+----------------------------------------------------------------------------------+', ])) ->assertOutputContains(implode(\PHP_EOL, [ - '+--------------+----------------------- Component: "Table" ---------------------------------------+', + '+--------------+------------------------- Recipe: "Table" ----------------------------------------+', '| File(s) | templates/components/Table.html.twig |', + '| | templates/components/Table/Body.html.twig |', + '| | templates/components/Table/Caption.html.twig |', + '| | templates/components/Table/Cell.html.twig |', + '| | templates/components/Table/Footer.html.twig |', + '| | templates/components/Table/Head.html.twig |', + '| | templates/components/Table/Header.html.twig |', + '| | templates/components/Table/Row.html.twig |', '| Dependencies | tales-from-a-dev/twig-tailwind-extra |', - '| | Table:Body |', - '| | Table:Caption |', - '| | Table:Cell |', - '| | Table:Footer |', - '| | Table:Head |', - '| | Table:Header |', - '| | Table:Row |', '+--------------+----------------------------------------------------------------------------------+', ])); } diff --git a/src/Toolkit/tests/Command/InstallCommandTest.php b/src/Toolkit/tests/Command/InstallCommandTest.php new file mode 100644 index 00000000000..6f104ab1a3f --- /dev/null +++ b/src/Toolkit/tests/Command/InstallCommandTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Tests\Command; + +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Filesystem\Filesystem; +use Zenstruck\Console\Test\InteractsWithConsole; + +class InstallCommandTest extends KernelTestCase +{ + use InteractsWithConsole; + + private Filesystem $filesystem; + private string $tmpDir; + + protected function setUp(): void + { + parent::setUp(); + + $this->bootKernel(); + $this->filesystem = self::getContainer()->get('filesystem'); + $this->tmpDir = $this->filesystem->tempnam(sys_get_temp_dir(), 'ux_toolkit_test_'); + $this->filesystem->remove($this->tmpDir); + $this->filesystem->mkdir($this->tmpDir); + } + + public function testShouldAbleToInstallComponentTableAndItsDependencies() + { + $expectedFiles = [ + 'Table/templates/components/Table.html.twig' => $this->tmpDir.'/templates/components/Table.html.twig', + 'Table/templates/components/Table/Body.html.twig' => $this->tmpDir.'/templates/components/Table/Body.html.twig', + 'Table/templates/components/Table/Caption.html.twig' => $this->tmpDir.'/templates/components/Table/Caption.html.twig', + 'Table/templates/components/Table/Cell.html.twig' => $this->tmpDir.'/templates/components/Table/Cell.html.twig', + 'Table/templates/components/Table/Footer.html.twig' => $this->tmpDir.'/templates/components/Table/Footer.html.twig', + 'Table/templates/components/Table/Head.html.twig' => $this->tmpDir.'/templates/components/Table/Head.html.twig', + 'Table/templates/components/Table/Header.html.twig' => $this->tmpDir.'/templates/components/Table/Header.html.twig', + 'Table/templates/components/Table/Row.html.twig' => $this->tmpDir.'/templates/components/Table/Row.html.twig', + ]; + + foreach ($expectedFiles as $expectedFile) { + $this->assertFileDoesNotExist($expectedFile); + } + + $testCommand = $this->consoleCommand(\sprintf('ux:install Table --destination="%s"', str_replace('\\', '\\\\', $this->tmpDir))) + ->execute() + ->assertSuccessful() + ->assertOutputContains('Installing recipe Table from the Shadcn UI kit...') + ->assertOutputContains('[OK] The recipe has been installed.') + ; + + // Files should be created + foreach ($expectedFiles as $fileName => $expectedFile) { + $testCommand->assertOutputContains($expectedFile); + $this->assertFileExists($expectedFile); + $this->assertEquals(file_get_contents(__DIR__.'/../../kits/shadcn/'.$fileName), file_get_contents($expectedFile)); + } + } + + public function testShouldFailAndSuggestAlternativeRecipesWhenKitIsExplicit() + { + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); + mkdir($destination); + + $this->bootKernel(); + $this->consoleCommand('ux:install A --kit=shadcn --destination='.$destination) + ->execute() + ->assertFaulty() + ->assertOutputContains('[WARNING] The recipe "A" does not exist') + ->assertOutputContains('Possible alternatives: "Alert", "AspectRatio", "Avatar"') + ; + } + + public function testShouldFailWhenComponentDoesNotExist() + { + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); + mkdir($destination); + + $this->bootKernel(); + $this->consoleCommand('ux:install Unknown --destination='.$destination) + ->execute() + ->assertFaulty() + ->assertOutputContains('The recipe "Unknown" does not exist'); + } + + public function testShouldWarnWhenComponentFileAlreadyExistsInNonInteractiveMode() + { + $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); + mkdir($destination); + + $this->bootKernel(); + $this->consoleCommand('ux:install Badge --destination='.$destination) + ->execute() + ->assertSuccessful(); + + $this->consoleCommand('ux:install Badge --destination='.$destination) + ->execute() + ->assertFaulty() + ->assertOutputContains('[WARNING] The recipe has not been installed.') + ; + } +} diff --git a/src/Toolkit/tests/Command/InstallComponentCommandTest.php b/src/Toolkit/tests/Command/InstallComponentCommandTest.php deleted file mode 100644 index e53e9ec017f..00000000000 --- a/src/Toolkit/tests/Command/InstallComponentCommandTest.php +++ /dev/null @@ -1,117 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\Tests\Command; - -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\Filesystem\Filesystem; -use Zenstruck\Console\Test\InteractsWithConsole; - -class InstallComponentCommandTest extends KernelTestCase -{ - use InteractsWithConsole; - - private Filesystem $filesystem; - private string $tmpDir; - - protected function setUp(): void - { - parent::setUp(); - - $this->bootKernel(); - $this->filesystem = self::getContainer()->get('filesystem'); - $this->tmpDir = $this->filesystem->tempnam(sys_get_temp_dir(), 'ux_toolkit_test_'); - $this->filesystem->remove($this->tmpDir); - $this->filesystem->mkdir($this->tmpDir); - } - - public function testShouldAbleToInstallComponentTableAndItsDependencies() - { - $expectedFiles = [ - 'Table.html.twig' => $this->tmpDir.'/Table.html.twig', - 'Table/Body.html.twig' => $this->tmpDir.'/Table/Body.html.twig', - 'Table/Caption.html.twig' => $this->tmpDir.'/Table/Caption.html.twig', - 'Table/Cell.html.twig' => $this->tmpDir.'/Table/Cell.html.twig', - 'Table/Footer.html.twig' => $this->tmpDir.'/Table/Footer.html.twig', - 'Table/Head.html.twig' => $this->tmpDir.'/Table/Head.html.twig', - 'Table/Header.html.twig' => $this->tmpDir.'/Table/Header.html.twig', - 'Table/Row.html.twig' => $this->tmpDir.'/Table/Row.html.twig', - ]; - - foreach ($expectedFiles as $expectedFile) { - $this->assertFileDoesNotExist($expectedFile); - } - - $testCommand = $this->consoleCommand(\sprintf('ux:toolkit:install-component Table --destination="%s"', str_replace('\\', '\\\\', $this->tmpDir))) - ->execute() - ->assertSuccessful() - ->assertOutputContains('Installing component Table from the Shadcn UI kit...') - ->assertOutputContains('[OK] The component has been installed.') - ; - - // Files should be created - foreach ($expectedFiles as $fileName => $expectedFile) { - $testCommand->assertOutputContains($fileName); - $this->assertFileExists($expectedFile); - $this->assertEquals(file_get_contents(__DIR__.'/../../kits/shadcn/templates/components/'.$fileName), file_get_contents($expectedFile)); - } - } - - public function testShouldFailAndSuggestAlternativeComponentsWhenKitIsExplicit() - { - $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); - mkdir($destination); - - $this->bootKernel(); - $this->consoleCommand('ux:toolkit:install-component Table: --kit=shadcn --destination='.$destination) - ->execute() - ->assertFaulty() - ->assertOutputContains('[WARNING] The component "Table:" does not exist') - ->assertOutputContains('Possible alternatives: ') - ->assertOutputContains('"Table:Body"') - ->assertOutputContains('"Table:Caption"') - ->assertOutputContains('"Table:Cell"') - ->assertOutputContains('"Table:Footer"') - ->assertOutputContains('"Table:Head"') - ->assertOutputContains('"Table:Header"') - ->assertOutputContains('"Table:Row"') - ; - } - - public function testShouldFailWhenComponentDoesNotExist() - { - $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); - mkdir($destination); - - $this->bootKernel(); - $this->consoleCommand('ux:toolkit:install-component Unknown --destination='.$destination) - ->execute() - ->assertFaulty() - ->assertOutputContains('The component "Unknown" does not exist'); - } - - public function testShouldWarnWhenComponentFileAlreadyExistsInNonInteractiveMode() - { - $destination = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid(); - mkdir($destination); - - $this->bootKernel(); - $this->consoleCommand('ux:toolkit:install-component Badge --destination='.$destination) - ->execute() - ->assertSuccessful(); - - $this->consoleCommand('ux:toolkit:install-component Badge --destination='.$destination) - ->execute() - ->assertFaulty() - ->assertOutputContains('[WARNING] The component has not been installed.') - ; - } -} diff --git a/src/Toolkit/tests/Dependency/ComponentDependencyTest.php b/src/Toolkit/tests/Dependency/ComponentDependencyTest.php deleted file mode 100644 index bd0a971ec1b..00000000000 --- a/src/Toolkit/tests/Dependency/ComponentDependencyTest.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\Tests\Dependency; - -use PHPUnit\Framework\TestCase; -use Symfony\UX\Toolkit\Dependency\ComponentDependency; - -final class ComponentDependencyTest extends TestCase -{ - public function testShouldBeInstantiable() - { - $dependency = new ComponentDependency('Table:Body'); - - $this->assertSame('Table:Body', $dependency->name); - $this->assertSame('Table:Body', (string) $dependency); - } - - public function testShouldFailIfComponentNameIsInvalid() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid component name "foobar".'); - - new ComponentDependency('foobar'); - } -} diff --git a/src/Toolkit/tests/Dependency/PhpPackageDependencyTest.php b/src/Toolkit/tests/Dependency/PhpPackageDependencyTest.php index a58d072944d..6c0ea38e042 100644 --- a/src/Toolkit/tests/Dependency/PhpPackageDependencyTest.php +++ b/src/Toolkit/tests/Dependency/PhpPackageDependencyTest.php @@ -22,10 +22,12 @@ public function testShouldBeInstantiable() $dependency = new PhpPackageDependency('twig/html-extra'); $this->assertSame('twig/html-extra', $dependency->name); $this->assertNull($dependency->constraintVersion); + $this->assertSame('PHP package "twig/html-extra"', $dependency->toDebug()); $this->assertSame('twig/html-extra', (string) $dependency); - $dependency = new PhpPackageDependency('twig/html-extra', new Version('3.2.1')); + $dependency = new PhpPackageDependency('twig/html-extra', new Version('^3.2.1')); $this->assertSame('twig/html-extra', $dependency->name); + $this->assertSame('PHP package "twig/html-extra:^3.2.1"', $dependency->toDebug()); $this->assertSame('twig/html-extra:^3.2.1', (string) $dependency); } diff --git a/src/Toolkit/tests/Dependency/RecipeDependencyTest.php b/src/Toolkit/tests/Dependency/RecipeDependencyTest.php new file mode 100644 index 00000000000..ac66318d247 --- /dev/null +++ b/src/Toolkit/tests/Dependency/RecipeDependencyTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Tests\Dependency; + +use PHPUnit\Framework\TestCase; +use Symfony\UX\Toolkit\Dependency\RecipeDependency; + +final class RecipeDependencyTest extends TestCase +{ + public function testShouldBeInstantiable() + { + $dependency = new RecipeDependency('Table'); + $this->assertSame('Table', $dependency->name); + $this->assertSame('Recipe "Table"', $dependency->toDebug()); + $this->assertSame('Table', (string) $dependency); + } +} diff --git a/src/Toolkit/tests/Dependency/StimulusControllerDependencyTest.php b/src/Toolkit/tests/Dependency/StimulusControllerDependencyTest.php deleted file mode 100644 index 164a38dab4d..00000000000 --- a/src/Toolkit/tests/Dependency/StimulusControllerDependencyTest.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\Tests\Dependency; - -use PHPUnit\Framework\TestCase; -use Symfony\UX\Toolkit\Dependency\StimulusControllerDependency; - -final class StimulusControllerDependencyTest extends TestCase -{ - public function testShouldBeInstantiable() - { - $dependency = new StimulusControllerDependency('clipboard'); - - $this->assertSame('clipboard', $dependency->name); - $this->assertSame('clipboard', (string) $dependency); - } - - public function testShouldFailIfComponentNameIsInvalid() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid Stimulus controller name "my_Controller".'); - - new StimulusControllerDependency('my_Controller'); - } -} diff --git a/src/Toolkit/tests/File/DocTest.php b/src/Toolkit/tests/File/DocTest.php deleted file mode 100644 index 02a9e6e5a91..00000000000 --- a/src/Toolkit/tests/File/DocTest.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\Tests\File; - -use PHPUnit\Framework\TestCase; -use Symfony\UX\Toolkit\File\Doc; - -final class DocTest extends TestCase -{ - public function testCanBeInstantiated() - { - $doc = new Doc( - '# Basic Button - -```twig - - Click me - -```' - ); - - self::assertEquals('# Basic Button - -```twig - - Click me - -```', $doc->markdownContent); - } -} diff --git a/src/Toolkit/tests/File/FileTest.php b/src/Toolkit/tests/File/FileTest.php deleted file mode 100644 index 984c10a3ea0..00000000000 --- a/src/Toolkit/tests/File/FileTest.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\UX\Toolkit\Tests\File; - -use PHPUnit\Framework\TestCase; -use Symfony\UX\Toolkit\File\File; - -final class FileTest extends TestCase -{ - public function testShouldFailIfPathIsNotRelative() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage(\sprintf('The path to the kit "%s" must be relative.', __FILE__.'/templates/components/Button.html.twig')); - - new File(__FILE__.'/templates/components/Button.html.twig', __FILE__.'Button.html.twig'); - } - - public function testShouldFailIfPathNameIsNotRelative() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage(\sprintf('The path name "%s" must be relative.', __FILE__.'Button.html.twig')); - - new File('templates/components/Button.html.twig', __FILE__.'Button.html.twig'); - } - - public function testShouldFailIfPathNameIsNotASubpathOfPathToKit() - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage(\sprintf('The relative path name "%s" must be a subpath of the relative path to the kit "%s".', 'foo/bar/Button.html.twig', 'templates/components/Button.html.twig')); - - new File('templates/components/Button.html.twig', 'foo/bar/Button.html.twig'); - } - - public function testCanInstantiateFile() - { - $file = new File('templates/components/Button.html.twig', 'Button.html.twig'); - - $this->assertSame('templates/components/Button.html.twig', $file->relativePathNameToKit); - $this->assertSame('Button.html.twig', $file->relativePathName); - $this->assertSame('templates/components/Button.html.twig', (string) $file); - } - - public function testCanInstantiateFileWithSubComponent() - { - $file = new File('templates/components/Table/Body.html.twig', 'Table/Body.html.twig'); - - $this->assertSame('templates/components/Table/Body.html.twig', $file->relativePathNameToKit); - $this->assertSame('Table/Body.html.twig', $file->relativePathName); - $this->assertSame('templates/components/Table/Body.html.twig', (string) $file); - } -} diff --git a/src/Toolkit/tests/FileTest.php b/src/Toolkit/tests/FileTest.php new file mode 100644 index 00000000000..21472ac4a50 --- /dev/null +++ b/src/Toolkit/tests/FileTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\UX\Toolkit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\UX\Toolkit\File; + +final class FileTest extends TestCase +{ + public function testShouldFailIfSourcePathIsNotRelative() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf('The source path "%s" must be relative.', __FILE__.'/templates/components/Button.html.twig')); + + new File(__FILE__.'/templates/components/Button.html.twig', __FILE__.'Button.html.twig'); + } + + public function testShouldFailIfDestinationPathIsNotRelative() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf('The destination path "%s" must be relative.', __FILE__.'Button.html.twig')); + + new File('templates/components/Button.html.twig', __FILE__.'Button.html.twig'); + } + + public function testCanInstantiateFile() + { + $file = new File('src-templates/components/Button.html.twig', 'dist-templates/components/Button.html.twig'); + + $this->assertSame('src-templates/components/Button.html.twig', $file->sourceRelativePathName); + $this->assertSame('dist-templates/components/Button.html.twig', $file->destinationRelativePathName); + $this->assertSame('src-templates/components/Button.html.twig', (string) $file); + } + + public function testCanInstantiateFileWithSubComponent() + { + $file = new File('src-templates/components/Table/Body.html.twig', 'dest-templates/components/Table/Body.html.twig'); + + $this->assertSame('src-templates/components/Table/Body.html.twig', $file->sourceRelativePathName); + $this->assertSame('dest-templates/components/Table/Body.html.twig', $file->destinationRelativePathName); + $this->assertSame('src-templates/components/Table/Body.html.twig', (string) $file); + } +} diff --git a/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/A/manifest.json b/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/A/manifest.json new file mode 100644 index 00000000000..31a7f4fa360 --- /dev/null +++ b/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/A/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "A", + "description": "Component A", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "recipe", + "name": "B" + } + ] +} diff --git a/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/templates/components/A.html.twig b/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/A/templates/components/A.html.twig similarity index 100% rename from src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/templates/components/A.html.twig rename to src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/A/templates/components/A.html.twig diff --git a/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/B/manifest.json b/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/B/manifest.json new file mode 100644 index 00000000000..3fa3c7de582 --- /dev/null +++ b/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/B/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "B", + "description": "Component B", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "recipe", + "name": "C" + } + ] +} diff --git a/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/templates/components/B.html.twig b/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/B/templates/components/B.html.twig similarity index 100% rename from src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/templates/components/B.html.twig rename to src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/B/templates/components/B.html.twig diff --git a/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/C/manifest.json b/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/C/manifest.json new file mode 100644 index 00000000000..49bfa56475c --- /dev/null +++ b/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/C/manifest.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../schema-kit-recipe-v1.json", + "type": "component", + "name": "C", + "description": "Component C", + "copy-files": { + "templates/": "templates/" + }, + "dependencies": [ + { + "type": "recipe", + "name": "A" + } + ] +} diff --git a/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/templates/components/C.html.twig b/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/C/templates/components/C.html.twig similarity index 100% rename from src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/templates/components/C.html.twig rename to src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/C/templates/components/C.html.twig diff --git a/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/manifest.json b/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/manifest.json index f23837787ff..5a84c88171b 100644 --- a/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/manifest.json +++ b/src/Toolkit/tests/Fixtures/kits/with-circular-components-dependencies/manifest.json @@ -1,4 +1,5 @@ { + "$schema": "../../../../schema-kit-v1.json", "name": "With Circular Components Dependencies", "description": "Kit used as a test fixture.", "license": "MIT", diff --git a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/clipboard_controller.js b/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/clipboard_controller.js deleted file mode 100644 index 3e2f4a2f79b..00000000000 --- a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/clipboard_controller.js +++ /dev/null @@ -1,5 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - // … -} diff --git a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/date_picker_controller.js b/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/date_picker_controller.js deleted file mode 100644 index 3e2f4a2f79b..00000000000 --- a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/date_picker_controller.js +++ /dev/null @@ -1,5 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - // … -} diff --git a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/local-time-controller.js b/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/local-time-controller.js deleted file mode 100644 index 3e2f4a2f79b..00000000000 --- a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/local-time-controller.js +++ /dev/null @@ -1,5 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - // … -} diff --git a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/users/list_item_controller.js b/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/users/list_item_controller.js deleted file mode 100644 index 3e2f4a2f79b..00000000000 --- a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/assets/controllers/users/list_item_controller.js +++ /dev/null @@ -1,5 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - // … -} diff --git a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/manifest.json b/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/manifest.json deleted file mode 100644 index 4589ccfdc23..00000000000 --- a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/manifest.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "With Stimulus Controllers", - "description": "Kit used as a test fixture.", - "license": "MIT", - "homepage": "https://ux.symfony.com/" -} diff --git a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/Clipboard.html.twig b/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/Clipboard.html.twig deleted file mode 100644 index 171027bc04e..00000000000 --- a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/Clipboard.html.twig +++ /dev/null @@ -1 +0,0 @@ -
{% block content %}
diff --git a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/DatePicker.html.twig b/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/DatePicker.html.twig deleted file mode 100644 index 7b4e9a8e332..00000000000 --- a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/DatePicker.html.twig +++ /dev/null @@ -1 +0,0 @@ -
{% block content %}
diff --git a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/LocalTime.html.twig b/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/LocalTime.html.twig deleted file mode 100644 index 37995d5bff0..00000000000 --- a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/LocalTime.html.twig +++ /dev/null @@ -1 +0,0 @@ -
{% block content %}
diff --git a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/UsersListItem.html.twig b/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/UsersListItem.html.twig deleted file mode 100644 index 7c7a15bde17..00000000000 --- a/src/Toolkit/tests/Fixtures/kits/with-stimulus-controllers/templates/components/UsersListItem.html.twig +++ /dev/null @@ -1 +0,0 @@ -
{% block content %}
diff --git a/src/Toolkit/tests/Functional/ComponentsRenderingTest.php b/src/Toolkit/tests/Functional/ComponentsRenderingTest.php index 6fdcbfb6cc3..7c43cb204e2 100644 --- a/src/Toolkit/tests/Functional/ComponentsRenderingTest.php +++ b/src/Toolkit/tests/Functional/ComponentsRenderingTest.php @@ -16,9 +16,10 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Filesystem\Path; use Symfony\Component\Finder\Finder; -use Symfony\UX\Toolkit\Asset\Component; use Symfony\UX\Toolkit\Kit\Kit; +use Symfony\UX\Toolkit\Kit\KitContextRunner; use Symfony\UX\Toolkit\Kit\KitFactory; +use Symfony\UX\Toolkit\Recipe\Recipe; use Symfony\UX\Toolkit\Registry\LocalRegistry; class ComponentsRenderingTest extends WebTestCase @@ -33,11 +34,11 @@ class ComponentsRenderingTest extends WebTestCase public static function provideTestComponentRendering(): iterable { foreach (LocalRegistry::getAvailableKitsName() as $kitName) { - $kitDir = Path::join(__DIR__, '../../kits', $kitName, 'docs/components'); - $docsFinder = (new Finder())->files()->name('*.md')->in($kitDir)->depth(0); + $kitDir = Path::join(__DIR__, '../../kits', $kitName); + $docsFinder = (new Finder())->files()->name('EXAMPLES.md')->in($kitDir)->depth(1); foreach ($docsFinder as $docFile) { - $componentName = $docFile->getFilenameWithoutExtension(); + $componentName = $docFile->getRelativePath(); $codeBlockMatchesResult = preg_match_all('/```twig.*?\n(?P.+?)```/s', $docFile->getContents(), $codeBlockMatches); if (false === $codeBlockMatchesResult || 0 === $codeBlockMatchesResult) { @@ -54,16 +55,17 @@ public static function provideTestComponentRendering(): iterable /** * @dataProvider provideTestComponentRendering */ - public function testComponentRendering(string $kitName, string $componentName, string $code) + public function testComponentRendering(string $kitName, string $recipeName, string $code) { $twig = self::getContainer()->get('twig'); + /** @var KitContextRunner $kitContextRunner */ $kitContextRunner = self::getContainer()->get('ux_toolkit.kit.kit_context_runner'); $kit = $this->instantiateKit($kitName); $template = $twig->createTemplate($code); $renderedCode = $kitContextRunner->runForKit($kit, fn () => $template->render()); - $this->assertCodeRenderedMatchesHtmlSnapshot($kit, $kit->getComponent($componentName), $code, $renderedCode); + $this->assertCodeRenderedMatchesHtmlSnapshot($kit, $kit->getRecipe($recipeName), $code, $renderedCode); } private function instantiateKit(string $kitName): Kit @@ -75,9 +77,10 @@ private function instantiateKit(string $kitName): Kit return $kitFactory->createKitFromAbsolutePath(Path::join(__DIR__, '../../kits', $kitName)); } - private function assertCodeRenderedMatchesHtmlSnapshot(Kit $kit, Component $component, string $code, string $renderedCode): void + private function assertCodeRenderedMatchesHtmlSnapshot(Kit $kit, Recipe $recipe, string $code, string $renderedCode): void { - $info = \sprintf(<< HTML, - $kit->name, - $component->name, + $kit->manifest->name, + $recipe->manifest->name, trim($code) ); diff --git a/src/Toolkit/tests/Functional/__snapshots__/ComponentsRenderingTest__testComponentRendering with data set Kit shadcn, component Alert, code 2__1.html b/src/Toolkit/tests/Functional/__snapshots__/ComponentsRenderingTest__testComponentRendering with data set Kit shadcn, component Alert, code 2__1.html index 2ea8f8f9a3d..62a955b45b7 100644 --- a/src/Toolkit/tests/Functional/__snapshots__/ComponentsRenderingTest__testComponentRendering with data set Kit shadcn, component Alert, code 2__1.html +++ b/src/Toolkit/tests/Functional/__snapshots__/ComponentsRenderingTest__testComponentRendering with data set Kit shadcn, component Alert, code 2__1.html @@ -3,18 +3,18 @@ - Component: Alert - Code: ```twig - - - Heads up! + + + Error - You can add components to your app using the cli. + Your session has expired. Please log in again. ``` - Rendered code (prettified for testing purposes, run "php vendor/bin/phpunit -d --update-snapshots" to update snapshots): --> -