@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
@@ -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" : null, (tone === "urgent") ? "border-rose-200 bg-rose-50/80" : null, (tone === "success") ? "border-emerald-200 bg-emerald-50/80" : null].filter(Boolean).join(" ")}
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
+ export type Props = {
2
+ label: string;
3
+ tone: "neutral" | "accent";
4
+ };
5
+
6
+ export default function BadgeChip({ label, tone }: Props) {
7
+ return (
8
+ <span
9
+ className={[
10
+ "inline-flex items-center rounded-full px-2.5 py-1 text-xs font-semibold tracking-wide",
11
+ tone == "neutral" && "bg-slate-100 text-slate-700",
12
+ tone == "accent" && "bg-amber-100 text-amber-700",
13
+ ]}
14
+ >
15
+ {label}
16
+ </span>
17
+ );
18
+ }
@@ -0,0 +1,35 @@
1
+ import { For, safe } from "@relevate/katachi";
2
+
3
+ export type Props = {
4
+ head: string[];
5
+ rows: string[][];
6
+ };
7
+
8
+ export default function ComparisonTable({ head, rows }: Props) {
9
+ return (
10
+ <div className="overflow-x-auto rounded-3xl border border-slate-200 bg-white">
11
+ <table className="min-w-full divide-y divide-slate-200">
12
+ <thead>
13
+ <tr>
14
+ <For each={head} as="cell">
15
+ <th className="bg-slate-50 px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.2em] text-slate-500">
16
+ {safe(cell)}
17
+ </th>
18
+ </For>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ <For each={rows} as="row">
23
+ <tr className="odd:bg-white even:bg-slate-50/60">
24
+ <For each={row} as="cell">
25
+ <td className="px-4 py-3 text-sm text-slate-700">
26
+ {safe(cell)}
27
+ </td>
28
+ </For>
29
+ </tr>
30
+ </For>
31
+ </tbody>
32
+ </table>
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,17 @@
1
+ export type Props = {
2
+ className: string;
3
+ tone: string;
4
+ size: string;
5
+ name: string;
6
+ };
7
+
8
+ export default function Glyph({ className, tone, size, name }: Props) {
9
+ return (
10
+ <svg
11
+ className={className}
12
+ data-tone={tone}
13
+ data-size={size}
14
+ data-name={name}
15
+ />
16
+ );
17
+ }
@@ -0,0 +1,17 @@
1
+ import type { TemplateNode } from "@relevate/katachi";
2
+
3
+ export type Props = {
4
+ label: string;
5
+ children?: TemplateNode;
6
+ };
7
+
8
+ export default function HoverNote({ label, children }: Props) {
9
+ return (
10
+ <span
11
+ data-hover-note={label}
12
+ className="cursor-help underline decoration-dotted underline-offset-4"
13
+ >
14
+ {children}
15
+ </span>
16
+ );
17
+ }
@@ -0,0 +1,25 @@
1
+ import { If, safe, type TemplateNode } from "@relevate/katachi";
2
+
3
+ export type Props = {
4
+ eyebrow?: string;
5
+ caption_html?: string;
6
+ children?: TemplateNode;
7
+ };
8
+
9
+ export default function MediaFrame({ eyebrow, caption_html, children }: Props) {
10
+ return (
11
+ <figure className="overflow-hidden rounded-[28px] border border-slate-200 bg-white shadow-sm">
12
+ <If test={eyebrow != null}>
13
+ <div className="border-b border-slate-200 px-5 py-3 text-[11px] font-semibold uppercase tracking-[0.28em] text-slate-500">
14
+ {eyebrow}
15
+ </div>
16
+ </If>
17
+ <div className="bg-slate-50 p-4">{children}</div>
18
+ <If test={caption_html != null}>
19
+ <figcaption className="border-t border-slate-200 px-5 py-4 text-sm leading-6 text-slate-600">
20
+ {safe(caption_html)}
21
+ </figcaption>
22
+ </If>
23
+ </figure>
24
+ );
25
+ }
@@ -0,0 +1,40 @@
1
+ import { If, type TemplateNode } from "@relevate/katachi";
2
+ import Glyph from "./glyph.template";
3
+
4
+ export type Props = {
5
+ tone: "calm" | "urgent" | "success";
6
+ title: string;
7
+ icon: string;
8
+ children?: TemplateNode;
9
+ };
10
+
11
+ export default function NoticePanel({ tone, title, icon, children }: Props) {
12
+ return (
13
+ <aside
14
+ className={[
15
+ "rounded-3xl border px-5 py-4 backdrop-blur-sm",
16
+ tone == "calm" && "border-sky-200 bg-sky-50/80",
17
+ tone == "urgent" && "border-rose-200 bg-rose-50/80",
18
+ tone == "success" && "border-emerald-200 bg-emerald-50/80",
19
+ ]}
20
+ >
21
+ <div className="flex items-start gap-3">
22
+ <Glyph
23
+ className="mt-0.5 h-5 w-5 shrink-0"
24
+ tone={tone}
25
+ size="18"
26
+ name={icon}
27
+ />
28
+ <div className="min-w-0 flex-1">
29
+ <h3 className="text-sm font-semibold tracking-tight">{title}</h3>
30
+ <div className="mt-2 text-sm leading-6 text-slate-700">{children}</div>
31
+ </div>
32
+ </div>
33
+ <If test={tone == "urgent"}>
34
+ <p className="mt-3 text-xs font-medium uppercase tracking-[0.24em] text-rose-700">
35
+ Action recommended
36
+ </p>
37
+ </If>
38
+ </aside>
39
+ );
40
+ }
@@ -0,0 +1,51 @@
1
+ import { If, safe } from "@relevate/katachi";
2
+ import Glyph from "./glyph.template";
3
+
4
+ export type Props = {
5
+ href: string;
6
+ title_html: string;
7
+ summary_html: string;
8
+ eyebrow_html?: string;
9
+ icon: string;
10
+ selected: boolean;
11
+ };
12
+
13
+ export default function ResourceTile({
14
+ href,
15
+ title_html,
16
+ summary_html,
17
+ eyebrow_html,
18
+ icon,
19
+ selected,
20
+ }: Props) {
21
+ return (
22
+ <li className="list-none" role="option" tabIndex={-1} aria-selected={selected}>
23
+ <a
24
+ href={href}
25
+ className="group block rounded-2xl border border-slate-200 bg-white p-4 transition hover:border-slate-300 hover:shadow-sm"
26
+ >
27
+ <div className="flex items-start gap-3">
28
+ <Glyph
29
+ className="mt-0.5 h-5 w-5 shrink-0 text-slate-500"
30
+ tone="slate"
31
+ size="18"
32
+ name={icon}
33
+ />
34
+ <div className="min-w-0 flex-1">
35
+ <If test={eyebrow_html != null}>
36
+ <div className="truncate text-xs font-medium uppercase tracking-[0.2em] text-slate-500">
37
+ {safe(eyebrow_html)}
38
+ </div>
39
+ </If>
40
+ <div className="mt-1 truncate text-sm font-semibold text-slate-900">
41
+ {safe(title_html)}
42
+ </div>
43
+ <p className="mt-1 text-sm leading-6 text-slate-600">
44
+ {safe(summary_html)}
45
+ </p>
46
+ </div>
47
+ </div>
48
+ </a>
49
+ </li>
50
+ );
51
+ }
@@ -0,0 +1,13 @@
1
+ import type { TemplateNode } from "@relevate/katachi";
2
+
3
+ export type Props = {
4
+ children?: TemplateNode;
5
+ };
6
+
7
+ export default function StackShell({ children }: Props) {
8
+ return (
9
+ <section 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)]">
10
+ {children}
11
+ </section>
12
+ );
13
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "preserve",
4
+ "strict": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "types": ["node", "@relevate/katachi/jsx"]
8
+ },
9
+ "include": ["src"]
10
+ }
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@relevate/katachi",
3
+ "version": "0.1.0",
4
+ "description": "Author template-like components once in restricted TSX and compile them to Askama, React, and other targets.",
5
+ "license": "MIT",
6
+ "author": "Relevate",
7
+ "type": "module",
8
+ "packageManager": "pnpm@9.1.2",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/rlvtapp/katachi.git"
12
+ },
13
+ "homepage": "https://github.com/rlvtapp/katachi#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/rlvtapp/katachi/issues"
16
+ },
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "bin": {
20
+ "katachi": "./bin/katachi.mjs"
21
+ },
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ },
27
+ "./jsx": "./dist/api/jsx.d.ts"
28
+ },
29
+ "files": [
30
+ "bin",
31
+ "dist",
32
+ "docs",
33
+ "examples",
34
+ "README.md",
35
+ "LICENSE",
36
+ "CONTRIBUTING.md"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "engines": {
42
+ "node": ">=22",
43
+ "pnpm": ">=9"
44
+ },
45
+ "keywords": [
46
+ "askama",
47
+ "compiler",
48
+ "react",
49
+ "templates",
50
+ "tsx"
51
+ ],
52
+ "scripts": {
53
+ "build": "pnpm run build:package && pnpm run build:examples",
54
+ "build:package": "pnpm exec tsc -p tsconfig.build.json && node ./scripts/finalize-package.mjs",
55
+ "build:examples": "pnpm exec tsx src/cli/index.ts build --project ./examples/basic",
56
+ "dev": "pnpm exec tsx watch src/cli/index.ts build --project ./examples/basic",
57
+ "start": "pnpm exec tsx src/cli/index.ts build --project ./examples/basic",
58
+ "test": "pnpm exec tsx --test tests/**/*.test.ts",
59
+ "typecheck": "pnpm exec tsc -p tsconfig.json",
60
+ "verify:askama": "pnpm exec tsx src/cli/index.ts verify:examples",
61
+ "verify:examples": "pnpm exec tsx src/cli/index.ts verify:examples",
62
+ "prepublishOnly": "pnpm test && pnpm typecheck && pnpm run build:package"
63
+ },
64
+ "devDependencies": {
65
+ "@types/node": "^22.19.15",
66
+ "tsx": "^4.21.0",
67
+ "typescript": "^5.9.3"
68
+ }
69
+ }