Skip to content

Commit 0288ebd

Browse files
committed
bug #804 [Site] More Functional Code, 2x Complex Live Components Examples, few fixes (weaverryan)
This PR was merged into the 2.x branch. Discussion ---------- [Site] More Functional Code, 2x Complex Live Components Examples, few fixes | Q | A | ------------- | --- | Bug fix? | yes (with respect to Twig errors) | New feature? | no | Tickets | None | License | MIT Hi! Prepping ux.symfony.com for 2.8 - very excited about this! Join me on a tour: ## 1) Invoice Creator Live Component Demo https://user-images.githubusercontent.com/121003/232886502-7f8da8f5-fe8a-4251-8e92-c2b56895b1ee.mp4 Parent component + collection of child components communicating by emitting events! ## 2) Product form + modal to create a Category https://user-images.githubusercontent.com/121003/232888282-ab12c4a1-8153-4fe5-babf-cbf4a313c47b.mp4 Opens a Bootstrap modal with a child component inside - and closes that modal from a `LiveAction`. ### 3) Much nicer code blocks Before: <img width="609" alt="Screenshot 2023-04-18 at 3 45 34 PM" src="https://user-images.githubusercontent.com/121003/232888991-67cad476-6836-4b10-9eb5-1ec6b43aafc6.png"> After: <img width="604" alt="Screenshot 2023-04-18 at 3 45 41 PM" src="https://user-images.githubusercontent.com/121003/232889021-01efedd9-c062-4072-a120-1b437d342634.png"> * "Copy" button * Link to view the file on GitHub * use statements are collapsed (click to show them) * "Expand code" to show longer code blocks ### 4) Explanations on longer examples Some examples deserve pointing out a few of the more complex parts. Some of the live component demos now do this: <img width="443" alt="Screenshot 2023-04-18 at 3 48 42 PM" src="https://user-images.githubusercontent.com/121003/232889344-3a776683-40a5-4ef9-8fa7-d909343f563a.png"> ### 5) Using a LOT of new features [Smart rendering](https://symfony.com/bundles/ux-live-component/current/index.html#the-smart-re-render-algorithm), [emitting events](https://symfony.com/bundles/ux-live-component/current/index.html#communication-between-components-emitting-events), [browser events](https://symfony.com/bundles/ux-live-component/current/index.html#dispatching-browser-javascript-events), etc, etc - MANY new features are being used on the site now. We're also using (in som e places) the new `<twig:Component>` syntax from #771. Even though editor support (e.g. for auto-completion) doesn't exist yet, I LOVE this. For example, to render a code block: ```html <twig:CodeBlock filename="src/Twig/SearchPackages.php" /> {# or more complex - logic for extracting" a specific Twig block lives in the CodeBlock.php component #} <twig:CodeBlock filename="templates/main/homepage.html.twig" targetTwigBlock="markdown_example" :showTwigExtends="false" :stripExcessHtml="true" /> ``` Thanks everyone for the work, review & testing on a lot of this new stuff. Using the new features on the site has been BLAST. Commits ------- e6edbde [Site] Adding InvoiceCreator live demo + modal demo
2 parents 3a2afe2 + e6edbde commit 0288ebd

File tree

105 files changed

+3312
-1318
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+3312
-1318
lines changed

src/LiveComponent/doc/index.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2302,7 +2302,7 @@ Stimulus controller - like this for Bootstrap's modal:
23022302
23032303
// assets/controllers/bootstrap-modal-controller.js
23042304
import { Controller } from '@hotwired/stimulus';
2305-
import { Modal } from 'bootstrap';
2305+
import Modal from 'bootstrap/js/dist/modal';
23062306
23072307
export default class extends Controller {
23082308
modal = null;
@@ -2317,7 +2317,7 @@ Just make sure this controller is attached to the modal element:
23172317

23182318
.. code-block:: html+twig
23192319

2320-
<div class="modal fade" {{ stimulus_controller('bootstrap-modal') }}">
2320+
<div class="modal fade" {{ stimulus_controller('bootstrap-modal') }}>
23212321
<div class="modal-dialog">
23222322
... content ...
23232323
</div>

src/TwigComponent/src/Twig/ComponentExtension.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Contracts\Service\ServiceSubscriberInterface;
1616
use Symfony\UX\TwigComponent\ComponentFactory;
1717
use Symfony\UX\TwigComponent\ComponentRenderer;
18+
use Twig\Error\RuntimeError;
1819
use Twig\Extension\AbstractExtension;
1920
use Twig\TwigFunction;
2021

@@ -53,11 +54,27 @@ public function getTokenParsers(): array
5354

5455
public function render(string $name, array $props = []): string
5556
{
56-
return $this->container->get(ComponentRenderer::class)->createAndRender($name, $props);
57+
try {
58+
return $this->container->get(ComponentRenderer::class)->createAndRender($name, $props);
59+
} catch (\Throwable $e) {
60+
$this->throwRuntimeError($name, $e);
61+
}
5762
}
5863

5964
public function embeddedContext(string $name, array $props, array $context): array
6065
{
61-
return $this->container->get(ComponentRenderer::class)->embeddedContext($name, $props, $context);
66+
try {
67+
return $this->container->get(ComponentRenderer::class)->embeddedContext($name, $props, $context);
68+
} catch (\Throwable $e) {
69+
$this->throwRuntimeError($name, $e);
70+
}
71+
}
72+
73+
private function throwRuntimeError(string $name, \Throwable $e): void
74+
{
75+
if (!($e instanceof \Exception)) {
76+
$e = new \Exception($e->getMessage(), $e->getCode(), $e->getPrevious());
77+
}
78+
throw new RuntimeError(sprintf('Error rendering "%s" component: %s', $name, $e->getMessage()), previous: $e);
6279
}
6380
}

src/TwigComponent/src/Twig/TwigPreLexer.php

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\UX\TwigComponent\Twig;
1313

14+
use Twig\Error\SyntaxError;
15+
1416
/**
1517
* Rewrites <twig:component> syntaxes to {% component %} syntaxes.
1618
*/
@@ -46,12 +48,12 @@ public function preLexComponents(string $input): string
4648
$this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock'] = false;
4749
}
4850

49-
$output .= $this->consumeBlock();
51+
$output .= $this->consumeBlock($componentName);
5052

5153
continue;
5254
}
5355

54-
$attributes = $this->consumeAttributes();
56+
$attributes = $this->consumeAttributes($componentName);
5557
$isSelfClosing = $this->consume('/>');
5658
if (!$isSelfClosing) {
5759
$this->consume('>');
@@ -79,7 +81,7 @@ public function preLexComponents(string $input): string
7981
$lastComponentName = $lastComponent['name'];
8082

8183
if ($closingComponentName !== $lastComponentName) {
82-
throw new \RuntimeException("Expected closing tag '</twig:{$lastComponentName}>' but found '</twig:{$closingComponentName}>' at line {$this->line}");
84+
throw new SyntaxError("Expected closing tag '</twig:{$lastComponentName}>' but found '</twig:{$closingComponentName}>'", $this->line);
8385
}
8486

8587
// we've reached the end of this component. If we're inside the
@@ -112,28 +114,40 @@ public function preLexComponents(string $input): string
112114

113115
if (!empty($this->currentComponents)) {
114116
$lastComponent = array_pop($this->currentComponents)['name'];
115-
throw new \RuntimeException(sprintf('Expected closing tag "</twig:%s>" not found at line %d.', $lastComponent, $this->line));
117+
throw new SyntaxError(sprintf('Expected closing tag "</twig:%s>" not found.', $lastComponent), $this->line);
116118
}
117119

118120
return $output;
119121
}
120122

121-
private function consumeComponentName(): string
123+
private function consumeComponentName(string $customExceptionMessage = null): string
122124
{
123125
$start = $this->position;
124126
while ($this->position < $this->length && preg_match('/[A-Za-z0-9_:@\-\/.]/', $this->input[$this->position])) {
125127
++$this->position;
126128
}
129+
127130
$componentName = substr($this->input, $start, $this->position - $start);
128131

129132
if (empty($componentName)) {
130-
throw new \RuntimeException("Expected component name at line {$this->line}");
133+
$exceptionMessage = $customExceptionMessage;
134+
if (null == $exceptionMessage) {
135+
$exceptionMessage = 'Expected component name when resolving the "<twig:" syntax.';
136+
}
137+
throw new SyntaxError($exceptionMessage, $this->line);
131138
}
132139

133140
return $componentName;
134141
}
135142

136-
private function consumeAttributes(): string
143+
private function consumeAttributeName(string $componentName): string
144+
{
145+
$message = sprintf('Expected attribute name when parsing the "<twig:%s" syntax.', $componentName);
146+
147+
return $this->consumeComponentName($message);
148+
}
149+
150+
private function consumeAttributes(string $componentName): string
137151
{
138152
$attributes = [];
139153

@@ -151,7 +165,7 @@ private function consumeAttributes(): string
151165
$isAttributeDynamic = true;
152166
}
153167

154-
$key = $this->consumeComponentName();
168+
$key = $this->consumeAttributeName($componentName);
155169

156170
// <twig:component someProp> -> someProp: true
157171
if (!$this->check('=')) {
@@ -202,13 +216,13 @@ private function consume(string $string): bool
202216
private function consumeChar($validChars = null): string
203217
{
204218
if ($this->position >= $this->length) {
205-
throw new \RuntimeException('Unexpected end of input');
219+
throw new SyntaxError('Unexpected end of input', $this->line);
206220
}
207221

208222
$char = $this->input[$this->position];
209223

210224
if (null !== $validChars && !\in_array($char, (array) $validChars, true)) {
211-
throw new \RuntimeException('Expected one of ['.implode('', (array) $validChars)."] but found '{$char}' at line {$this->line}");
225+
throw new SyntaxError('Expected one of ['.implode('', (array) $validChars)."] but found '{$char}'.", $this->line);
212226
}
213227

214228
++$this->position;
@@ -255,7 +269,7 @@ private function expectAndConsumeChar(string $char): void
255269
}
256270

257271
if ($this->position >= $this->length || $this->input[$this->position] !== $char) {
258-
throw new \RuntimeException("Expected '{$char}' but found '{$this->input[$this->position]}' at line {$this->line}");
272+
throw new SyntaxError("Expected '{$char}' but found '{$this->input[$this->position]}'.", $this->line);
259273
}
260274
++$this->position;
261275
}
@@ -276,9 +290,9 @@ private function check(string $chars): bool
276290
return true;
277291
}
278292

279-
private function consumeBlock(): string
293+
private function consumeBlock(string $componentName): string
280294
{
281-
$attributes = $this->consumeAttributes();
295+
$attributes = $this->consumeAttributes($componentName);
282296
$this->consume('>');
283297

284298
$blockName = '';
@@ -291,14 +305,14 @@ private function consumeBlock(): string
291305
}
292306

293307
if (empty($blockName)) {
294-
throw new \RuntimeException("Expected block name at line {$this->line}");
308+
throw new SyntaxError('Expected block name.', $this->line);
295309
}
296310

297311
$output = "{% block {$blockName} %}";
298312

299313
$closingTag = '</twig:block>';
300314
if (!$this->doesStringEventuallyExist($closingTag)) {
301-
throw new \RuntimeException("Expected closing tag '{$closingTag}' for block '{$blockName}' at line {$this->line}");
315+
throw new SyntaxError("Expected closing tag '{$closingTag}' for block '{$blockName}'.", $this->line);
302316
}
303317
$blockContents = $this->consumeUntil($closingTag);
304318

ux.symfony.com/assets/bootstrap.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ export const app = startStimulusApp(require.context(
88
/\.[jt]sx?$/
99
));
1010

11-
app.debug = process.env.NODE_ENV === 'development';
12-
1311
app.register('clipboard', Clipboard);
1412
// register any custom, 3rd party controllers here
1513

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
import Modal from 'bootstrap/js/dist/modal';
3+
4+
/**
5+
* Allows you to dispatch a "modal:close" JavaScript event to close it.
6+
*
7+
* This is useful inside a LiveComponent, where you can emit a browser event
8+
* to open or close the modal.
9+
*
10+
* See templates/components/BootstrapModal.html.twig to see how this is
11+
* attached to Bootstrap modal.
12+
*/
13+
export default class extends Controller {
14+
modal = null;
15+
16+
connect() {
17+
this.modal = Modal.getOrCreateInstance(this.element);
18+
document.addEventListener('modal:close', () => this.modal.hide());
19+
}
20+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
3+
export default class extends Controller {
4+
static targets = ['useStatements', 'expandCodeButton', 'codeContent'];
5+
6+
connect() {
7+
if (this.#isOverflowing(this.codeContentTarget)) {
8+
this.expandCodeButtonTarget.style.display = 'block';
9+
// add extra padding so the button doesn't block the code
10+
this.codeContentTarget.classList.add('pb-5');
11+
}
12+
}
13+
14+
expandUseStatements(event) {
15+
this.useStatementsTarget.style.display = 'block';
16+
event.currentTarget.remove();
17+
}
18+
19+
expandCode(event) {
20+
this.codeContentTarget.style.height = 'auto';
21+
this.codeContentTarget.classList.remove('pb-5');
22+
this.expandCodeButtonTarget.remove();
23+
}
24+
25+
#isOverflowing(element) {
26+
return element.scrollHeight > element.clientHeight;
27+
}
28+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Clipboard from 'stimulus-clipboard'
2+
3+
export default class extends Clipboard {
4+
static values = {
5+
source: String,
6+
}
7+
8+
/**
9+
* Overridden so we can simply pass the value in via a value.
10+
*/
11+
copy(event) {
12+
if (!this.sourceValue) {
13+
super.copy(event);
14+
}
15+
16+
event.preventDefault();
17+
18+
navigator.clipboard.writeText(this.sourceValue).then(() => this.copied());
19+
}
20+
}
Loading
Loading

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@
88
.terminal-code {
99
background-color: $n-900;
1010
border-radius: 12px;
11-
padding: 0px;
11+
padding: 0;
1212
color: #fff;
1313
font-size: 12px;
1414
}
1515

1616
.terminal-code.terminal-code-no-filename {
1717
padding-top: 0;
1818
}
19+
.terminal-code a.filename-header {
20+
color: white;
21+
display: inline-block;
22+
}
1923

2024
.terminal-wrapper .terminal-controls {
2125
position: absolute;
@@ -51,6 +55,25 @@
5155
color: #FFF;
5256
border-radius: 0px 0px 12px 12px;
5357
}
58+
.terminal-code .btn-copy, .terminal-code .btn-copy:active {
59+
background-color: $n-700;
60+
color: $n-200;
61+
}
62+
.terminal-code .btn-copy:hover {
63+
background-color: $n-600;
64+
}
65+
/* copy button inside the code block itself */
66+
.terminal-code .terminal-body .code-buttons {
67+
position: absolute;
68+
top: .5em;
69+
right: .5em;
70+
}
71+
.terminal-code .btn-link {
72+
color: $n-200;
73+
}
74+
.terminal-code .btn-link:hover {
75+
color: $n-100;
76+
}
5477
.terminal-code.terminal-code-no-filename .terminal-body {
5578
border-radius: 12px;
5679
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,8 @@ h4.ubuntu {
224224
color: $n-300;
225225
font-weight: lighter;
226226
}
227+
228+
blockquote {
229+
border-left: 5px solid $n-200;
230+
padding-left: 1rem;
231+
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,19 @@ footer {
7878
background-color: $n-700;
7979
border-color: $n-700;
8080
}
81+
82+
.btn-expand-code {
83+
position: absolute;
84+
bottom: 0;
85+
left: 0;
86+
width: 100%;
87+
color: rgb(156 163 175);
88+
background-color: rgb(55 65 81);
89+
}
90+
.btn-expand-code:hover, .btn-expand-code:active {
91+
background-color: rgb(55 65 81) !important;
92+
color: #fff !important;
93+
}
94+
.code-description a {
95+
text-decoration: underline;
96+
}

ux.symfony.com/code_snippets/README.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

ux.symfony.com/code_snippets/_ChartController.php

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)