@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.
Files changed (92) hide show
  1. package/CONTRIBUTING.md +60 -0
  2. package/LICENSE +21 -0
  3. package/README.md +194 -0
  4. package/bin/katachi.mjs +30 -0
  5. package/dist/api/index.d.ts +54 -0
  6. package/dist/api/index.js +45 -0
  7. package/dist/api/jsx.d.ts +26 -0
  8. package/dist/cli/index.d.ts +1 -0
  9. package/dist/cli/index.js +77 -0
  10. package/dist/core/ast.d.ts +115 -0
  11. package/dist/core/ast.js +51 -0
  12. package/dist/core/build.d.ts +15 -0
  13. package/dist/core/build.js +107 -0
  14. package/dist/core/compiler.d.ts +9 -0
  15. package/dist/core/compiler.js +9 -0
  16. package/dist/core/example-fixtures.d.ts +5 -0
  17. package/dist/core/example-fixtures.js +54 -0
  18. package/dist/core/parser.d.ts +5 -0
  19. package/dist/core/parser.js +637 -0
  20. package/dist/core/types.d.ts +65 -0
  21. package/dist/core/types.js +1 -0
  22. package/dist/core/verify.d.ts +25 -0
  23. package/dist/core/verify.js +270 -0
  24. package/dist/index.d.ts +7 -0
  25. package/dist/index.js +5 -0
  26. package/dist/targets/askama.d.ts +11 -0
  27. package/dist/targets/askama.js +122 -0
  28. package/dist/targets/index.d.ts +5 -0
  29. package/dist/targets/index.js +60 -0
  30. package/dist/targets/react.d.ts +4 -0
  31. package/dist/targets/react.js +28 -0
  32. package/dist/targets/shared.d.ts +36 -0
  33. package/dist/targets/shared.js +278 -0
  34. package/dist/targets/static-jsx.d.ts +4 -0
  35. package/dist/targets/static-jsx.js +28 -0
  36. package/dist/verify-examples.d.ts +1 -0
  37. package/dist/verify-examples.js +14 -0
  38. package/docs/architecture.md +122 -0
  39. package/docs/getting-started.md +154 -0
  40. package/docs/syntax.md +236 -0
  41. package/docs/targets.md +53 -0
  42. package/examples/basic/README.md +67 -0
  43. package/examples/basic/components/badge-chip.html +3 -0
  44. package/examples/basic/components/comparison-table.html +24 -0
  45. package/examples/basic/components/glyph.html +6 -0
  46. package/examples/basic/components/hover-note.html +6 -0
  47. package/examples/basic/components/media-frame.html +15 -0
  48. package/examples/basic/components/notice-panel.html +24 -0
  49. package/examples/basic/components/resource-tile.html +24 -0
  50. package/examples/basic/components/stack-shell.html +3 -0
  51. package/examples/basic/dist/askama/badge-chip.rs +18 -0
  52. package/examples/basic/dist/askama/comparison-table.rs +47 -0
  53. package/examples/basic/dist/askama/glyph.rs +21 -0
  54. package/examples/basic/dist/askama/hover-note.rs +19 -0
  55. package/examples/basic/dist/askama/includes/badge-chip.html +5 -0
  56. package/examples/basic/dist/askama/includes/comparison-table.html +34 -0
  57. package/examples/basic/dist/askama/includes/glyph.html +6 -0
  58. package/examples/basic/dist/askama/includes/hover-note.html +6 -0
  59. package/examples/basic/dist/askama/includes/media-frame.html +23 -0
  60. package/examples/basic/dist/askama/includes/notice-panel.html +34 -0
  61. package/examples/basic/dist/askama/includes/resource-tile.html +42 -0
  62. package/examples/basic/dist/askama/includes/stack-shell.html +5 -0
  63. package/examples/basic/dist/askama/media-frame.rs +37 -0
  64. package/examples/basic/dist/askama/notice-panel.rs +49 -0
  65. package/examples/basic/dist/askama/resource-tile.rs +59 -0
  66. package/examples/basic/dist/askama/stack-shell.rs +17 -0
  67. package/examples/basic/dist/jsx-static/badge-chip.tsx +18 -0
  68. package/examples/basic/dist/jsx-static/comparison-table.tsx +53 -0
  69. package/examples/basic/dist/jsx-static/glyph.tsx +21 -0
  70. package/examples/basic/dist/jsx-static/hover-note.tsx +19 -0
  71. package/examples/basic/dist/jsx-static/media-frame.tsx +41 -0
  72. package/examples/basic/dist/jsx-static/notice-panel.tsx +53 -0
  73. package/examples/basic/dist/jsx-static/resource-tile.tsx +63 -0
  74. package/examples/basic/dist/jsx-static/stack-shell.tsx +17 -0
  75. package/examples/basic/dist/react/badge-chip.tsx +18 -0
  76. package/examples/basic/dist/react/comparison-table.tsx +53 -0
  77. package/examples/basic/dist/react/glyph.tsx +21 -0
  78. package/examples/basic/dist/react/hover-note.tsx +19 -0
  79. package/examples/basic/dist/react/media-frame.tsx +41 -0
  80. package/examples/basic/dist/react/notice-panel.tsx +53 -0
  81. package/examples/basic/dist/react/resource-tile.tsx +63 -0
  82. package/examples/basic/dist/react/stack-shell.tsx +17 -0
  83. package/examples/basic/src/templates/badge-chip.template.tsx +18 -0
  84. package/examples/basic/src/templates/comparison-table.template.tsx +35 -0
  85. package/examples/basic/src/templates/glyph.template.tsx +17 -0
  86. package/examples/basic/src/templates/hover-note.template.tsx +17 -0
  87. package/examples/basic/src/templates/media-frame.template.tsx +25 -0
  88. package/examples/basic/src/templates/notice-panel.template.tsx +40 -0
  89. package/examples/basic/src/templates/resource-tile.template.tsx +51 -0
  90. package/examples/basic/src/templates/stack-shell.template.tsx +13 -0
  91. package/examples/basic/tsconfig.json +10 -0
  92. 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
@@ -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,3 @@
1
+ <span 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 %}">
2
+ {{ label }}
3
+ </span>
@@ -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,6 @@
1
+ <svg
2
+ class="{{ className }}"
3
+ data-tone="{{ tone }}"
4
+ data-size="{{ size }}"
5
+ data-name="{{ name }}"
6
+ />
@@ -0,0 +1,6 @@
1
+ <span
2
+ data-hover-note="{{ label }}"
3
+ class="cursor-help underline decoration-dotted underline-offset-4"
4
+ >
5
+ {{ children|safe }}
6
+ </span>
@@ -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,3 @@
1
+ <section class="mx-auto max-w-4xl space-y-6 rounded-[28px] border border-slate-200 bg-white/90 p-6 shadow-[0_24px_80px_-40px_rgba(15,23,42,0.45)]">
2
+ {{ children|safe }}
3
+ </section>
@@ -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,5 @@
1
+ <span
2
+ 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 %}'
3
+ >
4
+ {{ label }}
5
+ </span>
@@ -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,6 @@
1
+ <svg
2
+ class='{{ className }}'
3
+ data-tone='{{ tone }}'
4
+ data-size='{{ size }}'
5
+ data-name='{{ name }}'
6
+ />
@@ -0,0 +1,6 @@
1
+ <span
2
+ data-hover-note='{{ label }}'
3
+ class='cursor-help underline decoration-dotted underline-offset-4'
4
+ >
5
+ {{ children|safe }}
6
+ </span>
@@ -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>