Add docs on templating

master
Araozu 2024-07-19 22:08:37 -05:00
parent 2eb6e13d32
commit aa357101fb
8 changed files with 442 additions and 7 deletions

View 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>

View File

@ -7,9 +7,13 @@ export function highlightOnDom() {
// Create a visual indicador
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;
pre_el.appendChild(indicator);
}
}

View File

@ -43,7 +43,7 @@ case Cat(name, lives)
<br class="hidden lg:inline-block" />
compiled to PHP
</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,
semantics, type system and stdlib.
</p>

View File

@ -52,6 +52,9 @@ pagesLayout:
title: Templating
children:
- path: intro
- path: components
- path: props
- path: control-flow
---
import InteractiveCode from "../../components/InteractiveCode.astro";
import Code from "../../components/Code.astro"

View 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: &lt;b&gt;BOLD&lt;/b&gt;</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>
```

View 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

View File

@ -3,6 +3,7 @@ layout: ../../../layouts/DocsLayout.astro
title: Introduction
---
import Code from "../../../components/Code.astro"
import Info from "../../../components/docs/Info.astro"
# THP templating
@ -62,7 +63,7 @@ and compose them.
The following would be the equivalent in THP:
<Code thpcode={`
fun Button(String name) -> Html {
fun Button(String name) -> HTML {
<button class="some tailwind classes">
Hello {name}!
</button>
@ -74,7 +75,7 @@ way around, so you can have arbitrary logic in the component.
<Code thpcode={`
fun User(String name) {
fun User(String name) -> HTML {
// Get info from the database
val user = try Model::get_user(name)
else {
@ -87,13 +88,13 @@ fun User(String name) {
Hello {user.name}!
<br>
Here are your transactions:
#for t in user.transactions {
@for t in user.transactions {
<TransactionItem t={t} />
}
</div>
}
fun TransactionItem(Transaction t) {
fun TransactionItem(Transaction t) -> HTML {
<li>
{t.date} - {t.name} ({t.price})
</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>"
}
```

View 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>
```