@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
|
@@ -0,0 +1,34 @@
|
|
|
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
|
|
5
|
+
class='flex items-start gap-3'
|
|
6
|
+
>
|
|
7
|
+
{% let className = "mt-0.5 h-5 w-5 shrink-0" %}
|
|
8
|
+
{% let tone = tone %}
|
|
9
|
+
{% let size = "18" %}
|
|
10
|
+
{% let name = icon %}
|
|
11
|
+
{% include "./includes/glyph.html" %}
|
|
12
|
+
<div
|
|
13
|
+
class='min-w-0 flex-1'
|
|
14
|
+
>
|
|
15
|
+
<h3
|
|
16
|
+
class='text-sm font-semibold tracking-tight'
|
|
17
|
+
>
|
|
18
|
+
{{ title }}
|
|
19
|
+
</h3>
|
|
20
|
+
<div
|
|
21
|
+
class='mt-2 text-sm leading-6 text-slate-700'
|
|
22
|
+
>
|
|
23
|
+
{{ children|safe }}
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
{% if tone == "urgent" %}
|
|
28
|
+
<p
|
|
29
|
+
class='mt-3 text-xs font-medium uppercase tracking-[0.24em] text-rose-700'
|
|
30
|
+
>
|
|
31
|
+
Action recommended
|
|
32
|
+
</p>
|
|
33
|
+
{% endif %}
|
|
34
|
+
</aside>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<li
|
|
2
|
+
class='list-none'
|
|
3
|
+
role='option'
|
|
4
|
+
tabIndex='{{ -1 }}'
|
|
5
|
+
aria-selected='{{ selected }}'
|
|
6
|
+
>
|
|
7
|
+
<a
|
|
8
|
+
href='{{ href }}'
|
|
9
|
+
class='group block rounded-2xl border border-slate-200 bg-white p-4 transition hover:border-slate-300 hover:shadow-sm'
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
class='flex items-start gap-3'
|
|
13
|
+
>
|
|
14
|
+
{% let className = "mt-0.5 h-5 w-5 shrink-0 text-slate-500" %}
|
|
15
|
+
{% let tone = "slate" %}
|
|
16
|
+
{% let size = "18" %}
|
|
17
|
+
{% let name = icon %}
|
|
18
|
+
{% include "./includes/glyph.html" %}
|
|
19
|
+
<div
|
|
20
|
+
class='min-w-0 flex-1'
|
|
21
|
+
>
|
|
22
|
+
{% if eyebrow_html != null %}
|
|
23
|
+
<div
|
|
24
|
+
class='truncate text-xs font-medium uppercase tracking-[0.2em] text-slate-500'
|
|
25
|
+
>
|
|
26
|
+
{{ eyebrow_html|safe }}
|
|
27
|
+
</div>
|
|
28
|
+
{% endif %}
|
|
29
|
+
<div
|
|
30
|
+
class='mt-1 truncate text-sm font-semibold text-slate-900'
|
|
31
|
+
>
|
|
32
|
+
{{ title_html|safe }}
|
|
33
|
+
</div>
|
|
34
|
+
<p
|
|
35
|
+
class='mt-1 text-sm leading-6 text-slate-600'
|
|
36
|
+
>
|
|
37
|
+
{{ summary_html|safe }}
|
|
38
|
+
</p>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</a>
|
|
42
|
+
</li>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
use askama::Template;
|
|
2
|
+
|
|
3
|
+
#[derive(Template)]
|
|
4
|
+
#[template(
|
|
5
|
+
ext = "html",
|
|
6
|
+
source = r#"
|
|
7
|
+
<figure
|
|
8
|
+
class='overflow-hidden rounded-[28px] border border-slate-200 bg-white shadow-sm'
|
|
9
|
+
>
|
|
10
|
+
{% if eyebrow != null %}
|
|
11
|
+
<div
|
|
12
|
+
class='border-b border-slate-200 px-5 py-3 text-[11px] font-semibold uppercase tracking-[0.28em] text-slate-500'
|
|
13
|
+
>
|
|
14
|
+
{{ eyebrow }}
|
|
15
|
+
</div>
|
|
16
|
+
{% endif %}
|
|
17
|
+
<div
|
|
18
|
+
class='bg-slate-50 p-4'
|
|
19
|
+
>
|
|
20
|
+
{{ children|safe }}
|
|
21
|
+
</div>
|
|
22
|
+
{% if caption_html != null %}
|
|
23
|
+
<figcaption
|
|
24
|
+
class='border-t border-slate-200 px-5 py-4 text-sm leading-6 text-slate-600'
|
|
25
|
+
>
|
|
26
|
+
{{ caption_html|safe }}
|
|
27
|
+
</figcaption>
|
|
28
|
+
{% endif %}
|
|
29
|
+
</figure>
|
|
30
|
+
"#
|
|
31
|
+
)]
|
|
32
|
+
pub struct MediaFrameTemplate<'a> {
|
|
33
|
+
pub eyebrow: &'a str,
|
|
34
|
+
pub captionHtml: &'a str,
|
|
35
|
+
pub children: &'a str,
|
|
36
|
+
}
|
|
37
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
use askama::Template;
|
|
2
|
+
|
|
3
|
+
#[derive(Template)]
|
|
4
|
+
#[template(
|
|
5
|
+
ext = "html",
|
|
6
|
+
source = r#"
|
|
7
|
+
<aside
|
|
8
|
+
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 %}'
|
|
9
|
+
>
|
|
10
|
+
<div
|
|
11
|
+
class='flex items-start gap-3'
|
|
12
|
+
>
|
|
13
|
+
{% let className = "mt-0.5 h-5 w-5 shrink-0" %}
|
|
14
|
+
{% let tone = tone %}
|
|
15
|
+
{% let size = "18" %}
|
|
16
|
+
{% let name = icon %}
|
|
17
|
+
{% include "./includes/glyph.html" %}
|
|
18
|
+
<div
|
|
19
|
+
class='min-w-0 flex-1'
|
|
20
|
+
>
|
|
21
|
+
<h3
|
|
22
|
+
class='text-sm font-semibold tracking-tight'
|
|
23
|
+
>
|
|
24
|
+
{{ title }}
|
|
25
|
+
</h3>
|
|
26
|
+
<div
|
|
27
|
+
class='mt-2 text-sm leading-6 text-slate-700'
|
|
28
|
+
>
|
|
29
|
+
{{ children|safe }}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
{% if tone == "urgent" %}
|
|
34
|
+
<p
|
|
35
|
+
class='mt-3 text-xs font-medium uppercase tracking-[0.24em] text-rose-700'
|
|
36
|
+
>
|
|
37
|
+
Action recommended
|
|
38
|
+
</p>
|
|
39
|
+
{% endif %}
|
|
40
|
+
</aside>
|
|
41
|
+
"#
|
|
42
|
+
)]
|
|
43
|
+
pub struct NoticePanelTemplate<'a> {
|
|
44
|
+
pub tone: &'a str,
|
|
45
|
+
pub title: &'a str,
|
|
46
|
+
pub icon: &'a str,
|
|
47
|
+
pub children: &'a str,
|
|
48
|
+
}
|
|
49
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
use askama::Template;
|
|
2
|
+
|
|
3
|
+
#[derive(Template)]
|
|
4
|
+
#[template(
|
|
5
|
+
ext = "html",
|
|
6
|
+
source = r#"
|
|
7
|
+
<li
|
|
8
|
+
class='list-none'
|
|
9
|
+
role='option'
|
|
10
|
+
tabIndex='{{ -1 }}'
|
|
11
|
+
aria-selected='{{ selected }}'
|
|
12
|
+
>
|
|
13
|
+
<a
|
|
14
|
+
href='{{ href }}'
|
|
15
|
+
class='group block rounded-2xl border border-slate-200 bg-white p-4 transition hover:border-slate-300 hover:shadow-sm'
|
|
16
|
+
>
|
|
17
|
+
<div
|
|
18
|
+
class='flex items-start gap-3'
|
|
19
|
+
>
|
|
20
|
+
{% let className = "mt-0.5 h-5 w-5 shrink-0 text-slate-500" %}
|
|
21
|
+
{% let tone = "slate" %}
|
|
22
|
+
{% let size = "18" %}
|
|
23
|
+
{% let name = icon %}
|
|
24
|
+
{% include "./includes/glyph.html" %}
|
|
25
|
+
<div
|
|
26
|
+
class='min-w-0 flex-1'
|
|
27
|
+
>
|
|
28
|
+
{% if eyebrow_html != null %}
|
|
29
|
+
<div
|
|
30
|
+
class='truncate text-xs font-medium uppercase tracking-[0.2em] text-slate-500'
|
|
31
|
+
>
|
|
32
|
+
{{ eyebrow_html|safe }}
|
|
33
|
+
</div>
|
|
34
|
+
{% endif %}
|
|
35
|
+
<div
|
|
36
|
+
class='mt-1 truncate text-sm font-semibold text-slate-900'
|
|
37
|
+
>
|
|
38
|
+
{{ title_html|safe }}
|
|
39
|
+
</div>
|
|
40
|
+
<p
|
|
41
|
+
class='mt-1 text-sm leading-6 text-slate-600'
|
|
42
|
+
>
|
|
43
|
+
{{ summary_html|safe }}
|
|
44
|
+
</p>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</a>
|
|
48
|
+
</li>
|
|
49
|
+
"#
|
|
50
|
+
)]
|
|
51
|
+
pub struct ResourceTileTemplate<'a> {
|
|
52
|
+
pub href: &'a str,
|
|
53
|
+
pub titleHtml: &'a str,
|
|
54
|
+
pub summaryHtml: &'a str,
|
|
55
|
+
pub eyebrowHtml: &'a str,
|
|
56
|
+
pub icon: &'a str,
|
|
57
|
+
pub selected: bool,
|
|
58
|
+
}
|
|
59
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
use askama::Template;
|
|
2
|
+
|
|
3
|
+
#[derive(Template)]
|
|
4
|
+
#[template(
|
|
5
|
+
ext = "html",
|
|
6
|
+
source = r#"
|
|
7
|
+
<section
|
|
8
|
+
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)]'
|
|
9
|
+
>
|
|
10
|
+
{{ children|safe }}
|
|
11
|
+
</section>
|
|
12
|
+
"#
|
|
13
|
+
)]
|
|
14
|
+
pub struct StackShellTemplate<'a> {
|
|
15
|
+
pub children: &'a str,
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type BadgeChipProps = {
|
|
5
|
+
label: string;
|
|
6
|
+
tone: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function BadgeChip({ label, tone }: BadgeChipProps) {
|
|
10
|
+
return (
|
|
11
|
+
<span
|
|
12
|
+
className={`inline-flex items-center rounded-full px-2.5 py-1 text-xs font-semibold tracking-wide ${(tone === "neutral") ? "bg-slate-100 text-slate-700" : ""} ${(tone === "accent") ? "bg-amber-100 text-amber-700" : ""}`}
|
|
13
|
+
>
|
|
14
|
+
{label}
|
|
15
|
+
</span>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type ComparisonTableProps = {
|
|
5
|
+
head: string[];
|
|
6
|
+
rows: string[][];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function ComparisonTable({ head, rows }: ComparisonTableProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
className="overflow-x-auto rounded-3xl border border-slate-200 bg-white"
|
|
13
|
+
>
|
|
14
|
+
<table
|
|
15
|
+
className="min-w-full divide-y divide-slate-200"
|
|
16
|
+
>
|
|
17
|
+
<thead>
|
|
18
|
+
<tr>
|
|
19
|
+
{(head ?? []).map((cell, __index) => (
|
|
20
|
+
<>
|
|
21
|
+
<th
|
|
22
|
+
className="bg-slate-50 px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.2em] text-slate-500"
|
|
23
|
+
>
|
|
24
|
+
{cell}
|
|
25
|
+
</th>
|
|
26
|
+
</>
|
|
27
|
+
))}
|
|
28
|
+
</tr>
|
|
29
|
+
</thead>
|
|
30
|
+
<tbody>
|
|
31
|
+
{(rows ?? []).map((row, __index) => (
|
|
32
|
+
<>
|
|
33
|
+
<tr
|
|
34
|
+
className="odd:bg-white even:bg-slate-50/60"
|
|
35
|
+
>
|
|
36
|
+
{(row ?? []).map((cell, __index) => (
|
|
37
|
+
<>
|
|
38
|
+
<td
|
|
39
|
+
className="px-4 py-3 text-sm text-slate-700"
|
|
40
|
+
>
|
|
41
|
+
{cell}
|
|
42
|
+
</td>
|
|
43
|
+
</>
|
|
44
|
+
))}
|
|
45
|
+
</tr>
|
|
46
|
+
</>
|
|
47
|
+
))}
|
|
48
|
+
</tbody>
|
|
49
|
+
</table>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type GlyphProps = {
|
|
5
|
+
className: string;
|
|
6
|
+
tone: string;
|
|
7
|
+
size: string;
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default function Glyph({ className, tone, size, name }: GlyphProps) {
|
|
12
|
+
return (
|
|
13
|
+
<svg
|
|
14
|
+
className={className}
|
|
15
|
+
data-tone={tone}
|
|
16
|
+
data-size={size}
|
|
17
|
+
data-name={name}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type HoverNoteProps = {
|
|
5
|
+
label: string;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function HoverNote({ label, children }: HoverNoteProps) {
|
|
10
|
+
return (
|
|
11
|
+
<span
|
|
12
|
+
data-hover-note={label}
|
|
13
|
+
className="cursor-help underline decoration-dotted underline-offset-4"
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</span>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type MediaFrameProps = {
|
|
5
|
+
eyebrow?: string;
|
|
6
|
+
caption_html?: string;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function MediaFrame({ eyebrow, caption_html, children }: MediaFrameProps) {
|
|
11
|
+
return (
|
|
12
|
+
<figure
|
|
13
|
+
className="overflow-hidden rounded-[28px] border border-slate-200 bg-white shadow-sm"
|
|
14
|
+
>
|
|
15
|
+
{(eyebrow !== null) && (
|
|
16
|
+
<>
|
|
17
|
+
<div
|
|
18
|
+
className="border-b border-slate-200 px-5 py-3 text-[11px] font-semibold uppercase tracking-[0.28em] text-slate-500"
|
|
19
|
+
>
|
|
20
|
+
{eyebrow}
|
|
21
|
+
</div>
|
|
22
|
+
</>
|
|
23
|
+
)}
|
|
24
|
+
<div
|
|
25
|
+
className="bg-slate-50 p-4"
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
</div>
|
|
29
|
+
{(caption_html !== null) && (
|
|
30
|
+
<>
|
|
31
|
+
<figcaption
|
|
32
|
+
className="border-t border-slate-200 px-5 py-4 text-sm leading-6 text-slate-600"
|
|
33
|
+
>
|
|
34
|
+
{caption_html}
|
|
35
|
+
</figcaption>
|
|
36
|
+
</>
|
|
37
|
+
)}
|
|
38
|
+
</figure>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import Glyph from "./glyph";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export type NoticePanelProps = {
|
|
6
|
+
tone: string;
|
|
7
|
+
title: string;
|
|
8
|
+
icon: string;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default function NoticePanel({ tone, title, icon, children }: NoticePanelProps) {
|
|
13
|
+
return (
|
|
14
|
+
<aside
|
|
15
|
+
className={`rounded-3xl border px-5 py-4 backdrop-blur-sm ${(tone === "calm") ? "border-sky-200 bg-sky-50/80" : ""} ${(tone === "urgent") ? "border-rose-200 bg-rose-50/80" : ""} ${(tone === "success") ? "border-emerald-200 bg-emerald-50/80" : ""}`}
|
|
16
|
+
>
|
|
17
|
+
<div
|
|
18
|
+
className="flex items-start gap-3"
|
|
19
|
+
>
|
|
20
|
+
<Glyph
|
|
21
|
+
className="mt-0.5 h-5 w-5 shrink-0"
|
|
22
|
+
tone={tone}
|
|
23
|
+
size="18"
|
|
24
|
+
name={icon}
|
|
25
|
+
/>
|
|
26
|
+
<div
|
|
27
|
+
className="min-w-0 flex-1"
|
|
28
|
+
>
|
|
29
|
+
<h3
|
|
30
|
+
className="text-sm font-semibold tracking-tight"
|
|
31
|
+
>
|
|
32
|
+
{title}
|
|
33
|
+
</h3>
|
|
34
|
+
<div
|
|
35
|
+
className="mt-2 text-sm leading-6 text-slate-700"
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
{(tone === "urgent") && (
|
|
42
|
+
<>
|
|
43
|
+
<p
|
|
44
|
+
className="mt-3 text-xs font-medium uppercase tracking-[0.24em] text-rose-700"
|
|
45
|
+
>
|
|
46
|
+
Action recommended
|
|
47
|
+
</p>
|
|
48
|
+
</>
|
|
49
|
+
)}
|
|
50
|
+
</aside>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import Glyph from "./glyph";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export type ResourceTileProps = {
|
|
6
|
+
href: string;
|
|
7
|
+
title_html: string;
|
|
8
|
+
summary_html: string;
|
|
9
|
+
eyebrow_html?: string;
|
|
10
|
+
icon: string;
|
|
11
|
+
selected: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default function ResourceTile({ href, title_html, summary_html, eyebrow_html, icon, selected }: ResourceTileProps) {
|
|
15
|
+
return (
|
|
16
|
+
<li
|
|
17
|
+
className="list-none"
|
|
18
|
+
role="option"
|
|
19
|
+
tabIndex={-1}
|
|
20
|
+
aria-selected={selected}
|
|
21
|
+
>
|
|
22
|
+
<a
|
|
23
|
+
href={href}
|
|
24
|
+
className="group block rounded-2xl border border-slate-200 bg-white p-4 transition hover:border-slate-300 hover:shadow-sm"
|
|
25
|
+
>
|
|
26
|
+
<div
|
|
27
|
+
className="flex items-start gap-3"
|
|
28
|
+
>
|
|
29
|
+
<Glyph
|
|
30
|
+
className="mt-0.5 h-5 w-5 shrink-0 text-slate-500"
|
|
31
|
+
tone="slate"
|
|
32
|
+
size="18"
|
|
33
|
+
name={icon}
|
|
34
|
+
/>
|
|
35
|
+
<div
|
|
36
|
+
className="min-w-0 flex-1"
|
|
37
|
+
>
|
|
38
|
+
{(eyebrow_html !== null) && (
|
|
39
|
+
<>
|
|
40
|
+
<div
|
|
41
|
+
className="truncate text-xs font-medium uppercase tracking-[0.2em] text-slate-500"
|
|
42
|
+
>
|
|
43
|
+
{eyebrow_html}
|
|
44
|
+
</div>
|
|
45
|
+
</>
|
|
46
|
+
)}
|
|
47
|
+
<div
|
|
48
|
+
className="mt-1 truncate text-sm font-semibold text-slate-900"
|
|
49
|
+
>
|
|
50
|
+
{title_html}
|
|
51
|
+
</div>
|
|
52
|
+
<p
|
|
53
|
+
className="mt-1 text-sm leading-6 text-slate-600"
|
|
54
|
+
>
|
|
55
|
+
{summary_html}
|
|
56
|
+
</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</a>
|
|
60
|
+
</li>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type StackShellProps = {
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default function StackShell({ children }: StackShellProps) {
|
|
9
|
+
return (
|
|
10
|
+
<section
|
|
11
|
+
className="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)]"
|
|
12
|
+
>
|
|
13
|
+
{children}
|
|
14
|
+
</section>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type BadgeChipProps = {
|
|
5
|
+
label: string;
|
|
6
|
+
tone: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function BadgeChip({ label, tone }: BadgeChipProps) {
|
|
10
|
+
return (
|
|
11
|
+
<span
|
|
12
|
+
className={["inline-flex items-center rounded-full px-2.5 py-1 text-xs font-semibold tracking-wide", (tone === "neutral") ? "bg-slate-100 text-slate-700" : null, (tone === "accent") ? "bg-amber-100 text-amber-700" : null].filter(Boolean).join(" ")}
|
|
13
|
+
>
|
|
14
|
+
{label}
|
|
15
|
+
</span>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type ComparisonTableProps = {
|
|
5
|
+
head: string[];
|
|
6
|
+
rows: string[][];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function ComparisonTable({ head, rows }: ComparisonTableProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
className="overflow-x-auto rounded-3xl border border-slate-200 bg-white"
|
|
13
|
+
>
|
|
14
|
+
<table
|
|
15
|
+
className="min-w-full divide-y divide-slate-200"
|
|
16
|
+
>
|
|
17
|
+
<thead>
|
|
18
|
+
<tr>
|
|
19
|
+
{(head ?? []).map((cell, __index) => (
|
|
20
|
+
<>
|
|
21
|
+
<th
|
|
22
|
+
className="bg-slate-50 px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.2em] text-slate-500"
|
|
23
|
+
>
|
|
24
|
+
{cell}
|
|
25
|
+
</th>
|
|
26
|
+
</>
|
|
27
|
+
))}
|
|
28
|
+
</tr>
|
|
29
|
+
</thead>
|
|
30
|
+
<tbody>
|
|
31
|
+
{(rows ?? []).map((row, __index) => (
|
|
32
|
+
<>
|
|
33
|
+
<tr
|
|
34
|
+
className="odd:bg-white even:bg-slate-50/60"
|
|
35
|
+
>
|
|
36
|
+
{(row ?? []).map((cell, __index) => (
|
|
37
|
+
<>
|
|
38
|
+
<td
|
|
39
|
+
className="px-4 py-3 text-sm text-slate-700"
|
|
40
|
+
>
|
|
41
|
+
{cell}
|
|
42
|
+
</td>
|
|
43
|
+
</>
|
|
44
|
+
))}
|
|
45
|
+
</tr>
|
|
46
|
+
</>
|
|
47
|
+
))}
|
|
48
|
+
</tbody>
|
|
49
|
+
</table>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type GlyphProps = {
|
|
5
|
+
className: string;
|
|
6
|
+
tone: string;
|
|
7
|
+
size: string;
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default function Glyph({ className, tone, size, name }: GlyphProps) {
|
|
12
|
+
return (
|
|
13
|
+
<svg
|
|
14
|
+
className={className}
|
|
15
|
+
data-tone={tone}
|
|
16
|
+
data-size={size}
|
|
17
|
+
data-name={name}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export type HoverNoteProps = {
|
|
5
|
+
label: string;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function HoverNote({ label, children }: HoverNoteProps) {
|
|
10
|
+
return (
|
|
11
|
+
<span
|
|
12
|
+
data-hover-note={label}
|
|
13
|
+
className="cursor-help underline decoration-dotted underline-offset-4"
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
</span>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|