Skip to content

Commit 6bfa523

Browse files
WebMambakbond
authored andcommitted
Introduce cookbook documentation
1 parent bdd3107 commit 6bfa523

File tree

17 files changed

+607
-2
lines changed

17 files changed

+607
-2
lines changed
477 KB
Loading

ux.symfony.com/assets/styles/app.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
@import "components/ProductGrid";
8383
@import "components/PackageHeader";
8484
@import "components/PackageBox";
85+
@import "components/Cookbook";
8586
@import "components/Tabs";
8687
@import "components/Tag";
8788
@import "components/Terminal";
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
.Cookbook {
2+
h1 {
3+
margin-top: 3rem;
4+
margin-bottom: 1rem;
5+
text-align: center;
6+
font-size: 52px;
7+
font-weight: 700;
8+
line-height: 60px;
9+
}
10+
11+
.description {
12+
text-align: center;
13+
font-size: 24px;
14+
font-weight: 600;
15+
margin-top: 1.5rem;
16+
}
17+
18+
.tags {
19+
display: flex;
20+
justify-content: center;
21+
align-items: center;
22+
width: 100%;
23+
gap: 1rem;
24+
text-decoration: none;
25+
list-style: none;
26+
margin-bottom: 3rem;
27+
28+
li {
29+
background-color: rgb(74 29 150);
30+
color: rgb(202 191 253);
31+
font-weight: 500;
32+
font-size: 0.75rem;
33+
line-height: 1rem;
34+
padding: .125rem .625rem;
35+
border-radius: 0.25rem;
36+
}
37+
}
38+
39+
.image-title {
40+
width: 100%;
41+
max-height: 40vh;
42+
overflow: hidden;
43+
border-radius: 4px;
44+
margin-bottom: 3rem;
45+
46+
img {
47+
display: block;
48+
object-fit: contain;
49+
width: 100%;
50+
}
51+
}
52+
53+
.content {
54+
h2 {
55+
margin-top: 3rem;
56+
margin-bottom: 1rem;
57+
font-size: 32px;
58+
font-weight: 700;
59+
line-height: 40px;
60+
}
61+
62+
h3 {
63+
margin-top: 3rem;
64+
margin-bottom: 1rem;
65+
font-size: 24px;
66+
font-weight: 700;
67+
line-height: 32px;
68+
color: #FFFFFF;
69+
}
70+
}
71+
72+
pre {
73+
margin-top: 4rem;
74+
margin-bottom: 2rem;
75+
border-radius: 4px;
76+
background-color: #0A0A0A;
77+
padding: 2rem;
78+
}
79+
}

ux.symfony.com/composer.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ux.symfony.com/config/services.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ services:
1010
_defaults:
1111
autowire: true # Automatically injects dependencies in your services.
1212
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
13+
bind:
14+
string $cookbookPath: '%kernel.project_dir%/cookbook'
1315

1416
# makes classes in src/ available to be used as services
1517
# this creates a service per class whose id is the fully-qualified class name
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
---
2+
title: Architecture component
3+
description: Rules and pattern to work with components
4+
image: images/cookbook/component_architecture.png
5+
tags:
6+
- javascript
7+
- symfony
8+
---
9+
10+
## Introduction
11+
12+
In SymfonyUX exist two packages: [TwigComponents](https://symfony.com/bundles/ux-twig-component/current/index.html) and [LiveComponent](https://symfony.com/bundles/ux-live-component/current/index.html).
13+
Those two packages allow you to create reusable components in your Symfony application.
14+
But the component architecture is not exclusive to Symfony, it is a design pattern that can be applied to any programming language or framework.
15+
And the js world already implement this architecture for long time, on many different frameworks like React, Vue, or Svelte.
16+
So, a set of rules and pattern has already be defined to work with components. This is why SymfonyUX try to be as close as possible to those rules.
17+
So let's see what are those rules!
18+
19+
## 4 Rules
20+
21+
### Composition
22+
23+
A page is no longer just a page, but rather a collection of small, reusable components.
24+
These components can be assembled to form a page. For example, there could be a component for the title and another for the training list.
25+
The training list component could even be composed of smaller components, such as a training card component.
26+
The goal is to create the most atomic, and reusable components possible.
27+
28+
#### How does it work into Symfony?
29+
30+
In Symfony you can have a component Alert for example with the following template:
31+
32+
```twig
33+
<div class="alert alert-{{ type }}">
34+
<twig:Icon name="{{ icon }}" />
35+
{{ message }}
36+
</div>
37+
```
38+
39+
So here you can see we have an alert component that his himself use an Icon component.
40+
Or you can make composition with the following syntax:
41+
42+
```twig
43+
<twig:Card>
44+
<twig:CardHeader>
45+
<h2>My Card</h2>
46+
</twig:CardHeader>
47+
<twig:CardBody>
48+
<p>This is the content of my card.</p>
49+
</twig:CardBody>
50+
</twig:Card>
51+
```
52+
53+
So here we Card component, and we give to the content of this component mutliple other components.
54+
55+
### Independence
56+
57+
This is a really important rule, and not obvious. But your component should leave on his own context,
58+
he should not be aware of the rest of the page. You should to talk one component into a page, to another and it should work exactly the same.
59+
This rule make your component trully reusable.
60+
61+
***How does it work into Symfony?***
62+
63+
Symfony keep the context of the page into the context of your component. So this your own responsability to follow this rules.
64+
But notice that if there are conflic between a variable from the context page and your component, your component context override the page context.
65+
66+
### Props
67+
68+
Our component must remain independent, but we can customize it props.
69+
Let's take the example of a button component. You have your component that look on every page the same,
70+
the only change is the label. What you can do is to declare a prop `label` into your button component.
71+
And so now when you want to use your button component, you can pass the label you want as props. The component gonna take
72+
this props at his initialization and keep it all his life long.
73+
74+
***How does it work into Symfony?***
75+
76+
Let's take the example of the Alert component an [anonymous component](https://symfony.com/bundles/ux-twig-component/current/index.html#anonymous-components).
77+
We have the following template:
78+
79+
```twig
80+
{% props type, icon, message %}
81+
82+
<div class="alert alert-{{ type }}">
83+
<twig:Icon name="{{ icon }}" />
84+
{{ message }}
85+
</div>
86+
```
87+
88+
Just like that we define three props for our Alert component. And know we can use like this:
89+
90+
```twig
91+
<twig:Alert type="success" icon="check" message="Your account has been created." />
92+
```
93+
94+
If your component anonymous but a class component, you can simply define props
95+
by adding property to your class.
96+
97+
```php
98+
class Alert
99+
{
100+
public string $type;
101+
public string $icon;
102+
public string $message;
103+
}
104+
```
105+
106+
There are something really important to notice with props. It's your props
107+
should only go into one direction from the parent to child. But your props should never
108+
go up. **If your child need to change something in the parent, you should use events**.
109+
110+
### State
111+
112+
A state is pretty much like a prop but the main difference is a state can
113+
change during the life of the component. Let's take the example of a button component.
114+
You can have a state `loading` that can be `true` or `false`. When the button is clicked
115+
the state `loading` can be set to `true` and the button can display a loader instead of the label.
116+
And when the loading is done, the state `loading` can be set to `false` and the button can display the label again.
117+
118+
***How does it work into Symfony?***
119+
120+
In symfony you 2 different approach to handle state. The first one is to use stimulus directly
121+
in to your component. What we recommend to do is to set a controller stimulus at the root of your component.
122+
123+
```twig
124+
{% props label %}
125+
126+
<button data-controller="button" data-button-label="{{ label }}">
127+
{{ label }}
128+
</button>
129+
```
130+
131+
And then you can define your controller like this:
132+
133+
```js
134+
import { Controller } from 'stimulus';
135+
136+
export default class extends Controller {
137+
static values = { label: String };
138+
139+
connect() {
140+
this.element.textContent = this.labelValue;
141+
}
142+
143+
loading() {
144+
this.element.textContent = 'Loading...';
145+
}
146+
}
147+
```
148+
149+
The second approach is to use the [LiveComponent](https://symfony.com/bundles/ux-live-component/current/index.html) package.
150+
How to choose between the two? If your component don't need any backend logic
151+
for his state keep it simple and use stimulus approach. But if you need to handle
152+
backend logic for your state, use LiveComponent.
153+
With live component a live prop is a state. So if you want store the number of click on a button you can do
154+
the following component:
155+
156+
```php
157+
<?php
158+
159+
#[AsLiveComponent]
160+
class Button
161+
{
162+
#[LiveProp]
163+
public int $clicks = 0;
164+
165+
public function increment()
166+
{
167+
$this->clicks++;
168+
169+
$this->save();
170+
}
171+
}
172+
```
173+
174+
## Conclusion
175+
176+
Even in Symfony, you can use the component architecture.
177+
Follow those rules help your front developpers working on codebase
178+
their are familiar with since those rules are already used in the js world.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace App\Controller;
13+
14+
use App\Service\CookbookFactory;
15+
use App\Service\CookbookRepository;
16+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
17+
use Symfony\Component\HttpFoundation\Response;
18+
use Symfony\Component\Routing\Attribute\Route;
19+
20+
class CookbookController extends AbstractController
21+
{
22+
public function __construct(
23+
private CookbookRepository $cookbookRepository,
24+
private CookbookFactory $cookbookFactory,
25+
) {
26+
}
27+
28+
#[Route('/cookbook', name: 'app_cookbook_index')]
29+
public function index(): Response
30+
{
31+
$cookbooks = $this->cookbookRepository->findAll();
32+
33+
return $this->render('cookbook/index.html.twig', [
34+
'cookbooks' => $cookbooks,
35+
]);
36+
}
37+
38+
#[Route('/cookbook/{slug}', name: 'app_cookbook_show')]
39+
public function show(string $slug): Response
40+
{
41+
$cookbook = $this->cookbookRepository->findOneByName($slug);
42+
43+
return $this->render('cookbook/show.html.twig', [
44+
'slug' => $slug,
45+
'cookbook' => $cookbook,
46+
]);
47+
}
48+
}

ux.symfony.com/src/Model/Cookbook.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace App\Model;
13+
14+
class Cookbook
15+
{
16+
public function __construct(
17+
public string $title,
18+
public string $description,
19+
public string $route,
20+
public string $image,
21+
public string $content,
22+
/**
23+
* @var string[]
24+
*/
25+
public array $tags = [],
26+
) {
27+
}
28+
}

ux.symfony.com/src/Service/CommonMark/ConverterFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use League\CommonMark\CommonMarkConverter;
1515
use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
16+
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
1617
use League\CommonMark\Extension\Mention\MentionExtension;
1718
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
1819
use Tempest\Highlight\CommonMark\HighlightExtension;
@@ -47,6 +48,7 @@ public function __invoke(): CommonMarkConverter
4748
->addExtension(new ExternalLinkExtension())
4849
->addExtension(new MentionExtension())
4950
->addExtension(new HighlightExtension())
51+
->addExtension(new FrontMatterExtension())
5052
;
5153

5254
return $converter;

0 commit comments

Comments
 (0)