Add docs on templating
This commit is contained in:
parent
2eb6e13d32
commit
aa357101fb
4
src/components/docs/Info.astro
Normal file
4
src/components/docs/Info.astro
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<div class="my-6 px-4 py-2 rounded bg-[#f8e287] text-[#221b00] dark:bg-[#dbc66e] dark:text-[#3a3000]">
|
||||||
|
<div class="font-bold pt-2">Warning</div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
@ -7,9 +7,13 @@ export function highlightOnDom() {
|
|||||||
|
|
||||||
// Create a visual indicador
|
// Create a visual indicador
|
||||||
const indicator = document.createElement("span");
|
const indicator = document.createElement("span");
|
||||||
indicator.className = `absolute top-1 right-0 inline-block text-sm select-none opacity-75 ${language === "php" ? "bg-[#4f5b93]" : ""} px-2 rounded-full`;
|
|
||||||
|
let indicator_bg_class = "";
|
||||||
|
if (language === "php") { indicator_bg_class = "bg-[#4f5b93]"; }
|
||||||
|
else if (language === "html") { indicator_bg_class = "bg-[#dc4a20]"; }
|
||||||
|
|
||||||
|
indicator.className = `absolute top-1 right-0 inline-block text-sm select-none opacity-85 ${indicator_bg_class} px-2 rounded-full`;
|
||||||
indicator.innerText = language;
|
indicator.innerText = language;
|
||||||
pre_el.appendChild(indicator);
|
pre_el.appendChild(indicator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ case Cat(name, lives)
|
|||||||
<br class="hidden lg:inline-block" />
|
<br class="hidden lg:inline-block" />
|
||||||
compiled to PHP
|
compiled to PHP
|
||||||
</h1>
|
</h1>
|
||||||
<p class="font-display text-c-text text-xl pt-6 lg:pr-12">
|
<p class="text-c-text text-lg pt-6 lg:pr-12">
|
||||||
Inspired by Rust, Zig and Kotlin, THP has a modern syntax,
|
Inspired by Rust, Zig and Kotlin, THP has a modern syntax,
|
||||||
semantics, type system and stdlib.
|
semantics, type system and stdlib.
|
||||||
</p>
|
</p>
|
||||||
|
@ -52,6 +52,9 @@ pagesLayout:
|
|||||||
title: Templating
|
title: Templating
|
||||||
children:
|
children:
|
||||||
- path: intro
|
- path: intro
|
||||||
|
- path: components
|
||||||
|
- path: props
|
||||||
|
- path: control-flow
|
||||||
---
|
---
|
||||||
import InteractiveCode from "../../components/InteractiveCode.astro";
|
import InteractiveCode from "../../components/InteractiveCode.astro";
|
||||||
import Code from "../../components/Code.astro"
|
import Code from "../../components/Code.astro"
|
||||||
|
194
src/pages/learn/templating/components.mdx
Normal file
194
src/pages/learn/templating/components.mdx
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
---
|
||||||
|
layout: ../../../layouts/DocsLayout.astro
|
||||||
|
title: Components
|
||||||
|
---
|
||||||
|
import Code from "../../../components/Code.astro"
|
||||||
|
import Info from "../../../components/docs/Info.astro"
|
||||||
|
|
||||||
|
# Components
|
||||||
|
|
||||||
|
Like React, a component is any function that returns `HTML`.
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
We still need to determine whether the functions names
|
||||||
|
will be uppercase or not.
|
||||||
|
|
||||||
|
For now they will be uppercase.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun MyComponent() -> HTML
|
||||||
|
{
|
||||||
|
<p>Hello templates!</p>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
Inside the HTML tags you can (mostly) write the HTML you already
|
||||||
|
know.
|
||||||
|
|
||||||
|
## Text interpolation
|
||||||
|
|
||||||
|
Interpolation inside HTML works the same way as string interpolation.
|
||||||
|
Use brackets to insert an expression to the HTML:
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun MyComponent() -> HTML
|
||||||
|
{
|
||||||
|
val name = "John"
|
||||||
|
|
||||||
|
<p>Hello {name}!</p>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p>Hello John!</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use any expression that can be converted to a `String` inside,
|
||||||
|
like conditionals.
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun MyComponent() -> HTML
|
||||||
|
{
|
||||||
|
val name = "John"
|
||||||
|
val is_vip = true
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{if is_vip {"Welcome"} else {"Hi"}}
|
||||||
|
{name}!
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p>Welcome John!</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Raw HTML
|
||||||
|
|
||||||
|
Text interpolated is always escaped:
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun MyComponent() -> HTML
|
||||||
|
{
|
||||||
|
val user_input = "<b>BOLD</b>"
|
||||||
|
|
||||||
|
<p>answer: {user_input}</p>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
```html
|
||||||
|
<p>answer: <b>BOLD</b></p>
|
||||||
|
```
|
||||||
|
|
||||||
|
To include raw html use the `raw-html` attribute (subject to change):
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun MyComponent() -> HTML
|
||||||
|
{
|
||||||
|
val user_input = "<b>BOLD</b>"
|
||||||
|
|
||||||
|
<p>answer: <span raw-html={user_input}></span></p>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
```html
|
||||||
|
<p>answer: <span><b>BOLD</b></span></p>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Dynamic attributes
|
||||||
|
|
||||||
|
TODO: boolean attributes
|
||||||
|
|
||||||
|
Normal attributes (plain strings) work as you'd expect:
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun MyComponent() -> HTML
|
||||||
|
{
|
||||||
|
<button class="red">hello</button>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
```html
|
||||||
|
<button class="red">hello</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Dynamic attributes are used when you want to use values from code.
|
||||||
|
For those use brackets instead of double quotes:
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun MyComponent() -> HTML
|
||||||
|
{
|
||||||
|
val button_class = if true {"blue"} else {"yellow"}
|
||||||
|
|
||||||
|
// Note the braces
|
||||||
|
<button class={button_class}>hello</button>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
```html
|
||||||
|
<button class="blue">hello</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Fragments
|
||||||
|
|
||||||
|
An HTML expression consist of a single tag that may have children inside.
|
||||||
|
If you need to return multiple tags at once you can use fragments.
|
||||||
|
|
||||||
|
|
||||||
|
The following code doesn't work as you would expect:
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun MyComponent() -> HTML
|
||||||
|
{
|
||||||
|
<p>hello</p> // This is an error, an ignored expression
|
||||||
|
<p>world</p>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
Each `<p>` is a single expression, they are not grouped.
|
||||||
|
And since we cannot have unused expressions, the code will not compile.
|
||||||
|
|
||||||
|
To have these two `<p>` tags as a single expression use a fragment:
|
||||||
|
`<></>`
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun MyComponent() -> HTML
|
||||||
|
{
|
||||||
|
// This is the root "element"
|
||||||
|
<>
|
||||||
|
<p>hello</p> // Now these two are together
|
||||||
|
<p>world</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
```html
|
||||||
|
<p>hello</p>
|
||||||
|
<p>world</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Composition
|
||||||
|
|
||||||
|
To use a component inside another component call them as if it were an
|
||||||
|
html tag:
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun User() -> HTML
|
||||||
|
{
|
||||||
|
<span>I am the user</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MyComponent() -> HTML
|
||||||
|
{
|
||||||
|
<>
|
||||||
|
<p>status</p>
|
||||||
|
<User /> // Here we are using the other component
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
```html
|
||||||
|
<p>status</p>
|
||||||
|
<span>world</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
9
src/pages/learn/templating/control-flow.mdx
Normal file
9
src/pages/learn/templating/control-flow.mdx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
layout: ../../../layouts/DocsLayout.astro
|
||||||
|
title: Control flow
|
||||||
|
---
|
||||||
|
import Code from "../../../components/Code.astro"
|
||||||
|
import Info from "../../../components/docs/Info.astro"
|
||||||
|
|
||||||
|
# Control flow
|
||||||
|
|
@ -3,6 +3,7 @@ layout: ../../../layouts/DocsLayout.astro
|
|||||||
title: Introduction
|
title: Introduction
|
||||||
---
|
---
|
||||||
import Code from "../../../components/Code.astro"
|
import Code from "../../../components/Code.astro"
|
||||||
|
import Info from "../../../components/docs/Info.astro"
|
||||||
|
|
||||||
# THP templating
|
# THP templating
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ and compose them.
|
|||||||
The following would be the equivalent in THP:
|
The following would be the equivalent in THP:
|
||||||
|
|
||||||
<Code thpcode={`
|
<Code thpcode={`
|
||||||
fun Button(String name) -> Html {
|
fun Button(String name) -> HTML {
|
||||||
<button class="some tailwind classes">
|
<button class="some tailwind classes">
|
||||||
Hello {name}!
|
Hello {name}!
|
||||||
</button>
|
</button>
|
||||||
@ -74,7 +75,7 @@ way around, so you can have arbitrary logic in the component.
|
|||||||
|
|
||||||
|
|
||||||
<Code thpcode={`
|
<Code thpcode={`
|
||||||
fun User(String name) {
|
fun User(String name) -> HTML {
|
||||||
// Get info from the database
|
// Get info from the database
|
||||||
val user = try Model::get_user(name)
|
val user = try Model::get_user(name)
|
||||||
else {
|
else {
|
||||||
@ -87,13 +88,13 @@ fun User(String name) {
|
|||||||
Hello {user.name}!
|
Hello {user.name}!
|
||||||
<br>
|
<br>
|
||||||
Here are your transactions:
|
Here are your transactions:
|
||||||
#for t in user.transactions {
|
@for t in user.transactions {
|
||||||
<TransactionItem t={t} />
|
<TransactionItem t={t} />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TransactionItem(Transaction t) {
|
fun TransactionItem(Transaction t) -> HTML {
|
||||||
<li>
|
<li>
|
||||||
{t.date} - {t.name} ({t.price})
|
{t.date} - {t.name} ({t.price})
|
||||||
</li>
|
</li>
|
||||||
@ -101,3 +102,67 @@ fun TransactionItem(Transaction t) {
|
|||||||
`} />
|
`} />
|
||||||
|
|
||||||
|
|
||||||
|
## Is this a JavaScript Front-End Framework?
|
||||||
|
|
||||||
|
**No**
|
||||||
|
|
||||||
|
This is an HTML templating solution. All the code is run in the backend,
|
||||||
|
generates static HTML and sends it to the browser. There is no
|
||||||
|
reactivity, hooks, signals, etc.
|
||||||
|
|
||||||
|
If you need reactivity on the front-end and want to use this templating
|
||||||
|
take a look at [htmx](https://htmx.org/), [Alpine.js](https://alpinejs.dev/)
|
||||||
|
and [hyperscript](https://hyperscript.org/).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
|
||||||
|
We don't provide any syntax or facility for styling.
|
||||||
|
However, this component model is good to use with TailwindCSS.
|
||||||
|
|
||||||
|
|
||||||
|
## Conversion
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
TBD: This is a draft, subject to change.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
HTML expressions will be compiled to plain strings, and
|
||||||
|
those will be then composed.
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun Sample(String name) -> HTML {
|
||||||
|
val user = Model::get_user(name)
|
||||||
|
|
||||||
|
<button>
|
||||||
|
@if user != null
|
||||||
|
{
|
||||||
|
Hello {name} (id {user.name})!
|
||||||
|
}
|
||||||
|
@else
|
||||||
|
{
|
||||||
|
Not logged in
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
```php
|
||||||
|
function Sample(name) {
|
||||||
|
$user = Model::get_user(name);
|
||||||
|
|
||||||
|
$__expr_1 = "";
|
||||||
|
if ($user !== null) {
|
||||||
|
$__expr_2 = user.name;
|
||||||
|
$__expr_1 = "Hello {$name} (id {$__expr_2})";
|
||||||
|
} else {
|
||||||
|
$__expr_1 = "Not logged in";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "<button>{$__expr_1}</button>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
156
src/pages/learn/templating/props.mdx
Normal file
156
src/pages/learn/templating/props.mdx
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
---
|
||||||
|
layout: ../../../layouts/DocsLayout.astro
|
||||||
|
title: Props
|
||||||
|
---
|
||||||
|
import Code from "../../../components/Code.astro"
|
||||||
|
import Info from "../../../components/docs/Info.astro"
|
||||||
|
|
||||||
|
# Props
|
||||||
|
|
||||||
|
Props are used to send data to a children component.
|
||||||
|
|
||||||
|
THP's components can receive any datatype as props,
|
||||||
|
and they are defined as normal parameters.
|
||||||
|
|
||||||
|
For example, to receive a `String` declare it as a
|
||||||
|
parameter:
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun Greeter(String name) -> HTML
|
||||||
|
{
|
||||||
|
<p>Hello {name}!</p>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
And to send its value type the parameter name as an attribute:
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun Home() -> HTML
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<Greeter name="Rose" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div><p>Hello Rose</p></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can have as many props as you'd like, of any datatype:
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun Greeter(String name, Int age, Array[String] friends) -> HTML
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
## Static props
|
||||||
|
|
||||||
|
If the prop has a type `String` you can use a normal attribute.
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun Home() -> HTML
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
// name is a String, so we use ""
|
||||||
|
<Greeter name="Rose" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
|
||||||
|
## Dynamic props
|
||||||
|
|
||||||
|
However, if the prop has any other datatype you must use a
|
||||||
|
dynamic attribute (`{}`)
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
// This component receives a Cat object
|
||||||
|
fun Sample(Cat cat) -> HTML
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Home() -> HTML
|
||||||
|
{
|
||||||
|
val my_cat = Cat("Michifu")
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Sample cat={my_cat} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
|
||||||
|
## Components as props
|
||||||
|
|
||||||
|
If for some reason you want to use a component as a prop
|
||||||
|
use the `HTML` datatype:
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
// The parameter can have any name, not only \`child\`
|
||||||
|
fun Sample(HTML child) -> HTML
|
||||||
|
{
|
||||||
|
<>
|
||||||
|
<p>Sup</p>
|
||||||
|
{child}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
This, however, means that your prop component must be declared
|
||||||
|
as an attribute:
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun Home() -> HTML
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<Sample child={<span>I am the child</span>} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div>
|
||||||
|
<p>Sup</p>
|
||||||
|
<span>I am the child</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
A better solution may be to use slots.
|
||||||
|
|
||||||
|
## Slots
|
||||||
|
|
||||||
|
Slots allow you to write html inside a component tag.
|
||||||
|
|
||||||
|
<Code thpcode={`
|
||||||
|
fun MyButton() -> HTML
|
||||||
|
{
|
||||||
|
<button>
|
||||||
|
// This is the slot, it will render the HTML inside the tag
|
||||||
|
<Slot />
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Home() -> HTML
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
<MyButton>
|
||||||
|
buy <b>now!</b>
|
||||||
|
</MyButton>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
`} />
|
||||||
|
```html
|
||||||
|
<div>
|
||||||
|
<button>
|
||||||
|
buy <b>now!</b>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user