Skip to content

Commit 3d78abb

Browse files
committed
[Toolkit] Refactor ToolkitKit enum to ToolkitKitId, leverage description/uxIcon/installation steps in Kit VO
1 parent 43e95a3 commit 3d78abb

File tree

23 files changed

+244
-258
lines changed

23 files changed

+244
-258
lines changed

src/Toolkit/kits/shadcn/INSTALL.md

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Getting started
2+
3+
This kit provides ready-to-use and fully-customizable UI Twig components based on [Shadcn UI](https://ui.shadcn.com/) components's **design**.
4+
5+
Please note that not every Shadcn UI component is available in this kit, but we are working on it!
6+
7+
## Requirements
8+
9+
This kit requires TailwindCSS to work:
10+
- If you use Symfony AssetMapper, you can install TailwindCSS with the [TailwindBundle](https://symfony.com/bundles/TailwindBundle/current/index.html),
11+
- If you use Webpack Encore, you can follow the [TailwindCSS installation guide for Symfony](https://tailwindcss.com/docs/installation/framework-guides/symfony)
12+
13+
## Installation
14+
15+
In your `assets/styles/app.css`, after the TailwindCSS imports, add the following code:
16+
17+
```css
18+
@custom-variant dark (&:is(.dark *));
19+
20+
:root {
21+
--radius: 0.625rem;
22+
--background: oklch(1 0 0);
23+
--foreground: oklch(0.145 0 0);
24+
--card: oklch(1 0 0);
25+
--card-foreground: oklch(0.145 0 0);
26+
--popover: oklch(1 0 0);
27+
--popover-foreground: oklch(0.145 0 0);
28+
--primary: oklch(0.205 0 0);
29+
--primary-foreground: oklch(0.985 0 0);
30+
--secondary: oklch(0.97 0 0);
31+
--secondary-foreground: oklch(0.205 0 0);
32+
--muted: oklch(0.97 0 0);
33+
--muted-foreground: oklch(0.556 0 0);
34+
--accent: oklch(0.97 0 0);
35+
--accent-foreground: oklch(0.205 0 0);
36+
--destructive: oklch(0.577 0.245 27.325);
37+
--border: oklch(0.922 0 0);
38+
--input: oklch(0.922 0 0);
39+
--ring: oklch(0.708 0 0);
40+
--chart-1: oklch(0.646 0.222 41.116);
41+
--chart-2: oklch(0.6 0.118 184.704);
42+
--chart-3: oklch(0.398 0.07 227.392);
43+
--chart-4: oklch(0.828 0.189 84.429);
44+
--chart-5: oklch(0.769 0.188 70.08);
45+
--sidebar: oklch(0.985 0 0);
46+
--sidebar-foreground: oklch(0.145 0 0);
47+
--sidebar-primary: oklch(0.205 0 0);
48+
--sidebar-primary-foreground: oklch(0.985 0 0);
49+
--sidebar-accent: oklch(0.97 0 0);
50+
--sidebar-accent-foreground: oklch(0.205 0 0);
51+
--sidebar-border: oklch(0.922 0 0);
52+
--sidebar-ring: oklch(0.708 0 0);
53+
}
54+
55+
.dark {
56+
--background: oklch(0.145 0 0);
57+
--foreground: oklch(0.985 0 0);
58+
--card: oklch(0.205 0 0);
59+
--card-foreground: oklch(0.985 0 0);
60+
--popover: oklch(0.269 0 0);
61+
--popover-foreground: oklch(0.985 0 0);
62+
--primary: oklch(0.922 0 0);
63+
--primary-foreground: oklch(0.205 0 0);
64+
--secondary: oklch(0.269 0 0);
65+
--secondary-foreground: oklch(0.985 0 0);
66+
--muted: oklch(0.269 0 0);
67+
--muted-foreground: oklch(0.708 0 0);
68+
--accent: oklch(0.371 0 0);
69+
--accent-foreground: oklch(0.985 0 0);
70+
--destructive: oklch(0.704 0.191 22.216);
71+
--border: oklch(1 0 0 / 10%);
72+
--input: oklch(1 0 0 / 15%);
73+
--ring: oklch(0.556 0 0);
74+
--chart-1: oklch(0.488 0.243 264.376);
75+
--chart-2: oklch(0.696 0.17 162.48);
76+
--chart-3: oklch(0.769 0.188 70.08);
77+
--chart-4: oklch(0.627 0.265 303.9);
78+
--chart-5: oklch(0.645 0.246 16.439);
79+
--sidebar: oklch(0.205 0 0);
80+
--sidebar-foreground: oklch(0.985 0 0);
81+
--sidebar-primary: oklch(0.488 0.243 264.376);
82+
--sidebar-primary-foreground: oklch(0.985 0 0);
83+
--sidebar-accent: oklch(0.269 0 0);
84+
--sidebar-accent-foreground: oklch(0.985 0 0);
85+
--sidebar-border: oklch(1 0 0 / 10%);
86+
--sidebar-ring: oklch(0.439 0 0);
87+
}
88+
89+
@layer base {
90+
* {
91+
border-color: var(--border);
92+
outline-color: var(--ring);
93+
}
94+
95+
body {
96+
background-color: var(--background);
97+
color: var(--foreground);
98+
}
99+
}
100+
```
101+
102+
And voilà! You are now ready to use Shadcn components in your Symfony project.

src/Toolkit/kits/shadcn/manifest.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
2-
"name": "Shadcn",
2+
"name": "Shadcn UI",
3+
"description": "Component based on the Shadcn UI library, one of the most popular design systems in JavaScript world.",
34
"license": "MIT",
45
"homepage": "https://ux.symfony.com/components",
5-
"authors": ["Shadcn", "Symfony Community"]
6+
"authors": ["Shadcn", "Symfony Community"],
7+
"ux-icon": "simple-icons:shadcnui"
68
}

src/Toolkit/src/Assert.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*/
2323
public static function kitName(string $name): void
2424
{
25-
if (1 !== preg_match('/^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/', $name)) {
25+
if (1 !== preg_match('/^[a-zA-Z0-9](?:[a-zA-Z0-9-_ ]{0,61}[a-zA-Z0-9])?$/', $name)) {
2626
throw new \InvalidArgumentException(\sprintf('Invalid kit name "%s".', $name));
2727
}
2828
}

src/Toolkit/src/Kit/Kit.php

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ public function __construct(
3636
public readonly string $homepage,
3737
public readonly array $authors,
3838
public readonly string $license,
39+
public readonly ?string $description = null,
40+
public readonly ?string $uxIcon = null,
41+
public ?string $installAsMarkdown = null,
3942
private array $components = [],
4043
) {
4144
Assert::kitName($this->name);

src/Toolkit/src/Kit/KitFactory.php

+25-7
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@ public function createKitFromAbsolutePath(string $absolutePath): Kit
5454
$manifest = json_decode($this->filesystem->readFile($manifestPath), true, flags: \JSON_THROW_ON_ERROR);
5555

5656
$kit = new Kit(
57-
$absolutePath,
58-
$manifest['name'] ?? throw new \InvalidArgumentException('Manifest file is missing "name" key.'),
59-
$manifest['homepage'] ?? throw new \InvalidArgumentException('Manifest file is missing "homepage" key.'),
60-
$manifest['authors'] ?? throw new \InvalidArgumentException('Manifest file is missing "authors" key.'),
61-
$manifest['license'] ?? throw new \InvalidArgumentException('Manifest file is missing "license" key.'),
57+
path: $absolutePath,
58+
name: $manifest['name'] ?? throw new \InvalidArgumentException('Manifest file is missing "name" key.'),
59+
homepage: $manifest['homepage'] ?? throw new \InvalidArgumentException('Manifest file is missing "homepage" key.'),
60+
authors: $manifest['authors'] ?? throw new \InvalidArgumentException('Manifest file is missing "authors" key.'),
61+
license: $manifest['license'] ?? throw new \InvalidArgumentException('Manifest file is missing "license" key.'),
62+
description: $manifest['description'] ?? null,
63+
uxIcon: $manifest['ux-icon'] ?? null,
6264
);
6365

6466
$this->synchronizeKit($kit);
@@ -69,6 +71,7 @@ public function createKitFromAbsolutePath(string $absolutePath): Kit
6971
private function synchronizeKit(Kit $kit): void
7072
{
7173
$this->synchronizeKitComponents($kit);
74+
$this->synchronizeKitDocumentation($kit);
7275
}
7376

7477
private function synchronizeKitComponents(Kit $kit): void
@@ -86,15 +89,13 @@ private function synchronizeKitComponents(Kit $kit): void
8689
$relativePathNameToKit = $file->getRelativePathname();
8790
$relativePathName = str_replace($componentsPath.\DIRECTORY_SEPARATOR, '', $relativePathNameToKit);
8891
$componentName = $this->extractComponentName($relativePathName);
89-
$docPath = Path::join($kit->path, 'docs', 'components', $componentName.'.md');
9092
$component = new Component(
9193
name: $componentName,
9294
files: [new File(
9395
type: FileType::Twig,
9496
relativePathNameToKit: $relativePathNameToKit,
9597
relativePathName: $relativePathName,
9698
)],
97-
doc: $this->filesystem->exists($docPath) ? new Doc($this->filesystem->readFile($docPath)) : null,
9899
);
99100

100101
$kit->addComponent($component);
@@ -107,4 +108,21 @@ private static function extractComponentName(string $pathnameRelativeToKit): str
107108
{
108109
return str_replace(['.html.twig', '/'], ['', ':'], $pathnameRelativeToKit);
109110
}
111+
112+
private function synchronizeKitDocumentation(Kit $kit): void
113+
{
114+
// Read INSTALL.md if exists
115+
$fileInstall = Path::join($kit->path, 'INSTALL.md');
116+
if ($this->filesystem->exists($fileInstall)) {
117+
$kit->installAsMarkdown = $this->filesystem->readFile($fileInstall);
118+
}
119+
120+
// Iterate over Component and find their documentation
121+
foreach ($kit->getComponents() as $component) {
122+
$docPath = Path::join($kit->path, 'docs', 'components', $component->name.'.md');
123+
if ($this->filesystem->exists($docPath)) {
124+
$component->doc = new Doc($this->filesystem->readFile($docPath));
125+
}
126+
}
127+
}
110128
}

src/Toolkit/tests/AssertTest.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ public static function provideValidKitNames(): \Generator
3333
yield ['1-my-kit'];
3434
yield ['my-kit-1'];
3535
yield ['my-kit-1-with-dashes'];
36-
yield ['Shadcn-UI'];
37-
yield ['Shadcn-UI-1'];
36+
yield ['Shadcn UI'];
37+
yield ['Shadcn UI-1'];
3838
// Single character
3939
yield ['a'];
4040
yield ['1'];
@@ -47,6 +47,7 @@ public static function provideValidKitNames(): \Generator
4747
yield ['a-b-c'];
4848
yield ['a1-b2-c3'];
4949
yield ['A1-B2-C3'];
50+
yield ['my_kit'];
5051
}
5152

5253
/**
@@ -71,7 +72,6 @@ public static function provideInvalidKitNames(): \Generator
7172
// Ending with hyphen
7273
yield ['my-kit-'];
7374
// Invalid characters
74-
yield ['my_kit'];
7575
yield ['my.kit'];
7676
yield ['my@kit'];
7777
// Too long (64 chars)

src/Toolkit/tests/Command/LintKitCommandTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function testShouldBeAbleToLint(): void
2424
$this->consoleCommand('ux:toolkit:lint-kit shadcn')
2525
->execute()
2626
->assertSuccessful()
27-
->assertOutputContains('The kit "Shadcn" is valid, it has 46 components')
27+
->assertOutputContains('The kit "Shadcn UI" is valid, it has 46 components')
2828
;
2929
}
3030
}

src/Toolkit/tests/Kit/KitFactoryTest.php

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
1515
use Symfony\UX\Toolkit\Dependency\ComponentDependency;
1616
use Symfony\UX\Toolkit\Dependency\PhpPackageDependency;
17-
use Symfony\UX\Toolkit\Dependency\Version;
1817
use Symfony\UX\Toolkit\Kit\KitFactory;
1918

2019
final class KitFactoryTest extends KernelTestCase

src/Toolkit/tests/Registry/GitHubRegistryTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function testCanGetKitFromGithub(): void
6464
$kit = $githubRegistry->getKit('github.com/user/repo');
6565

6666
$this->assertTrue($isHttpClientCalled);
67-
$this->assertSame('Shadcn', $kit->name);
67+
$this->assertSame('Shadcn UI', $kit->name);
6868
$this->assertNotEmpty($kit->getComponents());
6969
$this->assertFileExists($kit->path);
7070
$this->assertFileExists(Path::join($kit->path, 'templates/components/Button.html.twig'));

src/Toolkit/tests/Registry/LocalRegistryTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ public function testCanGetKit(): void
2828
$kit = $localRegistry->getKit('shadcn');
2929

3030
$this->assertInstanceOf(Kit::class, $kit);
31-
$this->assertSame('Shadcn', $kit->name);
31+
$this->assertSame('Shadcn UI', $kit->name);
3232
}
3333
}

ux.symfony.com/src/Controller/SitemapController.php

+5-6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace App\Controller;
1313

14-
use App\Enum\ToolkitKit;
14+
use App\Enum\ToolkitKitId;
1515
use App\Service\LiveDemoRepository;
1616
use App\Service\Toolkit\ToolkitService;
1717
use App\Service\UxPackageRepository;
@@ -67,12 +67,11 @@ private function getSitemapUrls(): iterable
6767
}
6868

6969
// Toolkit kits
70-
foreach ($this->toolkitService->getKits() as $kitName => $kit) {
71-
yield $this->generateAbsoluteUrl('app_toolkit_kit', ['kit' => $kitName]);
70+
foreach ($this->toolkitService->getKits() as $kitId => $kit) {
71+
yield $this->generateAbsoluteUrl('app_toolkit_kit', ['kitId' => $kitId]);
7272

73-
$toolkitKit = ToolkitKit::from($kitName);
74-
foreach ($this->toolkitService->getDocumentableComponents($toolkitKit) as $component) {
75-
yield $this->generateAbsoluteUrl('app_toolkit_component', ['kit' => $kitName, 'componentName' => $component->name]);
73+
foreach ($this->toolkitService->getDocumentableComponents($kit) as $component) {
74+
yield $this->generateAbsoluteUrl('app_toolkit_component', ['kitId' => $kitId, 'component_name' => $component->name]);
7675
}
7776
}
7877
}

ux.symfony.com/src/Controller/Toolkit/ComponentsController.php

+18-16
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace App\Controller\Toolkit;
1313

14-
use App\Enum\ToolkitKit;
14+
use App\Enum\ToolkitKitId;
1515
use App\Service\Toolkit\ToolkitService;
1616
use App\Service\UxPackageRepository;
1717
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -39,7 +39,7 @@ public function __construct(
3939
}
4040

4141
#[Route('/toolkit/kits/{kit}/components/')]
42-
public function listComponents(ToolkitKit $kit): Response
42+
public function listComponents(ToolkitKitId $kit): Response
4343
{
4444
// TODO: implementing listing in the future :D
4545

@@ -48,43 +48,45 @@ public function listComponents(ToolkitKit $kit): Response
4848
], Response::HTTP_FOUND);
4949
}
5050

51-
#[Route('/toolkit/kits/{kit}/components/{componentName}', name: 'app_toolkit_component')]
52-
public function showComponent(ToolkitKit $kit, string $componentName): Response
51+
#[Route('/toolkit/kits/{kitId}/components/{componentName}', name: 'app_toolkit_component')]
52+
public function showComponent(ToolkitKitId $kitId, string $componentName): Response
5353
{
54-
if (null === $component = $this->toolkitService->getComponent($kit, $componentName)) {
54+
$kit = $this->toolkitService->getKit($kitId);
55+
if (null === $component = $kit->getComponent($componentName)) {
5556
throw $this->createNotFoundException(\sprintf('Component "%s" not found', $componentName));
5657
}
5758

5859
$package = $this->uxPackageRepository->find('toolkit');
5960

6061
return $this->render('toolkit/component.html.twig', [
6162
'package' => $package,
62-
'kit' => $kit,
6363
'components' => $this->toolkitService->getDocumentableComponents($kit),
64+
'kit' => $kit,
65+
'kit_id' => $kitId,
6466
'component' => $component,
6567
]);
6668
}
6769

6870
#[Route('/toolkit/component_preview', name: 'app_toolkit_component_preview')]
6971
public function previewComponent(
70-
Request $request,
71-
#[MapQueryParameter] ToolkitKit $toolkitKit,
72-
#[MapQueryParameter] string $code,
73-
#[MapQueryParameter] string $height,
74-
UriSigner $uriSigner,
75-
\Twig\Environment $twig,
72+
Request $request,
73+
#[MapQueryParameter] ToolkitKitId $kitId,
74+
#[MapQueryParameter] string $code,
75+
#[MapQueryParameter] string $height,
76+
UriSigner $uriSigner,
77+
\Twig\Environment $twig,
7678
#[Autowire(service: 'ux.twig_component.component_factory')]
77-
ComponentFactory $componentFactory,
79+
ComponentFactory $componentFactory,
7880
#[Autowire(service: 'profiler')]
79-
?Profiler $profiler,
81+
?Profiler $profiler,
8082
): Response {
8183
if (!$uriSigner->checkRequest($request)) {
8284
throw new BadRequestHttpException('Request is invalid.');
8385
}
8486

8587
$profiler?->disable();
8688

87-
$kit = $this->toolkitService->getKit($toolkitKit);
89+
$kit = $this->toolkitService->getKit($kitId);
8890

8991
$twig->setLoader(new ChainLoader([
9092
new FilesystemLoader($kit->path.\DIRECTORY_SEPARATOR.'templates'.\DIRECTORY_SEPARATOR.'components'),
@@ -120,7 +122,7 @@ public function findAnonymousComponentTemplate(string $name): ?string
120122
<meta charset="utf-8">
121123
<title>Preview</title>
122124
<meta name="viewport" content="width=device-width, initial-scale=1">
123-
{{ importmap('toolkit-{$toolkitKit->value}') }}
125+
{{ importmap('toolkit-{$kitId->value}') }}
124126
</head>
125127
<body class="flex min-h-[{$height}] w-full justify-center p-5 items-center">{$code}</body>
126128
</html>

0 commit comments

Comments
 (0)