lui-templates 0.0.6 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/LIQUID_SPEC.md +101 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/parsers/liquid.js +294 -3
- package/test/templates/render-comprehensive.liquid +21 -0
- package/test/templates/render-conditional.liquid +6 -0
- package/test/templates/render-expressions.liquid +3 -0
- package/test/templates/render-test.liquid +7 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Liquid Template Syntax Specification
|
|
2
|
+
|
|
3
|
+
This document describes the Liquid template syntax used in this project. AI agents and contributors should follow these specifications when working with Liquid templates.
|
|
4
|
+
|
|
5
|
+
## Syntax Overview
|
|
6
|
+
|
|
7
|
+
Liquid uses two types of delimiters:
|
|
8
|
+
|
|
9
|
+
### 1. Output (Expressions): `{{ }}`
|
|
10
|
+
Used for outputting variables and expressions:
|
|
11
|
+
```liquid
|
|
12
|
+
{{ variable }}
|
|
13
|
+
{{ user.name }}
|
|
14
|
+
{{ "Hello " + name }}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 2. Tags (Commands): `{% %}`
|
|
18
|
+
Used for logic, control flow, and special commands:
|
|
19
|
+
```liquid
|
|
20
|
+
{% if condition %}
|
|
21
|
+
Content
|
|
22
|
+
{% endif %}
|
|
23
|
+
|
|
24
|
+
{% unless condition %}
|
|
25
|
+
Content
|
|
26
|
+
{% endunless %}
|
|
27
|
+
|
|
28
|
+
{% for item in items %}
|
|
29
|
+
{{ item }}
|
|
30
|
+
{% endfor %}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Supported Commands
|
|
34
|
+
|
|
35
|
+
### Control Flow Commands
|
|
36
|
+
- `{% if condition %}...{% endif %}` - Conditional rendering
|
|
37
|
+
- `{% unless condition %}...{% endunless %}` - Inverse conditional rendering
|
|
38
|
+
- `{% for item in collection %}...{% endfor %}` - Loop over collections
|
|
39
|
+
|
|
40
|
+
### Special Commands
|
|
41
|
+
- `{% comment %}...{% endcomment %}` - Comments (not rendered)
|
|
42
|
+
- `{% raw %}...{% endraw %}` - Raw content (no processing)
|
|
43
|
+
- `{% render 'component', prop: value %}` - Render a component
|
|
44
|
+
|
|
45
|
+
## Component Rendering
|
|
46
|
+
|
|
47
|
+
The `{% render %}` command is used to instantiate components:
|
|
48
|
+
|
|
49
|
+
```liquid
|
|
50
|
+
{% render 'button', label: 'Submit', type: 'primary' %}
|
|
51
|
+
{% render 'user-card', name: user.name, age: 25 %}
|
|
52
|
+
{% render 'components/ui/icon', name: 'check', size: 24 %}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Syntax
|
|
56
|
+
```liquid
|
|
57
|
+
{% render 'path/to/component', prop1: value1, prop2: value2 %}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Rules
|
|
61
|
+
1. **Path**: The component path (string). Only the last segment is used (e.g., `'ui/button'` → `Button`)
|
|
62
|
+
2. **Component Name**: Converted to PascalCase (e.g., `'user-card'` → `UserCard`)
|
|
63
|
+
3. **Props**: Key-value pairs separated by commas
|
|
64
|
+
4. **Values**: Can be:
|
|
65
|
+
- String literals: `'text'` or `"text"`
|
|
66
|
+
- Numbers: `42`, `3.14`
|
|
67
|
+
- Booleans: `true`, `false`
|
|
68
|
+
- Variables: `userName`, `count`
|
|
69
|
+
- Expressions: `user.name`, `items.length`
|
|
70
|
+
|
|
71
|
+
### Examples
|
|
72
|
+
|
|
73
|
+
Simple with static values:
|
|
74
|
+
```liquid
|
|
75
|
+
{% render 'button', label: 'Click me', disabled: false %}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
With variables:
|
|
79
|
+
```liquid
|
|
80
|
+
{% render 'input', name: fieldName, value: fieldValue %}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
With expressions:
|
|
84
|
+
```liquid
|
|
85
|
+
{% render 'user-card', name: user.name, count: items.length %}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Within conditionals:
|
|
89
|
+
```liquid
|
|
90
|
+
{% if showProfile %}
|
|
91
|
+
{% render 'profile', user: currentUser %}
|
|
92
|
+
{% endif %}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Important Notes for AI Agents
|
|
96
|
+
|
|
97
|
+
1. **Always use `{% %}` for the render command**, not `{{ }}`
|
|
98
|
+
2. The render command is a **tag/command**, not an expression
|
|
99
|
+
3. Component names are automatically converted from kebab-case to PascalCase
|
|
100
|
+
4. Only simple variable identifiers are registered as component inputs
|
|
101
|
+
5. Complex expressions (e.g., `user.name`) are passed through but not registered as inputs
|
package/README.md
CHANGED
|
@@ -52,8 +52,8 @@ init(() => {
|
|
|
52
52
|
```js
|
|
53
53
|
import lui_templates from 'lui-templates';
|
|
54
54
|
|
|
55
|
-
const code = await lui_templates('src/templates/
|
|
56
|
-
await fs.writeFile('src/
|
|
55
|
+
const code = await lui_templates('src/templates/greeting.liquid');
|
|
56
|
+
await fs.writeFile('src/components/greeting.js', code, 'utf8');
|
|
57
57
|
|
|
58
58
|
await bundleApp('src/main.js'); // or whatever
|
|
59
59
|
```
|
package/package.json
CHANGED
package/src/parsers/liquid.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
NODE_TYPE_COMPONENT,
|
|
2
3
|
NODE_TYPE_ELEMENT,
|
|
3
4
|
NODE_TYPE_IF,
|
|
4
5
|
VALUE_TYPE_FIELD,
|
|
@@ -25,6 +26,7 @@ const COMMAND_FOR = 3;
|
|
|
25
26
|
const COMMAND_ENDIF = 4;
|
|
26
27
|
const COMMAND_ENDUNLESS = 5;
|
|
27
28
|
const COMMAND_ENDFOR = 6;
|
|
29
|
+
const COMMAND_RENDER = 7;
|
|
28
30
|
|
|
29
31
|
const command_map = new Map([
|
|
30
32
|
['if', COMMAND_IF],
|
|
@@ -33,6 +35,7 @@ const command_map = new Map([
|
|
|
33
35
|
['endif', COMMAND_ENDIF],
|
|
34
36
|
['endunless', COMMAND_ENDUNLESS],
|
|
35
37
|
['endfor', COMMAND_ENDFOR],
|
|
38
|
+
['render', COMMAND_RENDER],
|
|
36
39
|
]);
|
|
37
40
|
const command_map_reverse = new Map(
|
|
38
41
|
Array.from(command_map.entries())
|
|
@@ -172,6 +175,214 @@ class Tokenizer {
|
|
|
172
175
|
};
|
|
173
176
|
}
|
|
174
177
|
|
|
178
|
+
/**
|
|
179
|
+
Parses a liquid expression (string, number, or variable).
|
|
180
|
+
Used in {{ }}, command arguments, and other places.
|
|
181
|
+
@returns {Object} Value object with type and data
|
|
182
|
+
*/
|
|
183
|
+
parse_liquid_expression() {
|
|
184
|
+
const position = this.position_get();
|
|
185
|
+
|
|
186
|
+
// Skip whitespace
|
|
187
|
+
while (this.index < this.src.length && /\s/.test(this.char_current())) {
|
|
188
|
+
this.char_step();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const char = this.char_current();
|
|
192
|
+
|
|
193
|
+
// String literal (single or double quoted)
|
|
194
|
+
if (char === '"' || char === "'") {
|
|
195
|
+
const quote = char;
|
|
196
|
+
this.char_step(); // Skip opening quote
|
|
197
|
+
let value = '';
|
|
198
|
+
while (this.index < this.src.length) {
|
|
199
|
+
const c = this.char_current();
|
|
200
|
+
if (c === quote) {
|
|
201
|
+
this.char_step(); // Skip closing quote
|
|
202
|
+
return {
|
|
203
|
+
type: VALUE_TYPE_STATIC,
|
|
204
|
+
data: value,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
value += c;
|
|
208
|
+
this.char_step();
|
|
209
|
+
}
|
|
210
|
+
error('Unclosed string literal', position);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Number literal (base 10 only)
|
|
214
|
+
if (/[0-9]/.test(char)) {
|
|
215
|
+
let value = '';
|
|
216
|
+
while (this.index < this.src.length && /[0-9.]/.test(this.char_current())) {
|
|
217
|
+
value += this.char_current();
|
|
218
|
+
this.char_step();
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
type: VALUE_TYPE_STATIC,
|
|
222
|
+
data: parseFloat(value),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Variable name or property access (e.g., variable, variable.prop, variable.prop.subprop)
|
|
227
|
+
// Also handles boolean literals (true, false) and nil
|
|
228
|
+
// Only allows valid identifiers separated by dots, no other JavaScript expressions
|
|
229
|
+
if (/[a-zA-Z_$]/.test(char)) {
|
|
230
|
+
let value = '';
|
|
231
|
+
let base_variable = '';
|
|
232
|
+
let first_identifier = true;
|
|
233
|
+
|
|
234
|
+
while (this.index < this.src.length) {
|
|
235
|
+
const c = this.char_current();
|
|
236
|
+
|
|
237
|
+
// Start of an identifier
|
|
238
|
+
if (/[a-zA-Z_$]/.test(c)) {
|
|
239
|
+
let identifier = '';
|
|
240
|
+
while (this.index < this.src.length && /[a-zA-Z0-9_$]/.test(this.char_current())) {
|
|
241
|
+
identifier += this.char_current();
|
|
242
|
+
this.char_step();
|
|
243
|
+
}
|
|
244
|
+
value += identifier;
|
|
245
|
+
|
|
246
|
+
// Track the first identifier as the base variable
|
|
247
|
+
if (first_identifier) {
|
|
248
|
+
base_variable = identifier;
|
|
249
|
+
first_identifier = false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Property access dot
|
|
253
|
+
else if (c === '.') {
|
|
254
|
+
// Peek ahead to ensure there's an identifier after the dot
|
|
255
|
+
if (this.index + 1 < this.src.length && /[a-zA-Z_$]/.test(this.src.charAt(this.index + 1))) {
|
|
256
|
+
value += c;
|
|
257
|
+
this.char_step();
|
|
258
|
+
} else {
|
|
259
|
+
error('Expected property name after dot', position);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Stop at delimiters
|
|
263
|
+
else if (/[\s,}%)]/.test(c)) {
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
// Invalid character
|
|
267
|
+
else {
|
|
268
|
+
error(`Invalid character '${c}' in expression`, position);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!value) {
|
|
273
|
+
error('Expected expression', position);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check for boolean literals and nil
|
|
277
|
+
if (value === 'true') {
|
|
278
|
+
return {
|
|
279
|
+
type: VALUE_TYPE_STATIC,
|
|
280
|
+
data: true,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (value === 'false') {
|
|
284
|
+
return {
|
|
285
|
+
type: VALUE_TYPE_STATIC,
|
|
286
|
+
data: false,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (value === 'nil') {
|
|
290
|
+
return {
|
|
291
|
+
type: VALUE_TYPE_STATIC,
|
|
292
|
+
data: null,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Register the base variable (not for keywords)
|
|
297
|
+
if (base_variable) {
|
|
298
|
+
this.variables.set(base_variable, null);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
type: VALUE_TYPE_FIELD,
|
|
303
|
+
data: value,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
error('Expected string, number, or variable', position);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
Parses command arguments in the format: arg1, key1: value1, key2: value2
|
|
312
|
+
Used for render and potentially other commands.
|
|
313
|
+
@param {string} args_str - The arguments string
|
|
314
|
+
@returns {Object} Parsed arguments with path and props
|
|
315
|
+
*/
|
|
316
|
+
parse_command_arguments(args_str) {
|
|
317
|
+
const position = this.position_get();
|
|
318
|
+
const saved_index = this.index;
|
|
319
|
+
const saved_src = this.src;
|
|
320
|
+
|
|
321
|
+
// Temporarily set src to args_str for parsing
|
|
322
|
+
this.src = args_str;
|
|
323
|
+
this.index = 0;
|
|
324
|
+
|
|
325
|
+
const result = {
|
|
326
|
+
path: null,
|
|
327
|
+
props: {},
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
// Parse first argument (path)
|
|
332
|
+
result.path = this.parse_liquid_expression();
|
|
333
|
+
|
|
334
|
+
// Skip whitespace and comma
|
|
335
|
+
while (this.index < this.src.length && /[\s,]/.test(this.char_current())) {
|
|
336
|
+
this.char_step();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Parse key-value pairs
|
|
340
|
+
while (this.index < this.src.length) {
|
|
341
|
+
// Skip whitespace
|
|
342
|
+
while (this.index < this.src.length && /\s/.test(this.char_current())) {
|
|
343
|
+
this.char_step();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (this.index >= this.src.length) break;
|
|
347
|
+
|
|
348
|
+
// Parse key
|
|
349
|
+
const key_start = this.index;
|
|
350
|
+
while (this.index < this.src.length && /[a-zA-Z_$0-9]/.test(this.char_current())) {
|
|
351
|
+
this.char_step();
|
|
352
|
+
}
|
|
353
|
+
const key = this.src.slice(key_start, this.index);
|
|
354
|
+
|
|
355
|
+
if (!key) break;
|
|
356
|
+
|
|
357
|
+
// Skip whitespace
|
|
358
|
+
while (this.index < this.src.length && /\s/.test(this.char_current())) {
|
|
359
|
+
this.char_step();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Expect colon
|
|
363
|
+
if (this.char_current() !== ':') {
|
|
364
|
+
error(`Expected ':' after property name '${key}'`, position);
|
|
365
|
+
}
|
|
366
|
+
this.char_step(); // Skip colon
|
|
367
|
+
|
|
368
|
+
// Parse value
|
|
369
|
+
const value = this.parse_liquid_expression();
|
|
370
|
+
result.props[key] = value;
|
|
371
|
+
|
|
372
|
+
// Skip whitespace and comma
|
|
373
|
+
while (this.index < this.src.length && /[\s,]/.test(this.char_current())) {
|
|
374
|
+
this.char_step();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} finally {
|
|
378
|
+
// Restore original src and index
|
|
379
|
+
this.src = saved_src;
|
|
380
|
+
this.index = saved_index;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
|
|
175
386
|
/**
|
|
176
387
|
Expects to be in either top level or inside a block.
|
|
177
388
|
@returns {Array} Array of tokens
|
|
@@ -337,6 +548,21 @@ class Tokenizer {
|
|
|
337
548
|
value,
|
|
338
549
|
};
|
|
339
550
|
}
|
|
551
|
+
case 'render': {
|
|
552
|
+
// Parse command arguments and store them in the token
|
|
553
|
+
const args = this.parse_command_arguments(value);
|
|
554
|
+
|
|
555
|
+
// Variable tracking is now handled inside parse_liquid_expression()
|
|
556
|
+
|
|
557
|
+
return {
|
|
558
|
+
type: TOKEN_LIQUID,
|
|
559
|
+
...position,
|
|
560
|
+
trim_before,
|
|
561
|
+
trim_after,
|
|
562
|
+
command: COMMAND_RENDER,
|
|
563
|
+
args, // Store parsed arguments instead of raw string
|
|
564
|
+
};
|
|
565
|
+
}
|
|
340
566
|
}
|
|
341
567
|
|
|
342
568
|
const command = command_map.get(command_str);
|
|
@@ -781,6 +1007,26 @@ function build_nodes(tokens, index, index_end, condition_prefix = '') {
|
|
|
781
1007
|
index = conditional.index;
|
|
782
1008
|
continue;
|
|
783
1009
|
}
|
|
1010
|
+
case COMMAND_RENDER: {
|
|
1011
|
+
const component_node = build_render_node(token);
|
|
1012
|
+
|
|
1013
|
+
if (condition_prefix) {
|
|
1014
|
+
// Wrap component in conditional
|
|
1015
|
+
nodes.push({
|
|
1016
|
+
type: NODE_TYPE_IF,
|
|
1017
|
+
condition: {
|
|
1018
|
+
type: VALUE_TYPE_FIELD,
|
|
1019
|
+
data: condition_prefix,
|
|
1020
|
+
},
|
|
1021
|
+
child: component_node,
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
nodes.push(component_node);
|
|
1026
|
+
}
|
|
1027
|
+
index++;
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
784
1030
|
case COMMAND_ENDIF:
|
|
785
1031
|
case COMMAND_ENDUNLESS:
|
|
786
1032
|
case COMMAND_ENDFOR:
|
|
@@ -864,8 +1110,8 @@ function build_element_node(tokens, index) {
|
|
|
864
1110
|
case TOKEN_HTML_END:
|
|
865
1111
|
if (token.tag_name === token_start.tag_name) break loop;
|
|
866
1112
|
case TOKEN_LIQUID:
|
|
867
|
-
// apart from loops, everything is allowed in text-only
|
|
868
|
-
if (token.command !== COMMAND_FOR) break;
|
|
1113
|
+
// apart from loops and render commands, everything is allowed in text-only
|
|
1114
|
+
if (token.command !== COMMAND_FOR && token.command !== COMMAND_RENDER) break;
|
|
869
1115
|
case TOKEN_HTML_START:
|
|
870
1116
|
text_only = false;
|
|
871
1117
|
break text_extract;
|
|
@@ -939,7 +1185,7 @@ function build_children(tokens, index, tag_parent) {
|
|
|
939
1185
|
text_only = false;
|
|
940
1186
|
break;
|
|
941
1187
|
case TOKEN_LIQUID:
|
|
942
|
-
if (token.command === COMMAND_IF || token.command === COMMAND_UNLESS) {
|
|
1188
|
+
if (token.command === COMMAND_IF || token.command === COMMAND_UNLESS || token.command === COMMAND_RENDER) {
|
|
943
1189
|
text_only = false;
|
|
944
1190
|
}
|
|
945
1191
|
break;
|
|
@@ -1144,6 +1390,51 @@ function build_value_with_conditionals(tokens, condition_prefix = '') {
|
|
|
1144
1390
|
};
|
|
1145
1391
|
}
|
|
1146
1392
|
|
|
1393
|
+
/**
|
|
1394
|
+
Builds a component node from a render command token.
|
|
1395
|
+
@param {Object} token - The render command token with pre-parsed args
|
|
1396
|
+
@returns {Object} Component node
|
|
1397
|
+
*/
|
|
1398
|
+
function build_render_node(token) {
|
|
1399
|
+
const { args } = token;
|
|
1400
|
+
|
|
1401
|
+
// Extract path value
|
|
1402
|
+
let path = '';
|
|
1403
|
+
if (args.path.type === VALUE_TYPE_STATIC) {
|
|
1404
|
+
path = args.path.data;
|
|
1405
|
+
} else if (args.path.type === VALUE_TYPE_FIELD) {
|
|
1406
|
+
// If path is a variable, we can't determine the component name at parse time
|
|
1407
|
+
// For now, just use the variable name as component name
|
|
1408
|
+
path = args.path.data;
|
|
1409
|
+
} else {
|
|
1410
|
+
error('Invalid path type in render command', token);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// Extract component name from path (only use last part after splitting by '/')
|
|
1414
|
+
const component_name = path.split('/').pop();
|
|
1415
|
+
if (!component_name) error('Invalid component path in render command', token);
|
|
1416
|
+
|
|
1417
|
+
// Format component name to PascalCase (kebab-case to PascalCase)
|
|
1418
|
+
// e.g., 'user-card' -> 'UserCard', 'button' -> 'Button'
|
|
1419
|
+
const component = (
|
|
1420
|
+
component_name
|
|
1421
|
+
.charAt(0).toUpperCase() +
|
|
1422
|
+
component_name.slice(1)
|
|
1423
|
+
.replace(/-([a-z])/g, (_, char) => char.toUpperCase())
|
|
1424
|
+
.replace(/_([a-z])/g, (_, char) => char.toUpperCase())
|
|
1425
|
+
);
|
|
1426
|
+
|
|
1427
|
+
// Props are already parsed, just use them directly
|
|
1428
|
+
const props = args.props;
|
|
1429
|
+
|
|
1430
|
+
return {
|
|
1431
|
+
type: NODE_TYPE_COMPONENT,
|
|
1432
|
+
component,
|
|
1433
|
+
props,
|
|
1434
|
+
children: [],
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1147
1438
|
/**
|
|
1148
1439
|
Builds a value object out of text and expression tokens.
|
|
1149
1440
|
Trims whitespace from the start and end.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<!-- Simple render with static values -->
|
|
3
|
+
{% render 'button', label: 'Submit', type: 'primary' %}
|
|
4
|
+
|
|
5
|
+
<!-- Render with variables -->
|
|
6
|
+
{% render 'input', name: fieldName, value: fieldValue %}
|
|
7
|
+
|
|
8
|
+
<!-- Render with complex expressions -->
|
|
9
|
+
{% render 'user-card', name: user.name, age: user.age %}
|
|
10
|
+
|
|
11
|
+
<!-- Render with nested paths (only last part is used) -->
|
|
12
|
+
{% render 'components/ui/icon', name: 'check', size: 24 %}
|
|
13
|
+
|
|
14
|
+
<!-- Render with boolean and number literals -->
|
|
15
|
+
{% render 'checkbox', checked: true, count: 5 %}
|
|
16
|
+
|
|
17
|
+
<!-- Render within conditionals -->
|
|
18
|
+
{% if showProfile %}
|
|
19
|
+
{% render 'profile', user: currentUser %}
|
|
20
|
+
{% endif %}
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
{% render 'button', label: 'Click me', color: 'blue' %}
|
|
3
|
+
{% render 'components/nested/icon', name: iconName, size: 24 %}
|
|
4
|
+
<p>Some text</p>
|
|
5
|
+
{% render 'link', href: url, text: linkText %}
|
|
6
|
+
{% render 'card', title: 'Test', enabled: true, count: 5 %}
|
|
7
|
+
</div>
|