diff --git a/.gitignore b/.gitignore
index a0a9d64..7dd9fa5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
/var/
/vendor/
+/report/
.phpunit.cache
+.phpunit.result.cache
composer.lock
.php-cs-fixer.cache
diff --git a/README.md b/README.md
index 1b5ae90..0d3cb30 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,20 @@
## Twic Doc Bundle
-Allows you to create an overview for your Twig Components, be it either UX-Components, UX-Live-Components or simple snippet templates.
+[![Image](docs/resources/images/qossmic.png)](https://qossmic.com) Brought to you by qossmic!
-Components will be grouped in categories and optional sub-categories.
+1. [Installation](#installation)
+2. Configuration
+ 1. [Bundle Configuration](docs/BundleConfiguration.md)
+ 2. [Component Configuration](docs/ComponentConfiguration.md)
+3. [Routing](#routing)
+4. [Customization](#customizing-the-design)
+5. [Usage](docs/Usage.md)
+
+---
-The categories must be configured in the bundle-configuration, see below.
+Allows you to create an overview for your Twig Components, be it either [UX-Components](https://symfony.com/bundles/ux-twig-component/current/index.html), [UX-Live-Components](https://symfony.com/bundles/ux-live-component/current/index.html) or simple snippet templates.
+
+Components will be grouped in categories and optional sub-categories.
### Installation
@@ -38,110 +48,8 @@ twig_doc:
# or for localized: prefix: /{_locale}/twig/doc/
```
-### Template
-
-To use your design in the documentation, you have to override the component template.
-
-Create a template in your project: templates/bundles/TwigDocBundle/component.html.twig
-
-```twig
-{% extends '@!TwigDoc/component.html.twig' %}
-
-{% block stylesheets %}
-
-{% endblock %}
-```
-
-#### Customizing the documentation
-
-If you want to customize the documentation, you can override the template.
-
-Create a template in your project: templates/bundles/TwigDocBundle/documentation.html.twig
-
-```twig
-{% extends '@!TwigDoc/documentation.html.twig' %}
-
-{% block stylesheets %}
-
-{% endblock %}
-```
-
-### Configuration
+### Customizing the design
-Create a config file: configs/packages/twig_doc.yaml
+To customize the design of the documentation, you can override any template of the bundle in your project.
-#### Configure categories
-
-```yaml
-twig_doc:
- categories:
- - name: Button
- sub_categories:
- - Action
- - Submit
- - name: Heading
- - name: Notification
- sub_categories:
- - Alert
- - Flash
-#... component config
-```
-
-#### Example for a Twig UX-Component
-
-```yaml
-twig_doc:
- #... categories config
- components:
- - name: ActionButton # will render %kernel.project_dir%/templates/components/ActionButton.html.twig
- title: Action Button
- description: An Action button
- category: Buttons
- sub_category: Action
- tags:
- - buttons
- parameters:
- color: String
- text: String
- link: String
- variations:
- secondary:
- color: secondary
- text: Secondary Button
- link: '#'
- primary:
- color: primary
- text: Primary Button
- link: '#'
-```
-The bundle will look for this component in the folder configured for the ux-twig-component bundle (default: %kernel.project_dir%/templates/components/COMPONENT_NAME.html.twig).
-
-#### Example for a non ux-twig-component
-
-The only difference is that non-ux components use another default-path and the naming is not specified.
-
-```yaml
-twig_doc:
- #... categories config
- components:
- - name: snippets/alert # will render %kernel.project_dir%/templates/snippets/alert.html.twig
- title: Alert
- description: non twig-ux-component component
- category: Notification
- sub_category:
- - Alert
- tags:
- - highlight
- - nameIt
- parameters:
- type: String
- msg: String
- variations:
- primary:
- type: primary
- msg: Primary Alert
- danger:
- type: danger
- msg: Danger Alert
-```
-The bundle will look for this template in the twig template folder (default: %kernel.project_dir%/templates/COMPONENT_NAME.html.twig).
+See: [How to override any Part of a Bundle](https://symfony.com/doc/current/bundles/override.html)
diff --git a/composer.json b/composer.json
index 4c08521..ca8b1bb 100644
--- a/composer.json
+++ b/composer.json
@@ -18,12 +18,14 @@
"php": ">=8.2",
"symfony/framework-bundle": "^6.4|^7.0",
"symfony/validator": "^6.4|^7.0",
- "symfony/twig-bundle": "^6.4|^7.0"
+ "symfony/twig-bundle": "^6.4|^7.0",
+ "symfony/yaml": "^6.4|^7.0"
},
"require-dev": {
"phpunit/phpunit": "^10.5",
"symfony/browser-kit": "^6.4|^7.0",
"symfony/css-selector": "^6.4|^7.0",
+ "symfony/dom-crawler": "^6.4|^7.0",
"friendsofphp/php-cs-fixer": "^3.51"
}
}
diff --git a/docs/BundleConfiguration.md b/docs/BundleConfiguration.md
new file mode 100644
index 0000000..972791f
--- /dev/null
+++ b/docs/BundleConfiguration.md
@@ -0,0 +1,68 @@
+## Configuring the bundle
+
+When you do not want to customize, you do not need any config file.
+
+The bundle provides following defaults:
+
+```yaml
+twig_doc:
+ doc_identifier: TWIG_DOC
+ directories:
+ - '%twig.default_path%/components'
+ categories:
+ - name: Components
+```
+
+### Directories
+
+By default, the bundle looks for your components in this directory: `%twig.default_path%/components`
+
+You can provide additional directories in the config-file:
+
+```yaml
+twig_doc:
+ directories:
+ - '%twig.default_path%/snippets'
+ - '%kernel.project_dir%/resources/components'
+```
+
+### Documentation identifier
+
+By default, the bundle uses this identifier: `TWIG_DOC`
+
+To use another one:
+
+```yaml
+twig_doc:
+ doc_identifier: 'MY_DOC_IDENTIFIER'
+```
+
+In your component template, you can then mark up your documentation in the template:
+
+```twig
+{#MY_DOC_IDENTIFIER
+title: My component
+...
+MY_DOC_IDENTIFIER#}
+
+```
+
+### Categories
+
+The bundle groups components into categories and optionally into sub-categories.
+
+Example:
+
+```yaml
+twig_doc:
+ categories:
+ - name: Buttons
+ sub_categories:
+ - Action
+ - Submit
+ - name: Headings
+ - name: Alerts
+...
+```
+
+The default category is always merged into the configuration.
diff --git a/docs/ComponentConfiguration.md b/docs/ComponentConfiguration.md
new file mode 100644
index 0000000..6b6b2cd
--- /dev/null
+++ b/docs/ComponentConfiguration.md
@@ -0,0 +1,79 @@
+### Component Configuration
+
+You have two possibilities to let the bundle know of your components:
+
+1. Directly in the template of the component itself (you should stick to this)
+2. In the config file
+
+You can use both possibilities, but it is recommended to use only one to avoid scattering documentation over different places.
+
+When you do not provide a category for the component, it will be added to the default-category.
+
+#### In Template
+
+We "abused" the comment tag from twig to allow the component configuration directly in the template.
+This won't hurt twig at all as comments are totally ignored by the twig-parser.
+
+When providing the config in the template, you do not need to provide the name, this is automatically resolved from the template file.
+
+```twig
+{#TWIG_DOC
+ title: Fancy Button
+ description: This is a really fancy button
+ category: Buttons
+ tags:
+ - button
+ parameters:
+ type: String
+ text: String
+ variations:
+ primary:
+ type: primary
+ text: Hello World
+ secondary:
+ type: secondary
+ text: Welcome to Hell!
+#TWIG_DOC}
+
+
+```
+
+#### Config file
+
+This is only recommended for small sets of components.
+
+The bundle tries to resolve the path of the template in a compiler pass based on the name of the component.
+
+E.g.: name: Button -> bundle looks for a Button.html.twig
+
+For this to work, you need to ensure that your components are unique among all configured directories.
+
+```yaml
+...
+components:
+ - name: Button
+ title: Fancy Button
+ description: This is a really fancy button
+ category: Buttons
+ tags:
+ - button
+ parameters:
+ type: String
+ text: String
+ variations:
+ primary:
+ type: primary
+ text: Hello World
+ secondary:
+ type: secondary
+ text: Welcome to Hell!
+```
+
+Alternatively, you can provide a path for your component in the configuration (parameters are resolved automatically):
+
+```yaml
+...
+components:
+ - name: Button
+ path: '%twig.default_path%/snippets/FancyButton.html.twig'
+```
diff --git a/docs/Usage.md b/docs/Usage.md
new file mode 100644
index 0000000..c4668ad
--- /dev/null
+++ b/docs/Usage.md
@@ -0,0 +1,5 @@
+## Usage
+
+TODO
+- screenshots of UI
+- searching
diff --git a/docs/resources/images/qossmic.png b/docs/resources/images/qossmic.png
new file mode 100644
index 0000000..13cc823
Binary files /dev/null and b/docs/resources/images/qossmic.png differ
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 68e3ccf..b435231 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,7 +1,7 @@
-
+
diff --git a/src/Component/ComponentCategory.php b/src/Component/ComponentCategory.php
index 5e5ad60..fa3cf7e 100644
--- a/src/Component/ComponentCategory.php
+++ b/src/Component/ComponentCategory.php
@@ -11,6 +11,8 @@
*/
class ComponentCategory
{
+ public const DEFAULT_CATEGORY = 'Components';
+
private ?ComponentCategory $parent = null;
#[Assert\Regex('/^\w+$/')]
diff --git a/src/Component/ComponentItem.php b/src/Component/ComponentItem.php
index 7cc812e..04fc317 100644
--- a/src/Component/ComponentItem.php
+++ b/src/Component/ComponentItem.php
@@ -25,6 +25,12 @@ class ComponentItem
private array $variations;
#[Assert\Valid]
private ComponentCategory $category;
+ #[Assert\Length(max: 4096)]
+ #[Assert\NotBlank]
+ private string $projectPath;
+ #[Assert\Length(max: 4096)]
+ #[Assert\NotBlank]
+ private string $renderPath;
public function getName(): string
{
@@ -142,4 +148,28 @@ public function getMainCategory(): ComponentCategory
{
return $this->category->getParent() ?? $this->category;
}
+
+ public function getProjectPath(): ?string
+ {
+ return $this->projectPath;
+ }
+
+ public function setProjectPath(?string $projectPath): self
+ {
+ $this->projectPath = $projectPath;
+
+ return $this;
+ }
+
+ public function getRenderPath(): ?string
+ {
+ return $this->renderPath;
+ }
+
+ public function setRenderPath(?string $renderPath): self
+ {
+ $this->renderPath = $renderPath;
+
+ return $this;
+ }
}
diff --git a/src/Component/ComponentItemFactory.php b/src/Component/ComponentItemFactory.php
index 16c4986..146338c 100644
--- a/src/Component/ComponentItemFactory.php
+++ b/src/Component/ComponentItemFactory.php
@@ -28,7 +28,7 @@ public function create(array $data): ComponentItem
isset($data['sub_category']) ? 'sub_category' : 'category',
$data['sub_category'] ?? $data['category'],
implode(', ', array_keys($this->categoryService->getCategories())),
- implode(', ', array_keys($this->categoryService->getSubCategories()))
+ implode(', ', array_map(fn (ComponentCategory $category) => $category->getName(), $this->categoryService->getSubCategories()))
)
);
throw new InvalidComponentConfigurationException($violations);
@@ -55,11 +55,33 @@ private function createItem(array $data): ComponentItem
->setParameters($data['parameters'] ?? [])
->setVariations($data['variations'] ?? [
'default' => $this->createVariationParameters($data['parameters'] ?? []),
- ]);
+ ])
+ ->setProjectPath($data['path'] ?? '')
+ ->setRenderPath($data['renderPath'] ?? '');
return $item;
}
+ public function getParamsFromVariables(array $variables): array
+ {
+ $r = [];
+ foreach ($variables as $dotted) {
+ $keys = explode('.', $dotted);
+ $c = &$r[array_shift($keys)];
+ foreach ($keys as $key) {
+ if (isset($c[$key]) && $c[$key] === true) {
+ $c[$key] = [];
+ }
+ $c = &$c[$key];
+ }
+ if ($c === null) {
+ $c = 'Scalar';
+ }
+ }
+
+ return $r;
+ }
+
public function createVariationParameters(array $parameters): array
{
$params = [];
diff --git a/src/Configuration/ParserInterface.php b/src/Configuration/ParserInterface.php
new file mode 100644
index 0000000..4288eff
--- /dev/null
+++ b/src/Configuration/ParserInterface.php
@@ -0,0 +1,8 @@
+fixIndentation($data);
+
+ return Yaml::parse($content);
+ }
+
+ private function fixIndentation(string $content): string
+ {
+ $fileObject = new \SplFileObject('php://memory', 'r+');
+ $fileObject->fwrite($content);
+ $fileObject->rewind();
+
+ $firstLineDetected = false;
+ $indentationWhitespace = null;
+
+ $lines = [];
+
+ while ($fileObject->valid()) {
+ $line = $fileObject->current();
+ if (empty(trim($line))) {
+ $fileObject->next();
+ continue;
+ }
+ if ($firstLineDetected === false) {
+ $firstLineDetected = true;
+ // check for whitespaces at the beginning
+ if (!preg_match('#^(\s+)#', $line, $matches)) {
+ // no leading whitespaces, indentation seems to be fine
+ return $content;
+ }
+ $indentationWhitespace = $matches[1];
+ }
+ $line = substr($line, \strlen($indentationWhitespace));
+ $lines[] = $line;
+ $fileObject->next();
+ }
+
+ return implode("\n", $lines);
+ }
+}
diff --git a/src/Controller/TwigDocController.php b/src/Controller/TwigDocController.php
index 273e68d..a47c05d 100644
--- a/src/Controller/TwigDocController.php
+++ b/src/Controller/TwigDocController.php
@@ -52,11 +52,8 @@ public function componentView(Request $request): Response
if (!$component) {
throw new NotFoundHttpException(sprintf('Component %s is unknown', $name));
}
- $breakpoint = $request->query->get('breakpoint');
// disable profiler to get rid of toolbar in dev
- if ($this->profiler) {
- $this->profiler->disable();
- }
+ $this->profiler?->disable();
return new Response(
$this->twig->render('@TwigDoc/component.html.twig', [
diff --git a/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php
new file mode 100644
index 0000000..7476460
--- /dev/null
+++ b/src/DependencyInjection/Compiler/TwigDocCollectDocsPass.php
@@ -0,0 +1,133 @@
+hasExtension('twig_doc')) {
+ return;
+ }
+ $config = $container->getParameter('twig_doc.config');
+ $directories = $this->resolveDirectories($container, $config['directories']);
+ $container->getParameterBag()->remove('twig_doc.config');
+
+ if (empty($directories)) {
+ return;
+ }
+
+ $definition = $container->getDefinition('twig_doc.service.component');
+ $componentConfig = $this->enrichComponentsConfig($container, $directories, $definition->getArgument('$componentsConfig'));
+ $projectDir = $container->getParameter('kernel.project_dir');
+ $templateDir = $container->getParameter('twig.default_path');
+
+ foreach ($directories as $directory) {
+ // add resource to container to rebuild container on changes in templates
+ $container->addResource(new DirectoryResource($directory));
+ }
+
+ $finder = new Finder();
+ foreach ($finder->in($directories)->files()->filter(fn (SplFileInfo $file) => $file->getExtension() === 'twig') as $file) {
+ $doc = $this->parseDoc($file, $config['doc_identifier']);
+
+ if ($doc === null) {
+ continue;
+ }
+
+ $filename = $file->getFilename();
+ $componentName = substr($filename, 0, strpos($filename, '.'));
+
+ if (array_filter($componentConfig, fn (array $data) => $data['name'] === $componentName)) {
+ throw new InvalidConfigException(sprintf('component "%s" is configured twice, please configure either directly in the template or the general bundle configuration', $componentName));
+ }
+ $itemConfig = [
+ 'name' => $componentName,
+ 'path' => str_replace($projectDir.'/', '', $file->getRealPath()),
+ 'renderPath' => str_replace($templateDir.'/', '', $file->getRealPath()),
+ 'category' => ComponentCategory::DEFAULT_CATEGORY,
+ ];
+ $componentConfig[] = array_merge($itemConfig, $doc);
+ }
+
+ $definition->replaceArgument('$componentsConfig', $componentConfig);
+ }
+
+ private function parseDoc(SplFileInfo $file, string $docIdentifier): ?array
+ {
+ $content = $file->getContents();
+
+ $pattern = sprintf("/\{#%s\s(.*)%s#}/s", $docIdentifier, $docIdentifier);
+
+ preg_match($pattern, $content, $matches);
+
+ if (!isset($matches[1])) {
+ return null;
+ }
+
+ return $this->parser->parse($matches[1]);
+ }
+
+ private function enrichComponentsConfig(ContainerBuilder $container, array $directories, array $components): array
+ {
+ foreach ($components as &$component) {
+ if (!isset($component['path'])) {
+ $component['path'] = str_replace($container->getParameter('kernel.project_dir').'/', '', $this->getTemplatePath($component['name'], $directories));
+ }
+ if (!isset($component['renderPath'])) {
+ $component['renderPath'] = str_replace($container->getParameter('twig.default_path').'/', '', $component['path']);
+ }
+ }
+
+ return $components;
+ }
+
+ private function resolveDirectories(ContainerBuilder $container, array $directories): array
+ {
+ $directories[] = $container->getParameter('twig.default_path').'/components';
+
+ $directories = array_map(fn (string $dir) => $container->getParameterBag()->resolveValue($dir), $directories);
+
+ foreach ($directories as $idx => $dir) {
+ if (!is_dir($dir)) {
+ unset($directories[$idx]);
+ }
+ }
+
+ return array_unique($directories);
+ }
+
+ private function getTemplatePath(string $name, array $directories): ?string
+ {
+ $template = sprintf('%s.html.twig', $name);
+
+ $finder = new Finder();
+
+ $files = $finder->in($directories)->files()->filter(fn (SplFileInfo $file) => $file->getFilename() === $template);
+
+ if ($files->count() > 1) {
+ return null;
+ }
+
+ if (!$files->hasResults()) {
+ return null;
+ }
+
+ $files->getIterator()->rewind();
+
+ return $files->getIterator()->current();
+ }
+}
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index 5ea5e44..f1c64b4 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -13,6 +13,15 @@ public function getConfigTreeBuilder(): TreeBuilder
$treeBuilder->getRootNode()
->children()
+ ->scalarNode('doc_identifier')->defaultValue('TWIG_DOC')
+ ->validate()
+ ->ifTrue(fn ($v) => !preg_match('#^\w+$#', $v))
+ ->thenInvalid('The twig_doc documentation identifier must match \w (regex)')
+ ->end()
+ ->end()
+ ->arrayNode('directories')->defaultValue(['%twig.default_path%/components'])
+ ->scalarPrototype()->end()
+ ->end()
->arrayNode('categories')
->arrayPrototype()
->children()
@@ -23,7 +32,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end()
- ->arrayNode('components')
+ ->arrayNode('components')->defaultValue([])
->arrayPrototype()
->children()
->scalarNode('name')->isRequired()->cannotBeEmpty()->end()
diff --git a/src/DependencyInjection/TwigDocExtension.php b/src/DependencyInjection/TwigDocExtension.php
index b7ac68a..9592ce3 100644
--- a/src/DependencyInjection/TwigDocExtension.php
+++ b/src/DependencyInjection/TwigDocExtension.php
@@ -2,6 +2,7 @@
namespace Qossmic\TwigDocBundle\DependencyInjection;
+use Qossmic\TwigDocBundle\Component\ComponentCategory;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
@@ -14,13 +15,17 @@ public function load(array $configs, ContainerBuilder $container): void
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
+ $container->setParameter('twig_doc.config', $config);
+
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../../config'));
$loader->load('documentation.php');
$definition = $container->getDefinition('twig_doc.service.component');
$definition->setArgument('$componentsConfig', $config['components']);
+ $categories = array_merge([['name' => ComponentCategory::DEFAULT_CATEGORY]], $config['categories']);
+
$definition = $container->getDefinition('twig_doc.service.category');
- $definition->setArgument('$categoriesConfig', $config['categories']);
+ $definition->setArgument('$categoriesConfig', $categories);
}
}
diff --git a/src/Service/ComponentService.php b/src/Service/ComponentService.php
index 90222cf..40b596f 100644
--- a/src/Service/ComponentService.php
+++ b/src/Service/ComponentService.php
@@ -124,7 +124,7 @@ private function filterComponents(string $filterQuery, string $filterType): arra
}
/**
- * @return ComponentItem[]
+ * @return ComponentInvalid[]
*/
public function getInvalidComponents(): array
{
@@ -133,6 +133,6 @@ public function getInvalidComponents(): array
public function getComponent(string $name): ?ComponentItem
{
- return array_values($this->filterComponents($name, 'name'))[0] ?? null;
+ return array_values(array_filter($this->components, fn (ComponentItem $c) => $c->getName() === $name))[0] ?? null;
}
}
diff --git a/src/Twig/TwigDocExtension.php b/src/Twig/TwigDocExtension.php
index 8adba61..19e0dd2 100644
--- a/src/Twig/TwigDocExtension.php
+++ b/src/Twig/TwigDocExtension.php
@@ -58,6 +58,8 @@ public function renderComponent(ComponentItem $item, array $params): string
/**
* @return ComponentInvalid[]
+ *
+ * @codeCoverageIgnore
*/
public function getInvalidComponents(): array
{
@@ -66,6 +68,8 @@ public function getInvalidComponents(): array
/**
* @return ComponentCategory[]
+ *
+ * @codeCoverageIgnore
*/
public function getSubCategories(?string $mainCategoryName = null): array
{
@@ -79,6 +83,6 @@ public function getSubCategories(?string $mainCategoryName = null): array
*/
private function renderFallback(ComponentItem $item, array $params): string
{
- return $this->twig->render($item->getName().'.html.twig', $params);
+ return $this->twig->render($item->getRenderPath(), $params);
}
}
diff --git a/src/TwigDocBundle.php b/src/TwigDocBundle.php
index a6a9500..9ad2f9f 100644
--- a/src/TwigDocBundle.php
+++ b/src/TwigDocBundle.php
@@ -4,10 +4,20 @@
namespace Qossmic\TwigDocBundle;
+use Qossmic\TwigDocBundle\Configuration\YamlParser;
+use Qossmic\TwigDocBundle\DependencyInjection\Compiler\TwigDocCollectDocsPass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class TwigDocBundle extends Bundle
{
+ public function build(ContainerBuilder $container): void
+ {
+ parent::build($container);
+
+ $container->addCompilerPass(new TwigDocCollectDocsPass(new YamlParser()));
+ }
+
public function getPath(): string
{
return __DIR__.'/..';
diff --git a/templates/blocks/component_blocks.html.twig b/templates/blocks/component_blocks.html.twig
new file mode 100644
index 0000000..358c945
--- /dev/null
+++ b/templates/blocks/component_blocks.html.twig
@@ -0,0 +1,15 @@
+{% block language %}en{% endblock %}
+{% block head %}
+ {% block stylesheets %}
+ {% endblock %}
+ {% block title %}Twig Component Documentation{% endblock %}
+ {% block javascripts %}
+ {% endblock %}
+
+{% endblock %}
+
+{% block component %}
+ {% for i in 1..quantity %}
+ {{ renderComponent(component, componentData ?? [])|raw }}
+ {% endfor %}
+{% endblock%}
diff --git a/templates/blocks/page_blocks.html.twig b/templates/blocks/page_blocks.html.twig
new file mode 100644
index 0000000..b17b065
--- /dev/null
+++ b/templates/blocks/page_blocks.html.twig
@@ -0,0 +1,75 @@
+{% block head %}
+{% block stylesheets %}
+
+{% endblock %}
+ {% block title %}Twig Component Documentation{% endblock %}
+{% block javascripts %}
+
+{% endblock %}
+{% endblock %}
+
+{% block body %}
+
+
+ {% include '@TwigDoc/component/_search.html.twig' %}
+
+ {% if not components %}
+