@relevate/katachi 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/CONTRIBUTING.md +60 -0
- package/LICENSE +21 -0
- package/README.md +194 -0
- package/bin/katachi.mjs +30 -0
- package/dist/api/index.d.ts +54 -0
- package/dist/api/index.js +45 -0
- package/dist/api/jsx.d.ts +26 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +77 -0
- package/dist/core/ast.d.ts +115 -0
- package/dist/core/ast.js +51 -0
- package/dist/core/build.d.ts +15 -0
- package/dist/core/build.js +107 -0
- package/dist/core/compiler.d.ts +9 -0
- package/dist/core/compiler.js +9 -0
- package/dist/core/example-fixtures.d.ts +5 -0
- package/dist/core/example-fixtures.js +54 -0
- package/dist/core/parser.d.ts +5 -0
- package/dist/core/parser.js +637 -0
- package/dist/core/types.d.ts +65 -0
- package/dist/core/types.js +1 -0
- package/dist/core/verify.d.ts +25 -0
- package/dist/core/verify.js +270 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/targets/askama.d.ts +11 -0
- package/dist/targets/askama.js +122 -0
- package/dist/targets/index.d.ts +5 -0
- package/dist/targets/index.js +60 -0
- package/dist/targets/react.d.ts +4 -0
- package/dist/targets/react.js +28 -0
- package/dist/targets/shared.d.ts +36 -0
- package/dist/targets/shared.js +278 -0
- package/dist/targets/static-jsx.d.ts +4 -0
- package/dist/targets/static-jsx.js +28 -0
- package/dist/verify-examples.d.ts +1 -0
- package/dist/verify-examples.js +14 -0
- package/docs/architecture.md +122 -0
- package/docs/getting-started.md +154 -0
- package/docs/syntax.md +236 -0
- package/docs/targets.md +53 -0
- package/examples/basic/README.md +67 -0
- package/examples/basic/components/badge-chip.html +3 -0
- package/examples/basic/components/comparison-table.html +24 -0
- package/examples/basic/components/glyph.html +6 -0
- package/examples/basic/components/hover-note.html +6 -0
- package/examples/basic/components/media-frame.html +15 -0
- package/examples/basic/components/notice-panel.html +24 -0
- package/examples/basic/components/resource-tile.html +24 -0
- package/examples/basic/components/stack-shell.html +3 -0
- package/examples/basic/dist/askama/badge-chip.rs +18 -0
- package/examples/basic/dist/askama/comparison-table.rs +47 -0
- package/examples/basic/dist/askama/glyph.rs +21 -0
- package/examples/basic/dist/askama/hover-note.rs +19 -0
- package/examples/basic/dist/askama/includes/badge-chip.html +5 -0
- package/examples/basic/dist/askama/includes/comparison-table.html +34 -0
- package/examples/basic/dist/askama/includes/glyph.html +6 -0
- package/examples/basic/dist/askama/includes/hover-note.html +6 -0
- package/examples/basic/dist/askama/includes/media-frame.html +23 -0
- package/examples/basic/dist/askama/includes/notice-panel.html +34 -0
- package/examples/basic/dist/askama/includes/resource-tile.html +42 -0
- package/examples/basic/dist/askama/includes/stack-shell.html +5 -0
- package/examples/basic/dist/askama/media-frame.rs +37 -0
- package/examples/basic/dist/askama/notice-panel.rs +49 -0
- package/examples/basic/dist/askama/resource-tile.rs +59 -0
- package/examples/basic/dist/askama/stack-shell.rs +17 -0
- package/examples/basic/dist/jsx-static/badge-chip.tsx +18 -0
- package/examples/basic/dist/jsx-static/comparison-table.tsx +53 -0
- package/examples/basic/dist/jsx-static/glyph.tsx +21 -0
- package/examples/basic/dist/jsx-static/hover-note.tsx +19 -0
- package/examples/basic/dist/jsx-static/media-frame.tsx +41 -0
- package/examples/basic/dist/jsx-static/notice-panel.tsx +53 -0
- package/examples/basic/dist/jsx-static/resource-tile.tsx +63 -0
- package/examples/basic/dist/jsx-static/stack-shell.tsx +17 -0
- package/examples/basic/dist/react/badge-chip.tsx +18 -0
- package/examples/basic/dist/react/comparison-table.tsx +53 -0
- package/examples/basic/dist/react/glyph.tsx +21 -0
- package/examples/basic/dist/react/hover-note.tsx +19 -0
- package/examples/basic/dist/react/media-frame.tsx +41 -0
- package/examples/basic/dist/react/notice-panel.tsx +53 -0
- package/examples/basic/dist/react/resource-tile.tsx +63 -0
- package/examples/basic/dist/react/stack-shell.tsx +17 -0
- package/examples/basic/src/templates/badge-chip.template.tsx +18 -0
- package/examples/basic/src/templates/comparison-table.template.tsx +35 -0
- package/examples/basic/src/templates/glyph.template.tsx +17 -0
- package/examples/basic/src/templates/hover-note.template.tsx +17 -0
- package/examples/basic/src/templates/media-frame.template.tsx +25 -0
- package/examples/basic/src/templates/notice-panel.template.tsx +40 -0
- package/examples/basic/src/templates/resource-tile.template.tsx +51 -0
- package/examples/basic/src/templates/stack-shell.template.tsx +13 -0
- package/examples/basic/tsconfig.json +10 -0
- package/package.json +69 -0
package/docs/syntax.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Template Syntax
|
|
2
|
+
|
|
3
|
+
Katachi templates are written in restricted TSX.
|
|
4
|
+
|
|
5
|
+
That means:
|
|
6
|
+
|
|
7
|
+
- templates look like TSX
|
|
8
|
+
- editors can parse them
|
|
9
|
+
- the compiler only accepts a subset that can lower cleanly into the portable AST
|
|
10
|
+
|
|
11
|
+
This guide focuses on the supported syntax you can use in a normal Katachi
|
|
12
|
+
project.
|
|
13
|
+
|
|
14
|
+
## File shape
|
|
15
|
+
|
|
16
|
+
A template file should export:
|
|
17
|
+
|
|
18
|
+
- `Props`
|
|
19
|
+
- a default component function
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { For, If, isEmpty, len, safe, type TemplateNode } from "@relevate/katachi";
|
|
25
|
+
|
|
26
|
+
export type Props = {
|
|
27
|
+
title: string;
|
|
28
|
+
rows: string[][];
|
|
29
|
+
children?: TemplateNode;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default function Example({ title, rows, children }: Props) {
|
|
33
|
+
return (
|
|
34
|
+
<section>
|
|
35
|
+
<h2>{title}</h2>
|
|
36
|
+
<For each={rows} as="row">
|
|
37
|
+
<div>{safe(row[0])}</div>
|
|
38
|
+
</For>
|
|
39
|
+
<If test={len(rows) == 0}>
|
|
40
|
+
<p>Empty</p>
|
|
41
|
+
</If>
|
|
42
|
+
{children}
|
|
43
|
+
</section>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
In a real project, place these files in:
|
|
49
|
+
|
|
50
|
+
```txt
|
|
51
|
+
src/templates/**/*.template.tsx
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Supported constructs
|
|
55
|
+
|
|
56
|
+
### Intrinsic elements
|
|
57
|
+
|
|
58
|
+
Normal lowercase JSX tags work as expected:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<div />
|
|
62
|
+
<span>{value}</span>
|
|
63
|
+
<img src={src} alt={alt} />
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Imported template components
|
|
67
|
+
|
|
68
|
+
Capitalized tags are treated as template component invocations.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import Icon from "./icon.template";
|
|
72
|
+
|
|
73
|
+
<Icon icon="search" size="16" />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Children and slots
|
|
77
|
+
|
|
78
|
+
`children` is treated as a slot.
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
type Props = {
|
|
82
|
+
children?: TemplateNode;
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
<div>{children}</div>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### `If`
|
|
91
|
+
|
|
92
|
+
Use `If` for conditional rendering.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { If } from "@relevate/katachi";
|
|
96
|
+
|
|
97
|
+
<If test={variant == "warning"}>
|
|
98
|
+
<p>Warning</p>
|
|
99
|
+
</If>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `For`
|
|
103
|
+
|
|
104
|
+
Use `For` for loops.
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { For } from "@relevate/katachi";
|
|
108
|
+
|
|
109
|
+
<For each={rows} as="row">
|
|
110
|
+
<div>{row}</div>
|
|
111
|
+
</For>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Optional index binding:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
<For each={rows} as="row" index="i">
|
|
118
|
+
<div>{i}</div>
|
|
119
|
+
</For>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `safe(...)`
|
|
123
|
+
|
|
124
|
+
Use `safe(...)` for raw/safe HTML output.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
import { safe } from "@relevate/katachi";
|
|
128
|
+
|
|
129
|
+
<div>{safe(value)}</div>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Portable helpers
|
|
133
|
+
|
|
134
|
+
Use Katachi's portable helpers instead of target-specific template methods.
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { If, isEmpty, isNone, isSome, len } from "@relevate/katachi";
|
|
138
|
+
|
|
139
|
+
<If test={len(rows) == 0}>
|
|
140
|
+
<p>Empty</p>
|
|
141
|
+
</If>
|
|
142
|
+
|
|
143
|
+
<If test={isSome(breadcrumbs)}>
|
|
144
|
+
<div>{breadcrumbs}</div>
|
|
145
|
+
</If>
|
|
146
|
+
|
|
147
|
+
<If test={isNone(errorMessage) || isEmpty(errorMessage)}>
|
|
148
|
+
<p>No details</p>
|
|
149
|
+
</If>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Dynamic classes
|
|
153
|
+
|
|
154
|
+
Both `class` and `className` are supported in authoring input.
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
<div
|
|
158
|
+
className={[
|
|
159
|
+
"rounded-xl border",
|
|
160
|
+
variant == "note" && "border-primary/20 bg-primary/5",
|
|
161
|
+
variant == "warning" && "border-amber-500/20 bg-amber-50/50",
|
|
162
|
+
]}
|
|
163
|
+
/>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
This is Katachi syntax hosted in TSX. The compiler normalizes it and emits target-specific output.
|
|
167
|
+
|
|
168
|
+
## Expressions
|
|
169
|
+
|
|
170
|
+
Supported directly:
|
|
171
|
+
|
|
172
|
+
- variables
|
|
173
|
+
- string literals
|
|
174
|
+
- boolean literals
|
|
175
|
+
- number literals
|
|
176
|
+
- `==`, `!=`, `===`, `!==`
|
|
177
|
+
- `&&`
|
|
178
|
+
- `||`
|
|
179
|
+
- `!`
|
|
180
|
+
|
|
181
|
+
Best-effort passthrough currently exists for some Rust-ish expressions:
|
|
182
|
+
|
|
183
|
+
- `.len()`
|
|
184
|
+
- `.is_empty()`
|
|
185
|
+
- `.is_some()`
|
|
186
|
+
- `.is_none()`
|
|
187
|
+
- `.unwrap()`
|
|
188
|
+
- `.clone().unwrap()`
|
|
189
|
+
|
|
190
|
+
These are migration shims for existing Askama-style templates. Prefer the
|
|
191
|
+
portable helpers in new Katachi templates:
|
|
192
|
+
|
|
193
|
+
- `len(value)`
|
|
194
|
+
- `isEmpty(value)`
|
|
195
|
+
- `isSome(value)`
|
|
196
|
+
- `isNone(value)`
|
|
197
|
+
|
|
198
|
+
## Helper exports
|
|
199
|
+
|
|
200
|
+
`@relevate/katachi` exports:
|
|
201
|
+
|
|
202
|
+
- `ClassValue`
|
|
203
|
+
- `TemplateNode`
|
|
204
|
+
- `If`
|
|
205
|
+
- `For`
|
|
206
|
+
- `safe`
|
|
207
|
+
- `len`
|
|
208
|
+
- `isEmpty`
|
|
209
|
+
- `isSome`
|
|
210
|
+
- `isNone`
|
|
211
|
+
|
|
212
|
+
These are the public helper exports you use inside template files.
|
|
213
|
+
|
|
214
|
+
## What is not supported
|
|
215
|
+
|
|
216
|
+
Not all TSX is valid Katachi input.
|
|
217
|
+
|
|
218
|
+
Examples of unsupported or intentionally out-of-scope areas:
|
|
219
|
+
|
|
220
|
+
- hooks
|
|
221
|
+
- arbitrary function calls as template logic
|
|
222
|
+
- local mutation
|
|
223
|
+
- arbitrary statements in component bodies
|
|
224
|
+
- imports used for runtime React behavior
|
|
225
|
+
- framework-specific runtime features
|
|
226
|
+
|
|
227
|
+
In practice, treat template files as declarative template sources, not general
|
|
228
|
+
React modules.
|
|
229
|
+
|
|
230
|
+
## Recommended style
|
|
231
|
+
|
|
232
|
+
- keep component bodies declarative
|
|
233
|
+
- prefer `If` and `For` instead of ad hoc expression trees
|
|
234
|
+
- use imported Katachi templates for nested components
|
|
235
|
+
- prefer `className` in authoring files for editor familiarity
|
|
236
|
+
- keep prop types simple and serializable where possible
|
package/docs/targets.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Targets
|
|
2
|
+
|
|
3
|
+
Katachi emits multiple outputs from the same template source.
|
|
4
|
+
|
|
5
|
+
This guide is about what gets generated and how you would typically use those
|
|
6
|
+
outputs in a real project.
|
|
7
|
+
|
|
8
|
+
## Current targets
|
|
9
|
+
|
|
10
|
+
### `react`
|
|
11
|
+
|
|
12
|
+
- output folder: `dist/react`
|
|
13
|
+
- file type: `.tsx`
|
|
14
|
+
- purpose: React-consumable component output for apps and editor environments
|
|
15
|
+
|
|
16
|
+
### `jsx-static`
|
|
17
|
+
|
|
18
|
+
- output folder: `dist/jsx-static`
|
|
19
|
+
- file type: `.tsx`
|
|
20
|
+
- purpose: TSX output oriented toward static readability
|
|
21
|
+
|
|
22
|
+
### `askama`
|
|
23
|
+
|
|
24
|
+
- output folder: `dist/askama`
|
|
25
|
+
- file type: `.rs`
|
|
26
|
+
- purpose: Rust Askama wrapper output
|
|
27
|
+
|
|
28
|
+
### `askama-includes`
|
|
29
|
+
|
|
30
|
+
- output folder: `dist/askama/includes`
|
|
31
|
+
- file type: `.html`
|
|
32
|
+
- purpose: Askama partial output
|
|
33
|
+
|
|
34
|
+
## Which output should you use?
|
|
35
|
+
|
|
36
|
+
- Use `dist/react` if your consumer is a React app or an editor surface built in React.
|
|
37
|
+
- Use `dist/jsx-static` if you want a TSX artifact that reads a bit more statically.
|
|
38
|
+
- Use `dist/askama` and `dist/askama/includes` if your consumer is Rust + Askama.
|
|
39
|
+
|
|
40
|
+
## Relative imports and includes
|
|
41
|
+
|
|
42
|
+
Nested Katachi templates keep their relative structure in generated output.
|
|
43
|
+
|
|
44
|
+
That means:
|
|
45
|
+
|
|
46
|
+
- a nested React component import stays relative in `dist/react`
|
|
47
|
+
- a nested Askama include stays relative in `dist/askama/includes`
|
|
48
|
+
|
|
49
|
+
## Internal note
|
|
50
|
+
|
|
51
|
+
If you are extending Katachi itself, the target registry lives in
|
|
52
|
+
[src/targets/index.ts](../src/targets/index.ts), and each target has its own
|
|
53
|
+
emitter module under [src/targets](../src/targets).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Basic Example
|
|
2
|
+
|
|
3
|
+
This example is a small consumer-style Katachi project you can copy from when
|
|
4
|
+
setting up your own repo.
|
|
5
|
+
|
|
6
|
+
It includes:
|
|
7
|
+
|
|
8
|
+
- authoring templates in `src/templates/`
|
|
9
|
+
- a `tsconfig.json` that adds `@relevate/katachi/jsx` to `compilerOptions.types`
|
|
10
|
+
- expected Askama partials in `components/`
|
|
11
|
+
|
|
12
|
+
The example templates intentionally cover a useful slice of normal Katachi
|
|
13
|
+
usage:
|
|
14
|
+
|
|
15
|
+
- simple wrappers with `children`
|
|
16
|
+
- imported nested components
|
|
17
|
+
- dynamic `className` arrays
|
|
18
|
+
- `If`
|
|
19
|
+
- nested `For`
|
|
20
|
+
- `safe(...)`
|
|
21
|
+
- mixed HTML and expression attributes
|
|
22
|
+
|
|
23
|
+
## Example components
|
|
24
|
+
|
|
25
|
+
- `badge-chip.template.tsx`
|
|
26
|
+
- `comparison-table.template.tsx`
|
|
27
|
+
- `glyph.template.tsx`
|
|
28
|
+
- `hover-note.template.tsx`
|
|
29
|
+
- `media-frame.template.tsx`
|
|
30
|
+
- `notice-panel.template.tsx`
|
|
31
|
+
- `resource-tile.template.tsx`
|
|
32
|
+
- `stack-shell.template.tsx`
|
|
33
|
+
|
|
34
|
+
## Build the example project
|
|
35
|
+
|
|
36
|
+
From the Katachi repo root:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm exec katachi build --project ./examples/basic
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That writes generated output to:
|
|
43
|
+
|
|
44
|
+
- `examples/basic/dist/react`
|
|
45
|
+
- `examples/basic/dist/jsx-static`
|
|
46
|
+
- `examples/basic/dist/askama`
|
|
47
|
+
|
|
48
|
+
## Verify the public Askama fixtures
|
|
49
|
+
|
|
50
|
+
From the Katachi repo root:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pnpm verify:examples
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
That builds `examples/basic` and compares the generated Askama partials against
|
|
57
|
+
the expected files in `examples/basic/components`.
|
|
58
|
+
|
|
59
|
+
If you are trying to understand how a consumer project should look, start here
|
|
60
|
+
and then read [docs/getting-started.md](../../docs/getting-started.md).
|
|
61
|
+
|
|
62
|
+
The important parts to copy into your own project are:
|
|
63
|
+
|
|
64
|
+
- `src/templates/`
|
|
65
|
+
- `tsconfig.json`
|
|
66
|
+
- the `@relevate/katachi/jsx` type entry
|
|
67
|
+
- your chosen `katachi build` command
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<div class="overflow-x-auto rounded-3xl border border-slate-200 bg-white">
|
|
2
|
+
<table class="min-w-full divide-y divide-slate-200">
|
|
3
|
+
<thead>
|
|
4
|
+
<tr>
|
|
5
|
+
{% for cell in head %}
|
|
6
|
+
<th class="bg-slate-50 px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.2em] text-slate-500">
|
|
7
|
+
{{ cell|safe }}
|
|
8
|
+
</th>
|
|
9
|
+
{% endfor %}
|
|
10
|
+
</tr>
|
|
11
|
+
</thead>
|
|
12
|
+
<tbody>
|
|
13
|
+
{% for row in rows %}
|
|
14
|
+
<tr class="odd:bg-white even:bg-slate-50/60">
|
|
15
|
+
{% for cell in row %}
|
|
16
|
+
<td class="px-4 py-3 text-sm text-slate-700">
|
|
17
|
+
{{ cell|safe }}
|
|
18
|
+
</td>
|
|
19
|
+
{% endfor %}
|
|
20
|
+
</tr>
|
|
21
|
+
{% endfor %}
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<figure class="overflow-hidden rounded-[28px] border border-slate-200 bg-white shadow-sm">
|
|
2
|
+
{% if eyebrow != null %}
|
|
3
|
+
<div class="border-b border-slate-200 px-5 py-3 text-[11px] font-semibold uppercase tracking-[0.28em] text-slate-500">
|
|
4
|
+
{{ eyebrow }}
|
|
5
|
+
</div>
|
|
6
|
+
{% endif %}
|
|
7
|
+
<div class="bg-slate-50 p-4">
|
|
8
|
+
{{ children|safe }}
|
|
9
|
+
</div>
|
|
10
|
+
{% if caption_html != null %}
|
|
11
|
+
<figcaption class="border-t border-slate-200 px-5 py-4 text-sm leading-6 text-slate-600">
|
|
12
|
+
{{ caption_html|safe }}
|
|
13
|
+
</figcaption>
|
|
14
|
+
{% endif %}
|
|
15
|
+
</figure>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<aside
|
|
2
|
+
class="rounded-3xl border px-5 py-4 backdrop-blur-sm {% if tone == "calm" %}border-sky-200 bg-sky-50/80{% endif %} {% if tone == "urgent" %}border-rose-200 bg-rose-50/80{% endif %} {% if tone == "success" %}border-emerald-200 bg-emerald-50/80{% endif %}"
|
|
3
|
+
>
|
|
4
|
+
<div class="flex items-start gap-3">
|
|
5
|
+
{% let className = "mt-0.5 h-5 w-5 shrink-0" %}
|
|
6
|
+
{% let tone = tone %}
|
|
7
|
+
{% let size = "18" %}
|
|
8
|
+
{% let name = icon %}
|
|
9
|
+
{% include "./includes/glyph.html" %}
|
|
10
|
+
<div class="min-w-0 flex-1">
|
|
11
|
+
<h3 class="text-sm font-semibold tracking-tight">
|
|
12
|
+
{{ title }}
|
|
13
|
+
</h3>
|
|
14
|
+
<div class="mt-2 text-sm leading-6 text-slate-700">
|
|
15
|
+
{{ children|safe }}
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
{% if tone == "urgent" %}
|
|
20
|
+
<p class="mt-3 text-xs font-medium uppercase tracking-[0.24em] text-rose-700">
|
|
21
|
+
Action recommended
|
|
22
|
+
</p>
|
|
23
|
+
{% endif %}
|
|
24
|
+
</aside>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<li class="list-none" role="option" tabIndex="{{ -1 }}" aria-selected="{{ selected }}">
|
|
2
|
+
<a href="{{ href }}" class="group block rounded-2xl border border-slate-200 bg-white p-4 transition hover:border-slate-300 hover:shadow-sm">
|
|
3
|
+
<div class="flex items-start gap-3">
|
|
4
|
+
{% let className = "mt-0.5 h-5 w-5 shrink-0 text-slate-500" %}
|
|
5
|
+
{% let tone = "slate" %}
|
|
6
|
+
{% let size = "18" %}
|
|
7
|
+
{% let name = icon %}
|
|
8
|
+
{% include "./includes/glyph.html" %}
|
|
9
|
+
<div class="min-w-0 flex-1">
|
|
10
|
+
{% if eyebrow_html != null %}
|
|
11
|
+
<div class="truncate text-xs font-medium uppercase tracking-[0.2em] text-slate-500">
|
|
12
|
+
{{ eyebrow_html|safe }}
|
|
13
|
+
</div>
|
|
14
|
+
{% endif %}
|
|
15
|
+
<div class="mt-1 truncate text-sm font-semibold text-slate-900">
|
|
16
|
+
{{ title_html|safe }}
|
|
17
|
+
</div>
|
|
18
|
+
<p class="mt-1 text-sm leading-6 text-slate-600">
|
|
19
|
+
{{ summary_html|safe }}
|
|
20
|
+
</p>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</a>
|
|
24
|
+
</li>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
use askama::Template;
|
|
2
|
+
|
|
3
|
+
#[derive(Template)]
|
|
4
|
+
#[template(
|
|
5
|
+
ext = "html",
|
|
6
|
+
source = r#"
|
|
7
|
+
<span
|
|
8
|
+
class='inline-flex items-center rounded-full px-2.5 py-1 text-xs font-semibold tracking-wide {% if tone == "neutral" %}bg-slate-100 text-slate-700{% endif %} {% if tone == "accent" %}bg-amber-100 text-amber-700{% endif %}'
|
|
9
|
+
>
|
|
10
|
+
{{ label }}
|
|
11
|
+
</span>
|
|
12
|
+
"#
|
|
13
|
+
)]
|
|
14
|
+
pub struct BadgeChipTemplate<'a> {
|
|
15
|
+
pub label: &'a str,
|
|
16
|
+
pub tone: &'a str,
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
use askama::Template;
|
|
2
|
+
|
|
3
|
+
#[derive(Template)]
|
|
4
|
+
#[template(
|
|
5
|
+
ext = "html",
|
|
6
|
+
source = r#"
|
|
7
|
+
<div
|
|
8
|
+
class='overflow-x-auto rounded-3xl border border-slate-200 bg-white'
|
|
9
|
+
>
|
|
10
|
+
<table
|
|
11
|
+
class='min-w-full divide-y divide-slate-200'
|
|
12
|
+
>
|
|
13
|
+
<thead>
|
|
14
|
+
<tr>
|
|
15
|
+
{% for cell in head %}
|
|
16
|
+
<th
|
|
17
|
+
class='bg-slate-50 px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.2em] text-slate-500'
|
|
18
|
+
>
|
|
19
|
+
{{ cell|safe }}
|
|
20
|
+
</th>
|
|
21
|
+
{% endfor %}
|
|
22
|
+
</tr>
|
|
23
|
+
</thead>
|
|
24
|
+
<tbody>
|
|
25
|
+
{% for row in rows %}
|
|
26
|
+
<tr
|
|
27
|
+
class='odd:bg-white even:bg-slate-50/60'
|
|
28
|
+
>
|
|
29
|
+
{% for cell in row %}
|
|
30
|
+
<td
|
|
31
|
+
class='px-4 py-3 text-sm text-slate-700'
|
|
32
|
+
>
|
|
33
|
+
{{ cell|safe }}
|
|
34
|
+
</td>
|
|
35
|
+
{% endfor %}
|
|
36
|
+
</tr>
|
|
37
|
+
{% endfor %}
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</div>
|
|
41
|
+
"#
|
|
42
|
+
)]
|
|
43
|
+
pub struct ComparisonTableTemplate<'a> {
|
|
44
|
+
pub head: &'a [&'a str],
|
|
45
|
+
pub rows: &'a [&'a [&'a str]],
|
|
46
|
+
}
|
|
47
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
use askama::Template;
|
|
2
|
+
|
|
3
|
+
#[derive(Template)]
|
|
4
|
+
#[template(
|
|
5
|
+
ext = "html",
|
|
6
|
+
source = r#"
|
|
7
|
+
<svg
|
|
8
|
+
class='{{ className }}'
|
|
9
|
+
data-tone='{{ tone }}'
|
|
10
|
+
data-size='{{ size }}'
|
|
11
|
+
data-name='{{ name }}'
|
|
12
|
+
/>
|
|
13
|
+
"#
|
|
14
|
+
)]
|
|
15
|
+
pub struct GlyphTemplate<'a> {
|
|
16
|
+
pub className: &'a str,
|
|
17
|
+
pub tone: &'a str,
|
|
18
|
+
pub size: &'a str,
|
|
19
|
+
pub name: &'a str,
|
|
20
|
+
}
|
|
21
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
use askama::Template;
|
|
2
|
+
|
|
3
|
+
#[derive(Template)]
|
|
4
|
+
#[template(
|
|
5
|
+
ext = "html",
|
|
6
|
+
source = r#"
|
|
7
|
+
<span
|
|
8
|
+
data-hover-note='{{ label }}'
|
|
9
|
+
class='cursor-help underline decoration-dotted underline-offset-4'
|
|
10
|
+
>
|
|
11
|
+
{{ children|safe }}
|
|
12
|
+
</span>
|
|
13
|
+
"#
|
|
14
|
+
)]
|
|
15
|
+
pub struct HoverNoteTemplate<'a> {
|
|
16
|
+
pub label: &'a str,
|
|
17
|
+
pub children: &'a str,
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<div
|
|
2
|
+
class='overflow-x-auto rounded-3xl border border-slate-200 bg-white'
|
|
3
|
+
>
|
|
4
|
+
<table
|
|
5
|
+
class='min-w-full divide-y divide-slate-200'
|
|
6
|
+
>
|
|
7
|
+
<thead>
|
|
8
|
+
<tr>
|
|
9
|
+
{% for cell in head %}
|
|
10
|
+
<th
|
|
11
|
+
class='bg-slate-50 px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.2em] text-slate-500'
|
|
12
|
+
>
|
|
13
|
+
{{ cell|safe }}
|
|
14
|
+
</th>
|
|
15
|
+
{% endfor %}
|
|
16
|
+
</tr>
|
|
17
|
+
</thead>
|
|
18
|
+
<tbody>
|
|
19
|
+
{% for row in rows %}
|
|
20
|
+
<tr
|
|
21
|
+
class='odd:bg-white even:bg-slate-50/60'
|
|
22
|
+
>
|
|
23
|
+
{% for cell in row %}
|
|
24
|
+
<td
|
|
25
|
+
class='px-4 py-3 text-sm text-slate-700'
|
|
26
|
+
>
|
|
27
|
+
{{ cell|safe }}
|
|
28
|
+
</td>
|
|
29
|
+
{% endfor %}
|
|
30
|
+
</tr>
|
|
31
|
+
{% endfor %}
|
|
32
|
+
</tbody>
|
|
33
|
+
</table>
|
|
34
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<figure
|
|
2
|
+
class='overflow-hidden rounded-[28px] border border-slate-200 bg-white shadow-sm'
|
|
3
|
+
>
|
|
4
|
+
{% if eyebrow != null %}
|
|
5
|
+
<div
|
|
6
|
+
class='border-b border-slate-200 px-5 py-3 text-[11px] font-semibold uppercase tracking-[0.28em] text-slate-500'
|
|
7
|
+
>
|
|
8
|
+
{{ eyebrow }}
|
|
9
|
+
</div>
|
|
10
|
+
{% endif %}
|
|
11
|
+
<div
|
|
12
|
+
class='bg-slate-50 p-4'
|
|
13
|
+
>
|
|
14
|
+
{{ children|safe }}
|
|
15
|
+
</div>
|
|
16
|
+
{% if caption_html != null %}
|
|
17
|
+
<figcaption
|
|
18
|
+
class='border-t border-slate-200 px-5 py-4 text-sm leading-6 text-slate-600'
|
|
19
|
+
>
|
|
20
|
+
{{ caption_html|safe }}
|
|
21
|
+
</figcaption>
|
|
22
|
+
{% endif %}
|
|
23
|
+
</figure>
|