Skip to content

Commit 58b0eb2

Browse files
committed
minor #2716 [Site][Toolkit] Add manual installation steps for Component installation (and some website tweaks) (Kocal)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [Site][Toolkit] Add manual installation steps for Component installation (and some website tweaks) | Q | A | ------------- | --- | Bug fix? | no | New feature? | no <!-- please update src/**/CHANGELOG.md files --> | Docs? | yes <!-- required for new features --> | Issues | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead --> | License | MIT Another PR started in the plane :D ![IMG_2736 - copie](https://github.com/user-attachments/assets/b07dabcf-0662-439d-b7b8-9a93a6993d26) --- This one add manual installation steps in the documentation, letting people see files they will install before (or without) installing the UX Toolkit: https://github.com/user-attachments/assets/93ec2f2c-1a2b-4637-a9fa-f92fa4273f3d Commits ------- 948abe5 [Site] Refactor and use the CodeBlockRenderer::highlightCode() method to render the highlighted code inside a Terminal component 2379c89 [Site] Fix Terminal component styles, ensure it does not grow, and display scrollbars 5a6a89a [Site] Add manual installation steps for Toolkit kits's components 3b6a11b [Site] Rename CodePreview_Tabs to Toolkit_Tabs, extract Toolkit's documentation tabs rendering into ToolkitService
2 parents 30a140f + 948abe5 commit 58b0eb2

File tree

7 files changed

+128
-89
lines changed

7 files changed

+128
-89
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ $utilities: map-remove(
128128
@import "components/Button";
129129
@import "components/Browser";
130130
@import "components/Changelog";
131-
@import "components/CodePreview_Tabs";
132131
@import "components/DataList";
133132
@import "components/DemoContainer";
134133
@import "components/DemoCard";
@@ -153,6 +152,7 @@ $utilities: map-remove(
153152
@import "components/Terminal";
154153
@import "components/TerminalCommand";
155154
@import "components/ThemeSwitcher";
155+
@import "components/Toolkit_Tabs";
156156
@import "components/Wysiwyg";
157157

158158
// Utilities

ux.symfony.com/assets/styles/components/_Terminal.scss

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
border-radius: .75rem;
77
position: relative;
88
font-size: 12px;
9+
display: grid; // Ensure the Terminal overflow its parent if "pre" contains a very-long-line of the same highlighted element (e.g.: a long string)
910
}
1011

1112
.Terminal_light {
@@ -162,6 +163,7 @@
162163
overflow: visible;
163164
}
164165
scrollbar-width: thin;
166+
scrollbar-color: rgba(255, 255, 255, .8) transparent;
165167
pre {
166168
background: none;
167169
}
@@ -170,15 +172,15 @@
170172
}
171173
}
172174

173-
@media screen and (min-width: 768px) {
174-
.Terminal_content::-webkit-scrollbar {
175-
display: none;
176-
}
177-
.Terminal_content {
178-
--webkit-scrollbar-width: none;
179-
scrollbar-width: none;
180-
}
181-
}
175+
// @media screen and (min-width: 768px) {
176+
// .Terminal_content::-webkit-scrollbar {
177+
// display: none;
178+
// }
179+
// .Terminal_content {
180+
// --webkit-scrollbar-width: none;
181+
// scrollbar-width: none;
182+
// }
183+
// }
182184

183185
.Terminal_expand {
184186
position: absolute;

ux.symfony.com/assets/styles/components/_CodePreview_Tabs.scss renamed to ux.symfony.com/assets/styles/components/_Toolkit_Tabs.scss

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
.CodePreview_Tabs {
1+
.Toolkit_Tabs {
22
}
33

4-
.CodePreview_TabHead {
4+
.Toolkit_TabHead {
55
display: flex;
66
flex-direction: row;
77
margin-bottom: 1rem
88
}
99

10-
.CodePreview_TabControl {
10+
.Toolkit_TabControl {
1111
border-bottom: 3px solid transparent;
1212
color: var(--bs-primary-color);
1313
padding: 0 1rem;
@@ -18,24 +18,24 @@
1818
margin-bottom: -1px;
1919
}
2020

21-
.CodePreview_TabControl.active {
21+
.Toolkit_TabControl.active {
2222
border-color: var(--bs-secondary-color);
2323
}
2424

25-
.CodePreview_TabPanel {
25+
.Toolkit_TabPanel {
2626
position: relative;
2727
}
2828

29-
.CodePreview_TabPanel:not(.active) {
29+
.Toolkit_TabPanel:not(.active) {
3030
display: none;
3131
}
3232

33-
.CodePreview_TabPanel:has(.CodePreview_Preview) {
33+
.Toolkit_TabPanel:has(.Toolkit_Preview) {
3434
border: 1px solid var(--bs-border-color);
3535
border-radius: .75rem
3636
}
3737

38-
.CodePreview_Loader {
38+
.Toolkit_Loader {
3939
width: 100%;
4040
display: flex;
4141
justify-content: center;
@@ -48,7 +48,7 @@
4848
}
4949
}
5050

51-
.CodePreview_Preview {
51+
.Toolkit_Preview {
5252
width: 100%;
5353
transition: opacity .250s linear;
5454
border-radius: .75rem;

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
namespace App\Service\CommonMark;
1313

1414
use App\Service\CommonMark\Extension\CodeBlockRenderer\CodeBlockRenderer;
15+
use App\Service\Toolkit\ToolkitService;
1516
use League\CommonMark\CommonMarkConverter;
1617
use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
1718
use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
1819
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
1920
use League\CommonMark\Extension\Mention\MentionExtension;
2021
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
21-
use Symfony\Component\HttpFoundation\UriSigner;
22-
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
2322

2423
/**
2524
* @author Kevin Bond <kevinbond@gmail.com>
@@ -28,8 +27,7 @@
2827
final class ConverterFactory
2928
{
3029
public function __construct(
31-
private readonly UrlGeneratorInterface $urlGenerator,
32-
private readonly UriSigner $uriSigner,
30+
private readonly ToolkitService $toolkitService,
3331
) {
3432
}
3533

@@ -57,7 +55,7 @@ public function __invoke(): CommonMarkConverter
5755
->addExtension(new ExternalLinkExtension())
5856
->addExtension(new MentionExtension())
5957
->addExtension(new FrontMatterExtension())
60-
->addRenderer(FencedCode::class, new CodeBlockRenderer($this->urlGenerator, $this->uriSigner))
58+
->addRenderer(FencedCode::class, new CodeBlockRenderer($this->toolkitService))
6159
;
6260

6361
return $converter;

ux.symfony.com/src/Service/CommonMark/Extension/CodeBlockRenderer/CodeBlockRenderer.php

Lines changed: 10 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,19 @@
1111

1212
namespace App\Service\CommonMark\Extension\CodeBlockRenderer;
1313

14+
use App\Enum\ToolkitKitId;
15+
use App\Service\Toolkit\ToolkitService;
1416
use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
1517
use League\CommonMark\Node\Node;
1618
use League\CommonMark\Renderer\ChildNodeRendererInterface;
1719
use League\CommonMark\Renderer\NodeRendererInterface;
18-
use Symfony\Component\HttpFoundation\UriSigner;
19-
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
2020
use Tempest\Highlight\Highlighter;
2121
use Tempest\Highlight\WebTheme;
2222

2323
final readonly class CodeBlockRenderer implements NodeRendererInterface
2424
{
2525
public function __construct(
26-
private UrlGeneratorInterface $urlGenerator,
27-
private UriSigner $uriSigner,
26+
private ToolkitService $toolkitService,
2827
) {
2928
}
3029

@@ -38,45 +37,19 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \
3837
$infoWords = $node->getInfoWords();
3938
$language = $infoWords[0] ?? 'txt';
4039
$options = isset($infoWords[1]) && json_validate($infoWords[1]) ? json_decode($infoWords[1], true) : [];
40+
$kitId = ToolkitKitId::tryFrom($options['kit'] ?? null);
4141
$preview = $options['preview'] ?? false;
42-
$kit = $options['kit'] ?? null;
43-
$height = $options['height'] ?? '150px';
4442

45-
$code = $node->getLiteral();
43+
$output = $this->highlightCode($language, $code = $node->getLiteral());
4644

47-
$output = $this->highlightCode($code, $language);
48-
49-
if ($preview && $kit) {
50-
$previewUrl = $this->uriSigner->sign($this->urlGenerator->generate('app_toolkit_component_preview', [
51-
'kitId' => $kit,
52-
'code' => $code,
53-
'height' => $height,
54-
], UrlGeneratorInterface::ABSOLUTE_URL));
55-
56-
$output = <<<HTML
57-
<div class="CodePreview_Tabs" data-controller="tabs" data-tabs-tab-value="preview" data-tabs-active-class="active">
58-
<nav class="CodePreview_TabHead" role="tablist" style="border-bottom: 1px solid var(--bs-border-color)">
59-
<button class="CodePreview_TabControl" data-action="tabs#show" data-tabs-target="control" data-tabs-tab-param="preview" role="tab" aria-selected="true">Preview</button>
60-
<button class="CodePreview_TabControl" data-action="tabs#show" data-tabs-target="control" data-tabs-tab-param="code" role="tab" aria-selected="false">Code</button>
61-
</nav>
62-
<div class="CodePreview_TabBody">
63-
<div class="CodePreview_TabPanel active" data-tabs-target="tab" data-tab="preview" role="tabpanel">
64-
<div class="CodePreview_Loader" style="height: {$height};">
65-
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
66-
<span>Loading...</span>
67-
</div>
68-
<iframe class="CodePreview_Preview loading" src="{$previewUrl}" style="height: {$height};" loading="lazy" onload="this.previousElementSibling.style.display = 'none'; this.classList.remove('loading')"></iframe>
69-
</div>
70-
<div class="CodePreview_TabPanel" data-tabs-target="tab" data-tab="code" role="tabpanel">{$output}</div>
71-
</div>
72-
</div>
73-
HTML;
45+
if ($kitId && $preview) {
46+
$output = $this->toolkitService->renderComponentPreviewCodeTabs($kitId, $code, $output, $options['height'] ?? '150px');
7447
}
7548

7649
return $output;
7750
}
7851

79-
private function highlightCode(string $code, string $language): string
52+
public static function highlightCode(string $language, string $code, string $style = 'margin-bottom: 1rem'): string
8053
{
8154
$highlighter = new Highlighter();
8255

@@ -87,11 +60,9 @@ private function highlightCode(string $code, string $language): string
8760
: '<pre data-lang="'.$language.'" class="notranslate">'.$parsed.'</pre>';
8861

8962
return <<<HTML
90-
<div class="Terminal terminal-code" style="margin-bottom: 1rem;">
63+
<div class="Terminal terminal-code" style="$style">
9164
<div class="Terminal_body">
92-
<div class="Terminal_content" style="max-height: 450px;">
93-
{$output}
94-
</div>
65+
<div class="Terminal_content" style="max-height: 450px;">{$output}</div>
9566
</div>
9667
</div>
9768
HTML;

ux.symfony.com/src/Service/Toolkit/ToolkitService.php

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,23 @@
1212
namespace App\Service\Toolkit;
1313

1414
use App\Enum\ToolkitKitId;
15+
use App\Service\CommonMark\Extension\CodeBlockRenderer\CodeBlockRenderer;
1516
use Symfony\Component\DependencyInjection\Attribute\Autowire;
17+
use Symfony\Component\Filesystem\Path;
18+
use Symfony\Component\HttpFoundation\UriSigner;
19+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
1620
use Symfony\UX\Toolkit\Asset\Component;
21+
use Symfony\UX\Toolkit\Installer\PoolResolver;
1722
use Symfony\UX\Toolkit\Kit\Kit;
1823
use Symfony\UX\Toolkit\Registry\RegistryFactory;
1924

2025
class ToolkitService
2126
{
2227
public function __construct(
2328
#[Autowire(service: 'ux_toolkit.registry.registry_factory')]
24-
private RegistryFactory $registryFactory,
29+
private readonly RegistryFactory $registryFactory,
30+
private readonly UriSigner $uriSigner,
31+
private readonly UrlGeneratorInterface $urlGenerator,
2532
) {
2633
}
2734

@@ -54,4 +61,83 @@ public function getDocumentableComponents(Kit $kit): array
5461
{
5562
return array_filter($kit->getComponents(), fn (Component $component) => $component->doc);
5663
}
64+
65+
public function renderComponentPreviewCodeTabs(ToolkitKitId $kitId, string $code, string $highlightedCode, string $height): string
66+
{
67+
$previewUrl = $this->urlGenerator->generate('app_toolkit_component_preview', ['kitId' => $kitId->value, 'code' => $code, 'height' => $height], UrlGeneratorInterface::ABSOLUTE_URL);
68+
$previewUrl = $this->uriSigner->sign($previewUrl);
69+
70+
return self::generateTabs([
71+
'Preview' => <<<HTML
72+
<div class="Toolkit_Loader" style="height: {$height};">
73+
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
74+
<span>Loading...</span>
75+
</div>
76+
<iframe class="Toolkit_Preview loading" src="{$previewUrl}" style="height: {$height};" loading="lazy" onload="this.previousElementSibling.style.display = 'none'; this.classList.remove('loading')"></iframe>
77+
HTML,
78+
'Code' => $highlightedCode,
79+
]);
80+
}
81+
82+
public function renderInstallationSteps(ToolkitKitId $kitId, Component $component): string
83+
{
84+
$kit = $this->getKit($kitId);
85+
$pool = (new PoolResolver())->resolveForComponent($kit, $component);
86+
87+
$manual = '<p>The UX Toolkit is not mandatory to install a component. You can install it manually by following the next steps:</p>';
88+
$manual .= '<ol style="display: grid; gap: 1rem;">';
89+
$manual .= '<li><strong>Copy the following file(s) into your Symfony app:</strong>';
90+
foreach ($pool->getFiles() as $file) {
91+
$manual .= \sprintf(
92+
"<details><summary><code>%s</code></summary>\n%s\n</details>",
93+
$file->relativePathNameToKit,
94+
\sprintf("\n```%s\n%s\n```", pathinfo($file->relativePathNameToKit, \PATHINFO_EXTENSION), trim(file_get_contents(Path::join($kit->path, $file->relativePathNameToKit))))
95+
);
96+
}
97+
$manual .= '</li>';
98+
99+
if ($phpPackageDependencies = $pool->getPhpPackageDependencies()) {
100+
$manual .= '<li><strong>If necessary, install the following Composer dependencies:</strong>';
101+
$manual .= CodeBlockRenderer::highlightCode('shell', '$ composer require '.implode(' ', $phpPackageDependencies), 'margin-bottom: 0');
102+
$manual .= '</li>';
103+
}
104+
105+
$manual .= '<li><strong>And the most important, enjoy!</strong></li>';
106+
$manual .= '</ol>';
107+
108+
return $this->generateTabs([
109+
'Automatic' => \sprintf(
110+
'<p>Ensure the Symfony UX Toolkit is installed in your Symfony app:</p>%s<p>Then, run the following command to install the component and its dependencies:</p>%s',
111+
CodeBlockRenderer::highlightCode('shell', '$ composer require --dev symfony/ux-toolkit'),
112+
CodeBlockRenderer::highlightCode('shell', "$ bin/console ux:toolkit:install-component {$component->name} --kit {$kitId->value}"),
113+
),
114+
'Manual' => $manual,
115+
]);
116+
}
117+
118+
/**
119+
* @param non-empty-array<string, string> $tabs
120+
*/
121+
private static function generateTabs(array $tabs): string
122+
{
123+
$activeTabId = null;
124+
$tabsControls = '';
125+
$tabsPanels = '';
126+
127+
foreach ($tabs as $tabText => $tabContent) {
128+
$tabId = hash('xxh3', $tabText);
129+
$activeTabId ??= $tabId;
130+
$isActive = $activeTabId === $tabId;
131+
132+
$tabsControls .= \sprintf('<button class="Toolkit_TabControl" data-action="tabs#show" data-tabs-target="control" data-tabs-tab-param="%s" role="tab" aria-selected="%s">%s</button>', $tabId, $isActive ? 'true' : 'false', trim($tabText));
133+
$tabsPanels .= \sprintf('<div class="Toolkit_TabPanel %s" data-tabs-target="tab" data-tab="%s" role="tabpanel">%s</div>', $isActive ? 'active' : '', $tabId, $tabContent);
134+
}
135+
136+
return <<<HTML
137+
<div class="Toolkit_Tabs" data-controller="tabs" data-tabs-tab-value="{$activeTabId}" data-tabs-active-class="active">
138+
<nav class="Toolkit_TabHead" role="tablist" style="border-bottom: 1px solid var(--bs-border-color)">{$tabsControls}</nav>
139+
<div class="Toolkit_TabBody">{$tabsPanels}</div>
140+
</div>
141+
HTML;
142+
}
57143
}

0 commit comments

Comments
 (0)