@suzumiyaaoba/mdxr 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 (236) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/dist/cli.d.mts +1 -0
  4. package/dist/cli.mjs +2902 -0
  5. package/dist/components.d.mts +791 -0
  6. package/dist/components.mjs +2 -0
  7. package/dist/config-SI9IyFiC.mjs +184 -0
  8. package/dist/doc-context-CEqzMYKv.d.mts +568 -0
  9. package/dist/index.d.mts +20 -0
  10. package/dist/index.mjs +4 -0
  11. package/dist/ui-GKFD0mx5.mjs +15986 -0
  12. package/package.json +131 -0
  13. package/skill/SKILL.md +49 -0
  14. package/skill/references/components/charts.md +172 -0
  15. package/skill/references/components/document.md +98 -0
  16. package/skill/references/components/forms.md +35 -0
  17. package/skill/references/components/investigation.md +198 -0
  18. package/skill/references/components/layout.md +68 -0
  19. package/skill/references/components/output.md +158 -0
  20. package/skill/references/components/planning.md +155 -0
  21. package/skill/references/components/reports.md +252 -0
  22. package/skill/references/components/shadcn.md +19 -0
  23. package/skill/references/components.md +186 -0
  24. package/skill/references/extending.md +48 -0
  25. package/src/ask-sheet.ts +40 -0
  26. package/src/assets/css.ts +392 -0
  27. package/src/assets/scripts.ts +78 -0
  28. package/src/catalog.ts +287 -0
  29. package/src/cli.ts +178 -0
  30. package/src/client/doc-events.ts +1108 -0
  31. package/src/client/entry.ts +27 -0
  32. package/src/client-js.ts +47 -0
  33. package/src/component-map.ts +47 -0
  34. package/src/components/ui/accordion.tsx +77 -0
  35. package/src/components/ui/alert-dialog.tsx +185 -0
  36. package/src/components/ui/alert.tsx +76 -0
  37. package/src/components/ui/aspect-ratio.tsx +22 -0
  38. package/src/components/ui/attachment.tsx +208 -0
  39. package/src/components/ui/avatar.tsx +106 -0
  40. package/src/components/ui/badge.tsx +52 -0
  41. package/src/components/ui/breadcrumb.tsx +121 -0
  42. package/src/components/ui/bubble.tsx +128 -0
  43. package/src/components/ui/button-group.tsx +88 -0
  44. package/src/components/ui/button.tsx +58 -0
  45. package/src/components/ui/calendar.tsx +226 -0
  46. package/src/components/ui/card.tsx +102 -0
  47. package/src/components/ui/carousel.tsx +246 -0
  48. package/src/components/ui/chart.tsx +379 -0
  49. package/src/components/ui/checkbox.tsx +27 -0
  50. package/src/components/ui/collapsible.tsx +19 -0
  51. package/src/components/ui/combobox.tsx +298 -0
  52. package/src/components/ui/command.tsx +193 -0
  53. package/src/components/ui/context-menu.tsx +271 -0
  54. package/src/components/ui/dialog.tsx +159 -0
  55. package/src/components/ui/direction.tsx +4 -0
  56. package/src/components/ui/drawer.tsx +227 -0
  57. package/src/components/ui/dropdown-menu.tsx +269 -0
  58. package/src/components/ui/empty.tsx +104 -0
  59. package/src/components/ui/field.tsx +237 -0
  60. package/src/components/ui/fieldset.tsx +32 -0
  61. package/src/components/ui/frame.tsx +87 -0
  62. package/src/components/ui/hover-card.tsx +50 -0
  63. package/src/components/ui/input-group.tsx +159 -0
  64. package/src/components/ui/input-otp.tsx +83 -0
  65. package/src/components/ui/input.tsx +19 -0
  66. package/src/components/ui/item.tsx +202 -0
  67. package/src/components/ui/kbd.tsx +26 -0
  68. package/src/components/ui/label.tsx +19 -0
  69. package/src/components/ui/marker.tsx +71 -0
  70. package/src/components/ui/menubar.tsx +284 -0
  71. package/src/components/ui/message-scroller.tsx +128 -0
  72. package/src/components/ui/message.tsx +91 -0
  73. package/src/components/ui/meter.tsx +80 -0
  74. package/src/components/ui/native-select.tsx +64 -0
  75. package/src/components/ui/navigation-menu.tsx +170 -0
  76. package/src/components/ui/pagination.tsx +133 -0
  77. package/src/components/ui/popover.tsx +87 -0
  78. package/src/components/ui/progress.tsx +82 -0
  79. package/src/components/ui/questionnaire.tsx +328 -0
  80. package/src/components/ui/radio-group.tsx +35 -0
  81. package/src/components/ui/resizable.tsx +49 -0
  82. package/src/components/ui/scroll-area.tsx +50 -0
  83. package/src/components/ui/select.tsx +201 -0
  84. package/src/components/ui/separator.tsx +22 -0
  85. package/src/components/ui/sheet.tsx +135 -0
  86. package/src/components/ui/sidebar.tsx +730 -0
  87. package/src/components/ui/skeleton.tsx +13 -0
  88. package/src/components/ui/slider.tsx +51 -0
  89. package/src/components/ui/spinner.tsx +16 -0
  90. package/src/components/ui/switch.tsx +31 -0
  91. package/src/components/ui/table.tsx +113 -0
  92. package/src/components/ui/tabs.tsx +82 -0
  93. package/src/components/ui/textarea.tsx +17 -0
  94. package/src/components/ui/toast.tsx +229 -0
  95. package/src/components/ui/toggle-group.tsx +87 -0
  96. package/src/components/ui/toggle.tsx +43 -0
  97. package/src/components/ui/tooltip.tsx +65 -0
  98. package/src/components.ts +78 -0
  99. package/src/config.ts +56 -0
  100. package/src/define.ts +122 -0
  101. package/src/doc-context.ts +20 -0
  102. package/src/editor.ts +94 -0
  103. package/src/format-error.ts +78 -0
  104. package/src/guards.ts +84 -0
  105. package/src/hooks/use-mobile.ts +21 -0
  106. package/src/html.ts +91 -0
  107. package/src/hydrate/export-index.ts +232 -0
  108. package/src/hydrate/import-scan.ts +169 -0
  109. package/src/hydrate/plugins.ts +118 -0
  110. package/src/hydrate/runtime-module.ts +145 -0
  111. package/src/hydrate-runtime.ts +67 -0
  112. package/src/hydrate.ts +147 -0
  113. package/src/index.ts +16 -0
  114. package/src/init.ts +57 -0
  115. package/src/langs.ts +148 -0
  116. package/src/lines.ts +53 -0
  117. package/src/load-user-module.ts +191 -0
  118. package/src/mdx.ts +248 -0
  119. package/src/paths.ts +36 -0
  120. package/src/rehype/shiki.ts +533 -0
  121. package/src/remark/alerts.ts +53 -0
  122. package/src/remark/ast.ts +96 -0
  123. package/src/remark/callouts.ts +25 -0
  124. package/src/remark/code-file.ts +85 -0
  125. package/src/remark/code-meta.ts +21 -0
  126. package/src/remark/directives.ts +170 -0
  127. package/src/remark/file-paths.ts +64 -0
  128. package/src/remark/headings.ts +131 -0
  129. package/src/remark/no-js.ts +40 -0
  130. package/src/render.ts +337 -0
  131. package/src/serve.ts +322 -0
  132. package/src/styles/globals.css +134 -0
  133. package/src/styles/shadcn.css +641 -0
  134. package/src/tailwind.ts +119 -0
  135. package/src/ui/approvals.tsx +76 -0
  136. package/src/ui/ask-question.tsx +386 -0
  137. package/src/ui/ask.tsx +206 -0
  138. package/src/ui/attrs.ts +51 -0
  139. package/src/ui/audit.tsx +139 -0
  140. package/src/ui/bar-chart.tsx +334 -0
  141. package/src/ui/benchmarks.tsx +143 -0
  142. package/src/ui/bits.tsx +537 -0
  143. package/src/ui/board.tsx +173 -0
  144. package/src/ui/bridge.tsx +207 -0
  145. package/src/ui/bumps.tsx +178 -0
  146. package/src/ui/callout.tsx +106 -0
  147. package/src/ui/changes.tsx +89 -0
  148. package/src/ui/chart-bits.tsx +52 -0
  149. package/src/ui/chart.ts +577 -0
  150. package/src/ui/checks.tsx +203 -0
  151. package/src/ui/child-index.tsx +44 -0
  152. package/src/ui/children.ts +43 -0
  153. package/src/ui/chips.tsx +49 -0
  154. package/src/ui/cmd.tsx +27 -0
  155. package/src/ui/columns.tsx +31 -0
  156. package/src/ui/comments.tsx +544 -0
  157. package/src/ui/compare.tsx +67 -0
  158. package/src/ui/decision.tsx +79 -0
  159. package/src/ui/deps.tsx +78 -0
  160. package/src/ui/details.tsx +43 -0
  161. package/src/ui/diff-parse.ts +307 -0
  162. package/src/ui/diff.tsx +458 -0
  163. package/src/ui/diffstat.tsx +58 -0
  164. package/src/ui/due.tsx +65 -0
  165. package/src/ui/effort.tsx +29 -0
  166. package/src/ui/endpoints.tsx +118 -0
  167. package/src/ui/envvars.tsx +96 -0
  168. package/src/ui/figure.tsx +37 -0
  169. package/src/ui/file-icon.ts +1020 -0
  170. package/src/ui/file-link.ts +32 -0
  171. package/src/ui/file-ref.tsx +39 -0
  172. package/src/ui/files.tsx +112 -0
  173. package/src/ui/findings.tsx +85 -0
  174. package/src/ui/flow.tsx +73 -0
  175. package/src/ui/funnel.tsx +121 -0
  176. package/src/ui/gantt.tsx +443 -0
  177. package/src/ui/gauges.tsx +135 -0
  178. package/src/ui/glossary.tsx +29 -0
  179. package/src/ui/graph-layout.ts +149 -0
  180. package/src/ui/graph-specs.tsx +102 -0
  181. package/src/ui/graph.tsx +278 -0
  182. package/src/ui/grid.tsx +119 -0
  183. package/src/ui/hypothesis.tsx +94 -0
  184. package/src/ui/icon.tsx +80 -0
  185. package/src/ui/incident.tsx +130 -0
  186. package/src/ui/index.ts +342 -0
  187. package/src/ui/ins-del.tsx +41 -0
  188. package/src/ui/json.tsx +190 -0
  189. package/src/ui/layout.ts +9 -0
  190. package/src/ui/line-chart.tsx +251 -0
  191. package/src/ui/matrix.tsx +208 -0
  192. package/src/ui/meta.tsx +73 -0
  193. package/src/ui/option.tsx +64 -0
  194. package/src/ui/owner.tsx +40 -0
  195. package/src/ui/packages.tsx +104 -0
  196. package/src/ui/pathway.tsx +96 -0
  197. package/src/ui/phase.tsx +41 -0
  198. package/src/ui/pie-chart.tsx +170 -0
  199. package/src/ui/plan.tsx +77 -0
  200. package/src/ui/pre.tsx +175 -0
  201. package/src/ui/priority.tsx +51 -0
  202. package/src/ui/props.tsx +77 -0
  203. package/src/ui/quadrant.tsx +172 -0
  204. package/src/ui/radar.tsx +200 -0
  205. package/src/ui/ref.tsx +160 -0
  206. package/src/ui/release.tsx +178 -0
  207. package/src/ui/req.tsx +42 -0
  208. package/src/ui/review.tsx +133 -0
  209. package/src/ui/risk.tsx +67 -0
  210. package/src/ui/sankey.tsx +287 -0
  211. package/src/ui/scatter.tsx +237 -0
  212. package/src/ui/schema.tsx +113 -0
  213. package/src/ui/score.tsx +105 -0
  214. package/src/ui/search.tsx +120 -0
  215. package/src/ui/series.tsx +38 -0
  216. package/src/ui/severity.tsx +69 -0
  217. package/src/ui/shadcn.tsx +216 -0
  218. package/src/ui/spark.tsx +86 -0
  219. package/src/ui/stack.tsx +57 -0
  220. package/src/ui/stats.tsx +63 -0
  221. package/src/ui/status-badge.tsx +66 -0
  222. package/src/ui/statuspage.tsx +238 -0
  223. package/src/ui/steps.tsx +74 -0
  224. package/src/ui/summary.tsx +41 -0
  225. package/src/ui/symbol-ref.tsx +73 -0
  226. package/src/ui/terminal.tsx +117 -0
  227. package/src/ui/tests.tsx +218 -0
  228. package/src/ui/timeline.tsx +63 -0
  229. package/src/ui/toc.tsx +56 -0
  230. package/src/ui/tones.ts +187 -0
  231. package/src/ui/trace.tsx +69 -0
  232. package/src/ui/tree.tsx +281 -0
  233. package/src/ui/treemap.tsx +128 -0
  234. package/src/ui/venn.tsx +258 -0
  235. package/src/ui/verdict.tsx +78 -0
  236. package/src/ui/waterfall.tsx +142 -0
@@ -0,0 +1,73 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent } from "../define.js";
4
+ import { nonEmpty } from "../guards.js";
5
+ import { hasIcon, Icon, normalizeIconName } from "./icon.js";
6
+ import { TEXT } from "./tones.js";
7
+
8
+ export const MetaItem = defineComponent(
9
+ {
10
+ description:
11
+ '<Meta> 内の1項目。label と内容を並べる。icon にアイコン名 (例: "lucide:tag") を付けられる',
12
+ schema: v.looseObject({
13
+ icon: v.optional(
14
+ v.pipe(
15
+ v.string(),
16
+ v.check(
17
+ (n) => hasIcon(n),
18
+ (i) => `unknown icon: ${i.input}`
19
+ )
20
+ )
21
+ ),
22
+ label: v.optional(v.string()),
23
+ }),
24
+ },
25
+ ({ label, icon, children }) => (
26
+ <span className="inline-flex items-baseline gap-1">
27
+ {icon === undefined ? null : (
28
+ <Icon
29
+ className={`h-3.5 w-3.5 self-center ${TEXT.faint}`}
30
+ name={normalizeIconName(icon)}
31
+ />
32
+ )}
33
+ {nonEmpty(label) ? <span className={TEXT.faint}>{label}:</span> : null}
34
+ <span className={`font-medium ${TEXT.body}`}>{children}</span>
35
+ </span>
36
+ )
37
+ );
38
+
39
+ const SHORTHAND: [string, "date" | "owner" | "version" | "updated", string][] =
40
+ [
41
+ ["Date", "date", "lucide:calendar"],
42
+ ["Owner", "owner", "lucide:user"],
43
+ ["Version", "version", "lucide:tag"],
44
+ ["Updated", "updated", "lucide:history"],
45
+ ];
46
+
47
+ export const Meta = defineComponent(
48
+ {
49
+ description:
50
+ "文書メタ情報の行。date/owner/version/updated 属性か <MetaItem> を子に取る",
51
+ schema: v.looseObject({
52
+ date: v.optional(v.string()),
53
+ owner: v.optional(v.string()),
54
+ updated: v.optional(v.string()),
55
+ version: v.optional(v.string()),
56
+ }),
57
+ },
58
+ ({ date, owner, version, updated, children }) => {
59
+ const values = { date, owner, updated, version };
60
+ return (
61
+ <div className="not-prose my-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
62
+ {SHORTHAND.map(([label, key, icon]) =>
63
+ nonEmpty(values[key]) ? (
64
+ <MetaItem icon={icon} key={key} label={label}>
65
+ {values[key]}
66
+ </MetaItem>
67
+ ) : null
68
+ )}
69
+ {children}
70
+ </div>
71
+ );
72
+ }
73
+ );
@@ -0,0 +1,64 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent } from "../define.js";
4
+ import { nonEmpty } from "../guards.js";
5
+ import { Pill, TrimBody } from "./bits.js";
6
+ import { BORDER_CLS, TONE, TONE_BORDER } from "./tones.js";
7
+
8
+ export const OPTION_STATUSES = [
9
+ "considered",
10
+ "recommended",
11
+ "rejected",
12
+ ] as const;
13
+ export type OptionStatus = (typeof OPTION_STATUSES)[number];
14
+
15
+ const STYLES: Record<
16
+ OptionStatus,
17
+ { card: string; chip: string; icon: string; label: string }
18
+ > = {
19
+ considered: {
20
+ card: BORDER_CLS,
21
+ chip: TONE.neutral,
22
+ icon: "lucide:minus",
23
+ label: "Considered",
24
+ },
25
+ recommended: {
26
+ card: TONE_BORDER.emerald,
27
+ chip: TONE.emerald,
28
+ icon: "lucide:thumbs-up",
29
+ label: "Recommended",
30
+ },
31
+ rejected: {
32
+ card: `${BORDER_CLS} opacity-70`,
33
+ chip: TONE.red,
34
+ icon: "lucide:thumbs-down",
35
+ label: "Rejected",
36
+ },
37
+ };
38
+
39
+ export const Option = defineComponent(
40
+ {
41
+ description:
42
+ "検討した選択肢カード。status は recommended|considered|rejected。<Columns> と組み合わせて並べる",
43
+ schema: v.looseObject({
44
+ status: v.optional(v.picklist(OPTION_STATUSES), "considered"),
45
+ title: v.optional(v.string()),
46
+ }),
47
+ },
48
+ ({ title, status, children }) => {
49
+ const s = STYLES[status];
50
+ return (
51
+ <div className={`my-6 rounded-lg border px-4 py-3 ${s.card}`}>
52
+ <div className="flex flex-wrap items-center gap-2">
53
+ {nonEmpty(title) ? (
54
+ <span className="font-semibold">{title}</span>
55
+ ) : null}
56
+ <Pill className={s.chip} icon={s.icon}>
57
+ {s.label}
58
+ </Pill>
59
+ </div>
60
+ <TrimBody className="mt-1.5 text-sm">{children}</TrimBody>
61
+ </div>
62
+ );
63
+ }
64
+ );
@@ -0,0 +1,40 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent } from "../define.js";
4
+ import { nonEmpty } from "../guards.js";
5
+ import { TEXT } from "./tones.js";
6
+
7
+ /** `"Bob Tanaka"` → `"BT"`, `"@devin"` → `"D"` — avatar text from a name. */
8
+ export const initials = (name: string): string => {
9
+ const clean = name.replace(/^@+/u, "").trim();
10
+ const parts = clean.split(/[\s._-]+/u).filter(Boolean);
11
+ const chars =
12
+ parts.length > 0
13
+ ? parts.slice(0, 2).map((w) => w.charAt(0))
14
+ : [clean.charAt(0)];
15
+ return chars.join("").toUpperCase();
16
+ };
17
+
18
+ export const Owner = defineComponent(
19
+ {
20
+ description: "担当者チップ。name は必須、role で役割を添えられる",
21
+ schema: v.looseObject({
22
+ name: v.string(),
23
+ role: v.optional(v.string()),
24
+ }),
25
+ },
26
+ ({ name, role }) => (
27
+ <span
28
+ className={`not-prose inline-flex items-center gap-1.5 rounded-full bg-neutral-100 py-0.5 pr-2 pl-0.5 text-xs ${TEXT.chip} dark:bg-neutral-800`}
29
+ >
30
+ <span
31
+ aria-hidden
32
+ className="flex h-4.5 w-4.5 items-center justify-center rounded-full bg-neutral-300 text-[9px] font-semibold text-neutral-700 dark:bg-neutral-600 dark:text-neutral-100"
33
+ >
34
+ {initials(name)}
35
+ </span>
36
+ <span className="font-medium">{name}</span>
37
+ {nonEmpty(role) ? <span className={TEXT.faint}>{role}</span> : null}
38
+ </span>
39
+ )
40
+ );
@@ -0,0 +1,104 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent, flattenChildren } from "../define.js";
4
+ import { nonEmpty, safeHref } from "../guards.js";
5
+ import { NUMISH } from "./attrs.js";
6
+ import { CaptionBar, ListPanel, ListRow, MaybeLink, RowNote } from "./bits.js";
7
+ import { isEl } from "./children.js";
8
+ import { Icon } from "./icon.js";
9
+ import {
10
+ CAPTION_TITLE_CLS,
11
+ COUNT_CHIP_CLS,
12
+ LOC_CLS,
13
+ MINI_CHIP_CLS,
14
+ TEXT,
15
+ TONE,
16
+ TRIM_CLS,
17
+ } from "./tones.js";
18
+
19
+ export const PACKAGE_KINDS = ["dep", "dev", "peer", "optional"] as const;
20
+ export type PackageKind = (typeof PACKAGE_KINDS)[number];
21
+
22
+ const PKG_STYLES: Record<PackageKind, { cls: string; label: string }> = {
23
+ dep: { cls: TONE.sky, label: "dep" },
24
+ dev: { cls: TONE.neutral, label: "dev" },
25
+ optional: { cls: TONE.amber, label: "optional" },
26
+ peer: { cls: TONE.violet, label: "peer" },
27
+ };
28
+
29
+ export const Package = defineComponent(
30
+ {
31
+ description:
32
+ "パッケージ棚卸し1行。name は必須、version、kind は dep|dev|peer|optional、license にライセンス名、href でレジストリ/リポジトリリンク。children は用途メモ",
33
+ schema: v.looseObject({
34
+ href: v.optional(v.string()),
35
+ kind: v.optional(v.picklist(PACKAGE_KINDS)),
36
+ license: v.optional(v.string()),
37
+ name: v.string(),
38
+ note: v.optional(v.string()),
39
+ version: v.optional(NUMISH),
40
+ }),
41
+ },
42
+ ({ name, version, kind, license, note, href, children }) => {
43
+ const link = safeHref(href);
44
+ return (
45
+ <ListRow>
46
+ <Icon
47
+ className={`h-3.5 w-3.5 shrink-0 self-center ${TEXT.faint}`}
48
+ name="lucide:package"
49
+ />
50
+ <MaybeLink
51
+ className={`font-mono text-[0.85em] font-medium ${TEXT.code} no-underline hover:underline`}
52
+ href={link}
53
+ >
54
+ {name}
55
+ </MaybeLink>
56
+ {version === undefined || version === "" ? null : (
57
+ <code className={LOC_CLS}>{String(version)}</code>
58
+ )}
59
+ {kind === undefined ? null : (
60
+ <span className={`${MINI_CHIP_CLS} ${PKG_STYLES[kind].cls}`}>
61
+ {PKG_STYLES[kind].label}
62
+ </span>
63
+ )}
64
+ {nonEmpty(license) ? (
65
+ <span className={`${MINI_CHIP_CLS} ${TONE.neutral}`}>{license}</span>
66
+ ) : null}
67
+ {nonEmpty(note) ? (
68
+ <span className={`text-sm ${TEXT.muted}`}>{note}</span>
69
+ ) : null}
70
+ <RowNote className={TRIM_CLS}>{children}</RowNote>
71
+ </ListRow>
72
+ );
73
+ }
74
+ );
75
+
76
+ export const Packages = defineComponent(
77
+ {
78
+ description:
79
+ "パッケージ一覧のコンテナ。<Package> を並べ、総数をキャプションに表示。title はキャプション",
80
+ schema: v.looseObject({
81
+ title: v.optional(v.string()),
82
+ }),
83
+ },
84
+ ({ title, children }) => {
85
+ const total = flattenChildren(children).filter((n) =>
86
+ isEl(n, Package)
87
+ ).length;
88
+ const caption = nonEmpty(title) || total > 0;
89
+ return (
90
+ <ListPanel>
91
+ {caption ? (
92
+ <CaptionBar className={CAPTION_TITLE_CLS}>
93
+ <Icon className="h-3.5 w-3.5" name="lucide:boxes" />
94
+ {nonEmpty(title) ? title : "Packages"}
95
+ {total > 0 ? (
96
+ <span className={`ml-auto ${COUNT_CHIP_CLS}`}>{total}</span>
97
+ ) : null}
98
+ </CaptionBar>
99
+ ) : null}
100
+ {children}
101
+ </ListPanel>
102
+ );
103
+ }
104
+ );
@@ -0,0 +1,96 @@
1
+ import type { ReactElement } from "react";
2
+ import * as v from "valibot";
3
+
4
+ import { defineComponent, flattenChildren } from "../define.js";
5
+ import { nonEmpty } from "../guards.js";
6
+ import { attrTrue, BOOLISH_PROP } from "./attrs.js";
7
+ import { CaptionBar, Panel } from "./bits.js";
8
+ import { isEl } from "./children.js";
9
+ import { Icon } from "./icon.js";
10
+ import {
11
+ isStatus,
12
+ STATUS_ICON_CLS,
13
+ STATUS_ICONS,
14
+ STATUS_PROP,
15
+ } from "./status-badge.js";
16
+ import {
17
+ CAPTION_TITLE_CLS,
18
+ RAIL_BG_CLS,
19
+ TEXT,
20
+ TEXT_SUB,
21
+ TRIM_CLS,
22
+ } from "./tones.js";
23
+
24
+ export const Stop = defineComponent(
25
+ {
26
+ description:
27
+ "経路ステップ1点。label は必須 (バージョン・環境名など)、status は todo|doing|done|blocked、note で補足 (codemod・レビュー待ちなど)、current で現在地マーク。children は説明文",
28
+ schema: v.looseObject({
29
+ current: BOOLISH_PROP,
30
+ label: v.string(),
31
+ note: v.optional(v.string()),
32
+ status: STATUS_PROP,
33
+ }),
34
+ },
35
+ // Rendered by Pathway — standalone use falls back to a chip row.
36
+ ({ label, note, status, current, children }) => {
37
+ const st = isStatus(status) ? status : "todo";
38
+ return (
39
+ <div className="flex w-28 shrink-0 flex-col items-center gap-1 px-1 text-center">
40
+ <Icon
41
+ className={`h-5 w-5 ${STATUS_ICON_CLS[st]} ${attrTrue(current) ? "rounded-full ring-2 ring-sky-400/60" : ""}`}
42
+ label={st}
43
+ name={STATUS_ICONS[st]}
44
+ />
45
+ <span className="font-mono text-xs font-semibold">{label}</span>
46
+ {nonEmpty(note) ? (
47
+ <span className={`${TEXT_SUB} ${TEXT.faint}`}>{note}</span>
48
+ ) : null}
49
+ {children === undefined ? null : (
50
+ <span className={`${TEXT_SUB} ${TEXT.muted} ${TRIM_CLS}`}>
51
+ {children}
52
+ </span>
53
+ )}
54
+ </div>
55
+ );
56
+ }
57
+ );
58
+
59
+ export const Pathway = defineComponent(
60
+ {
61
+ description:
62
+ "経路ステッパー (バージョンパス v17→v18→v19、環境昇格 dev→staging→prod、段階ロールアウト)。<Stop> をコネクタ線付きで横に並べる。title はキャプション",
63
+ schema: v.looseObject({
64
+ title: v.optional(v.string()),
65
+ }),
66
+ },
67
+ ({ title, children }): ReactElement => {
68
+ const stops = flattenChildren(children);
69
+ return (
70
+ <Panel>
71
+ {nonEmpty(title) ? (
72
+ <CaptionBar className={CAPTION_TITLE_CLS}>
73
+ <Icon className="h-3.5 w-3.5" name="lucide:milestone" />
74
+ {title}
75
+ </CaptionBar>
76
+ ) : null}
77
+ <div className="flex items-start gap-x-1 overflow-x-auto px-4 py-4">
78
+ {stops.map((stop, i) => (
79
+ <div
80
+ className="flex flex-1 items-start last:flex-none"
81
+ key={isEl(stop, Stop) ? (stop.key ?? i) : i}
82
+ >
83
+ {stop}
84
+ {i < stops.length - 1 ? (
85
+ <div
86
+ aria-hidden
87
+ className={`mt-2.5 h-0.5 min-w-4 flex-1 rounded-full ${RAIL_BG_CLS}`}
88
+ />
89
+ ) : null}
90
+ </div>
91
+ ))}
92
+ </div>
93
+ </Panel>
94
+ );
95
+ }
96
+ );
@@ -0,0 +1,41 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent } from "../define.js";
4
+ import { nonEmpty } from "../guards.js";
5
+ import { Due } from "./due.js";
6
+ import { Owner } from "./owner.js";
7
+ import { STATUS_PROP, StatusBadge } from "./status-badge.js";
8
+ import { BORDER_CLS } from "./tones.js";
9
+
10
+ export const Phase = defineComponent(
11
+ {
12
+ description:
13
+ "計画のフェーズ見出し。`:::phase` からも生成される。owner/due で担当・期限を示せる",
14
+ schema: v.looseObject({
15
+ due: v.optional(v.string()),
16
+ owner: v.optional(v.string()),
17
+ status: STATUS_PROP,
18
+ title: v.string(),
19
+ }),
20
+ },
21
+ ({ title, status, owner, due, children }) => {
22
+ const hasChips = status !== undefined || nonEmpty(owner) || nonEmpty(due);
23
+ return (
24
+ <section className="my-10">
25
+ <h2
26
+ className={`mt-0 flex flex-wrap items-center gap-3 border-b pb-2 ${BORDER_CLS}`}
27
+ >
28
+ {title}
29
+ {hasChips ? (
30
+ <span className="inline-flex items-center gap-2">
31
+ {status === undefined ? null : <StatusBadge status={status} />}
32
+ {nonEmpty(owner) ? <Owner name={owner} /> : null}
33
+ {nonEmpty(due) ? <Due date={due} /> : null}
34
+ </span>
35
+ ) : null}
36
+ </h2>
37
+ {children}
38
+ </section>
39
+ );
40
+ }
41
+ );
@@ -0,0 +1,170 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import * as v from "valibot";
3
+
4
+ import { defineComponent, flattenChildren, parseProps } from "../define.js";
5
+ import { nonEmpty } from "../guards.js";
6
+ import { attrTrue, BOOLISH_PROP, NUMISH, numOf } from "./attrs.js";
7
+ import { CODE_CHIP_CLS } from "./bits.js";
8
+ import { ChartPanel } from "./chart-bits.js";
9
+ import { CHART_TONES, fmtNum, sectorPath, toneOf } from "./chart.js";
10
+ import { isEl } from "./children.js";
11
+ import { TEXT, TEXT_MICRO } from "./tones.js";
12
+
13
+ /**
14
+ * Pie/donut chart — part-of-whole share across <Slice> children. `donut`
15
+ * cuts a center hole carrying the total; legend rows show name · value · %.
16
+ */
17
+
18
+ const SLICE_SCHEMA = v.looseObject({
19
+ name: v.string(),
20
+ note: v.optional(v.string()),
21
+ tone: v.optional(v.picklist(CHART_TONES)),
22
+ value: NUMISH,
23
+ });
24
+
25
+ type SliceSpec = v.InferOutput<typeof SLICE_SCHEMA>;
26
+
27
+ export const Slice = defineComponent(
28
+ {
29
+ description:
30
+ "円グラフの1区切り。<PieChart> の子。name/value は必須、tone で色、note で補足",
31
+ schema: SLICE_SCHEMA,
32
+ },
33
+ // Standalone use: a `name value` chip so misplaced slices still render.
34
+ ({ name, value, note }): ReactElement => (
35
+ <span className={`font-mono ${CODE_CHIP_CLS}`}>
36
+ {name}
37
+ <span className="opacity-70">{value}</span>
38
+ {nonEmpty(note) ? <span className="opacity-70">{note}</span> : null}
39
+ </span>
40
+ )
41
+ );
42
+
43
+ const collectSlices = (
44
+ children: ReactNode
45
+ ): { rest: ReactNode[]; slices: { n: number; spec: SliceSpec }[] } => {
46
+ const slices: { n: number; spec: SliceSpec }[] = [];
47
+ const rest: ReactNode[] = [];
48
+ for (const child of flattenChildren(children)) {
49
+ if (isEl(child, Slice)) {
50
+ const spec = parseProps(SLICE_SCHEMA, child.props, "Slice");
51
+ slices.push({ n: Math.max(0, numOf(spec.value) ?? 0), spec });
52
+ } else {
53
+ rest.push(child);
54
+ }
55
+ }
56
+ return { rest, slices };
57
+ };
58
+
59
+ const VB = 200;
60
+ const CX = VB / 2;
61
+ const CY = VB / 2;
62
+ const R1 = 92;
63
+ const R0 = 58;
64
+
65
+ export const PieChart = defineComponent(
66
+ {
67
+ description:
68
+ "円グラフ/ドーナツコンテナ (構成比)。<Slice name value> を並べる。donut で中央に穴 (合計を表示)、unit は凡例の値の単位",
69
+ schema: v.looseObject({
70
+ donut: BOOLISH_PROP,
71
+ title: v.optional(v.string()),
72
+ unit: v.optional(v.string()),
73
+ }),
74
+ },
75
+ ({ title, donut, unit, children }) => {
76
+ const { slices, rest } = collectSlices(children);
77
+ const total = slices.reduce((a, s) => a + s.n, 0);
78
+ const hole = attrTrue(donut);
79
+ let angle = -Math.PI / 2;
80
+
81
+ const arcs = slices.map((s, i) => {
82
+ const span = total === 0 ? 0 : (s.n / total) * Math.PI * 2;
83
+ const a0 = angle;
84
+ const a1 = angle + span;
85
+ angle = a1;
86
+ return {
87
+ d: sectorPath(CX, CY, hole ? R0 : 0, R1, a0, a1),
88
+ i,
89
+ s,
90
+ };
91
+ });
92
+
93
+ return (
94
+ <ChartPanel
95
+ figure={nonEmpty(unit) ? `${fmtNum(total)}${unit}` : undefined}
96
+ icon="lucide:chart-pie"
97
+ title={title}
98
+ >
99
+ <div className="flex flex-col items-center gap-4 px-4 py-4 sm:flex-row sm:items-start">
100
+ <div className="relative h-44 w-44 shrink-0">
101
+ <svg className="h-full w-full" viewBox={`0 0 ${VB} ${VB}`}>
102
+ <title>{title ?? "pie chart"}</title>
103
+ {total === 0 ? (
104
+ <circle
105
+ className="fill-neutral-200 dark:fill-neutral-700"
106
+ cx={CX}
107
+ cy={CY}
108
+ r={hole ? (R0 + R1) / 2 : R1}
109
+ />
110
+ ) : null}
111
+ {arcs.map((a) => (
112
+ <path
113
+ className={toneOf(a.s.spec.tone, a.i).fill}
114
+ d={a.d}
115
+ key={a.i}
116
+ >
117
+ <title>
118
+ {`${a.s.spec.name} · ${fmtNum(a.s.n)}${unit ?? ""} (${total === 0 ? 0 : Math.round((a.s.n / total) * 100)}%)`}
119
+ </title>
120
+ </path>
121
+ ))}
122
+ </svg>
123
+ {hole ? (
124
+ <div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center">
125
+ <span className="font-mono text-lg font-semibold tabular-nums">
126
+ {fmtNum(total)}
127
+ </span>
128
+ {nonEmpty(unit) ? (
129
+ <span className={`${TEXT_MICRO} ${TEXT.faint}`}>{unit}</span>
130
+ ) : null}
131
+ </div>
132
+ ) : null}
133
+ </div>
134
+ <ul className="min-w-0 flex-1 space-y-1.5 self-center sm:self-auto">
135
+ {slices.map((s, i) => {
136
+ const style = toneOf(s.spec.tone, i);
137
+ const pct = total === 0 ? 0 : (s.n / total) * 100;
138
+ return (
139
+ <li
140
+ className="flex items-baseline gap-2 text-sm"
141
+ key={s.spec.name}
142
+ >
143
+ <span
144
+ className={`h-2 w-2 shrink-0 self-center rounded-[2px] ${style.bg}`}
145
+ />
146
+ <span className="min-w-0 flex-1 truncate">
147
+ {s.spec.name}
148
+ {nonEmpty(s.spec.note) ? (
149
+ <span className={`ml-1.5 ${TEXT_MICRO} ${TEXT.faint}`}>
150
+ {s.spec.note}
151
+ </span>
152
+ ) : null}
153
+ </span>
154
+ <span
155
+ className={`font-mono ${TEXT_MICRO} tabular-nums ${TEXT.muted}`}
156
+ >
157
+ {fmtNum(s.n)}
158
+ {unit}
159
+ {` · ${Math.round(pct)}%`}
160
+ </span>
161
+ </li>
162
+ );
163
+ })}
164
+ </ul>
165
+ </div>
166
+ {rest.length > 0 ? <div className="px-4 pb-3">{rest}</div> : null}
167
+ </ChartPanel>
168
+ );
169
+ }
170
+ );
@@ -0,0 +1,77 @@
1
+ import type { ReactElement } from "react";
2
+ import * as v from "valibot";
3
+
4
+ import { defineComponent } from "../define.js";
5
+ import { nonEmpty } from "../guards.js";
6
+ import { Meta } from "./meta.js";
7
+ import { STATUS_PROP, StatusBadge } from "./status-badge.js";
8
+ import { BORDER_CLS } from "./tones.js";
9
+
10
+ interface PlanHeaderProps {
11
+ date?: string | undefined;
12
+ owner?: string | undefined;
13
+ status?: string | undefined;
14
+ title?: string | undefined;
15
+ updated?: string | undefined;
16
+ version?: string | undefined;
17
+ }
18
+
19
+ const hasMeta = (p: PlanHeaderProps): boolean =>
20
+ [p.date, p.owner, p.version, p.updated].some(nonEmpty);
21
+
22
+ /**
23
+ * The document header row shared by `<Plan>` and the frontmatter-generated
24
+ * header in render.ts. `status` is validated by StatusBadge's own schema.
25
+ */
26
+ export const PlanHeader = (p: PlanHeaderProps): ReactElement => (
27
+ <header className={`mb-8 border-b pb-4 ${BORDER_CLS}`}>
28
+ <div className="flex flex-wrap items-center gap-3">
29
+ {nonEmpty(p.title) ? <h1 className="m-0">{p.title}</h1> : null}
30
+ {nonEmpty(p.status) ? <StatusBadge status={p.status} /> : null}
31
+ </div>
32
+ {hasMeta(p) ? (
33
+ <Meta
34
+ date={p.date}
35
+ owner={p.owner}
36
+ updated={p.updated}
37
+ version={p.version}
38
+ />
39
+ ) : null}
40
+ </header>
41
+ );
42
+
43
+ export const Plan = defineComponent(
44
+ {
45
+ description:
46
+ "ドキュメントのルート要素。title/status/date/owner/version/updated のヘッダを描画する",
47
+ schema: v.looseObject({
48
+ date: v.optional(v.string()),
49
+ owner: v.optional(v.string()),
50
+ status: STATUS_PROP,
51
+ title: v.optional(v.string()),
52
+ updated: v.optional(v.string()),
53
+ version: v.optional(v.string()),
54
+ }),
55
+ },
56
+ ({ title, status, date, owner, version, updated, children }) => {
57
+ const showHeader =
58
+ nonEmpty(title) ||
59
+ nonEmpty(status) ||
60
+ [date, owner, version, updated].some(nonEmpty);
61
+ return (
62
+ <article>
63
+ {showHeader ? (
64
+ <PlanHeader
65
+ date={date}
66
+ owner={owner}
67
+ status={status}
68
+ title={title}
69
+ updated={updated}
70
+ version={version}
71
+ />
72
+ ) : null}
73
+ {children}
74
+ </article>
75
+ );
76
+ }
77
+ );