@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,203 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import * as v from "valibot";
3
+
4
+ import { defineComponent, flattenChildren } from "../define.js";
5
+ import { isOneOf, nonEmpty, safeHref } from "../guards.js";
6
+ import { attrTrue, BOOLISH_PROP, NUMISH } from "./attrs.js";
7
+ import { CaptionBar, ListPanel, MaybeLink, TrimBody } from "./bits.js";
8
+ import { isEl, propOf } from "./children.js";
9
+ import { Icon } from "./icon.js";
10
+ import { formatDuration, parseDuration } from "./tests.js";
11
+ import {
12
+ CHIP_BORDER_CLS,
13
+ COUNT_CHIP_CLS,
14
+ LOC_CLS,
15
+ MINI_CHIP_CLS,
16
+ SUNKEN_CLS,
17
+ TEXT,
18
+ TONE,
19
+ TONE_TEXT,
20
+ } from "./tones.js";
21
+
22
+ export const CHECK_STATUSES = [
23
+ "pass",
24
+ "fail",
25
+ "running",
26
+ "pending",
27
+ "skip",
28
+ ] as const;
29
+ export type CheckStatus = (typeof CHECK_STATUSES)[number];
30
+
31
+ const isCheckStatus = isOneOf(CHECK_STATUSES);
32
+
33
+ const STYLES: Record<
34
+ CheckStatus,
35
+ { cls: string; icon: string; label: string }
36
+ > = {
37
+ fail: {
38
+ cls: TONE_TEXT.red,
39
+ icon: "lucide:circle-x",
40
+ label: "failed",
41
+ },
42
+ pass: {
43
+ cls: TONE_TEXT.emerald,
44
+ icon: "lucide:circle-check",
45
+ label: "passed",
46
+ },
47
+ pending: {
48
+ cls: TONE_TEXT.neutral,
49
+ icon: "lucide:circle-dashed",
50
+ label: "pending",
51
+ },
52
+ running: {
53
+ cls: TONE_TEXT.sky,
54
+ icon: "lucide:loader-circle",
55
+ label: "running",
56
+ },
57
+ skip: {
58
+ cls: TONE_TEXT.neutral,
59
+ icon: "lucide:circle-minus",
60
+ label: "skipped",
61
+ },
62
+ };
63
+
64
+ export const Check = defineComponent(
65
+ {
66
+ description:
67
+ "CI チェック1行。name は必須、status は pass|fail|running|pending|skip。duration、required で必須チェックチップ、href で詳細リンク。children はログ抜粋などの詳細",
68
+ schema: v.looseObject({
69
+ duration: v.optional(NUMISH),
70
+ href: v.optional(v.string()),
71
+ name: v.string(),
72
+ required: BOOLISH_PROP,
73
+ status: v.optional(v.picklist(CHECK_STATUSES), "pass"),
74
+ }),
75
+ },
76
+ ({ name, status, duration, required, href, children }) => {
77
+ const s = STYLES[status];
78
+ const link = safeHref(href);
79
+ return (
80
+ <div className="px-4 py-2.5">
81
+ <div className="flex flex-wrap items-baseline gap-x-2.5 gap-y-1">
82
+ <Icon
83
+ className={`h-4 w-4 shrink-0 self-center ${s.cls}`}
84
+ label={s.label}
85
+ name={s.icon}
86
+ />
87
+ <MaybeLink
88
+ className="text-sm text-inherit no-underline hover:underline"
89
+ href={link}
90
+ >
91
+ {name}
92
+ </MaybeLink>
93
+ {attrTrue(required) ? (
94
+ <span className={`${MINI_CHIP_CLS} ${TONE.neutral}`}>Required</span>
95
+ ) : null}
96
+ {duration === undefined || duration === "" ? null : (
97
+ <span className={`${LOC_CLS} ml-auto tabular-nums`}>
98
+ {String(duration)}
99
+ </span>
100
+ )}
101
+ </div>
102
+ <TrimBody
103
+ className={`mt-1.5 rounded-md border-l-2 px-3 py-2 text-xs ${TEXT.body} ${
104
+ status === "fail"
105
+ ? "border-red-400/60 bg-red-500/5"
106
+ : `${CHIP_BORDER_CLS} ${SUNKEN_CLS}`
107
+ }`}
108
+ >
109
+ {children}
110
+ </TrimBody>
111
+ </div>
112
+ );
113
+ }
114
+ );
115
+
116
+ interface CheckSummary {
117
+ counts: Map<CheckStatus, number>;
118
+ durationOk: boolean;
119
+ total: number;
120
+ totalMs: number;
121
+ }
122
+
123
+ const summarizeChecks = (children: ReactNode): CheckSummary => {
124
+ const counts = new Map<CheckStatus, number>();
125
+ let totalMs = 0;
126
+ let total = 0;
127
+ let durationOk = true;
128
+ for (const node of flattenChildren(children)) {
129
+ if (!isEl(node, Check)) {
130
+ continue;
131
+ }
132
+ const st = propOf(node, "status");
133
+ const status = isCheckStatus(st) ? st : "pass";
134
+ counts.set(status, (counts.get(status) ?? 0) + 1);
135
+ total += 1;
136
+ const rawDuration = propOf(node, "duration");
137
+ const d = parseDuration(rawDuration);
138
+ if (d !== undefined) {
139
+ totalMs += d;
140
+ continue;
141
+ }
142
+ if (rawDuration !== undefined && rawDuration !== "") {
143
+ durationOk = false;
144
+ }
145
+ }
146
+ return { counts, durationOk, total, totalMs };
147
+ };
148
+
149
+ const ChecksCaption = ({
150
+ context,
151
+ summary,
152
+ title,
153
+ }: {
154
+ context: string | undefined;
155
+ summary: CheckSummary;
156
+ title: string | undefined;
157
+ }): ReactElement => (
158
+ <CaptionBar className="flex flex-wrap items-center gap-x-2.5 gap-y-1">
159
+ <Icon className="h-3.5 w-3.5" name="lucide:list-checks" />
160
+ <span className="font-medium">{nonEmpty(title) ? title : "Checks"}</span>
161
+ {nonEmpty(context) ? (
162
+ <span className={COUNT_CHIP_CLS}>{context}</span>
163
+ ) : null}
164
+ <span className="ml-auto flex items-center gap-x-2 font-medium">
165
+ {CHECK_STATUSES.map((st) => {
166
+ const c = summary.counts.get(st);
167
+ return c === undefined ? null : (
168
+ <span className={STYLES[st].cls} key={st}>
169
+ {c} {STYLES[st].label}
170
+ </span>
171
+ );
172
+ })}
173
+ {summary.durationOk && summary.totalMs > 0 ? (
174
+ <span className="font-mono tabular-nums">
175
+ {formatDuration(summary.totalMs)}
176
+ </span>
177
+ ) : null}
178
+ </span>
179
+ </CaptionBar>
180
+ );
181
+
182
+ export const Checks = defineComponent(
183
+ {
184
+ description:
185
+ "CI/検証ステータス一覧のコンテナ。<Check> を並べる。title はキャプション、context は対象チップ (commit SHA・PR 番号など)。status 別の件数と合計時間を自動集計",
186
+ schema: v.looseObject({
187
+ context: v.optional(v.string()),
188
+ title: v.optional(v.string()),
189
+ }),
190
+ },
191
+ ({ title, context, children }) => {
192
+ const summary = summarizeChecks(children);
193
+ const caption = nonEmpty(title) || nonEmpty(context) || summary.total > 0;
194
+ return (
195
+ <ListPanel>
196
+ {caption ? (
197
+ <ChecksCaption context={context} summary={summary} title={title} />
198
+ ) : null}
199
+ {children}
200
+ </ListPanel>
201
+ );
202
+ }
203
+ );
@@ -0,0 +1,44 @@
1
+ import { createContext, isValidElement, useContext, useMemo } from "react";
2
+ import type { ReactElement, ReactNode } from "react";
3
+
4
+ import { flattenChildren } from "../define.js";
5
+
6
+ export interface ChildIndex {
7
+ /** 1-based position among siblings; 0 when rendered outside an indexed container. */
8
+ n: number;
9
+ /** Whether this is the last sibling — used to hide trailing connectors. */
10
+ last: boolean;
11
+ }
12
+
13
+ const ChildIndexCtx = createContext<ChildIndex>({ last: true, n: 0 });
14
+
15
+ export const useChildIndex = (): ChildIndex => useContext(ChildIndexCtx);
16
+
17
+ const IndexedChild = ({
18
+ n,
19
+ last,
20
+ children,
21
+ }: ChildIndex & { children?: ReactNode }): ReactElement => {
22
+ const value = useMemo(() => ({ last, n }), [last, n]);
23
+ return (
24
+ <ChildIndexCtx.Provider value={value}>{children}</ChildIndexCtx.Provider>
25
+ );
26
+ };
27
+
28
+ /**
29
+ * Wrap each flattened child in a provider carrying its position, so child
30
+ * components (FlowStep, Finding, …) can render numbers and connectors without
31
+ * prop injection.
32
+ */
33
+ export const indexChildren = (children: ReactNode): ReactNode[] => {
34
+ const flat = flattenChildren(children);
35
+ return flat.map((child, i) => (
36
+ <IndexedChild
37
+ key={isValidElement(child) ? (child.key ?? i) : i}
38
+ last={i === flat.length - 1}
39
+ n={i + 1}
40
+ >
41
+ {child}
42
+ </IndexedChild>
43
+ ));
44
+ };
@@ -0,0 +1,43 @@
1
+ import { isValidElement } from "react";
2
+ import type { ReactElement, ReactNode } from "react";
3
+
4
+ import { flattenChildren } from "../define.js";
5
+ import { isRecord } from "../guards.js";
6
+
7
+ /**
8
+ * `n` is an element rendered by `type` — a string tag (`"ul"`, `"li"`) or a
9
+ * component reference (`Finding`, `Search`). Pairs with `flattenChildren`
10
+ * from define.ts to pick items out of a `children` tree.
11
+ */
12
+ export const isEl = <P extends { children?: ReactNode }>(
13
+ n: unknown,
14
+ type: unknown
15
+ ): n is ReactElement<P> => isValidElement<P>(n) && n.type === type;
16
+
17
+ /** Read a single prop off an element, or `undefined` for non-elements. */
18
+ export const propOf = (n: unknown, key: string): unknown =>
19
+ isValidElement(n) && isRecord(n.props) ? n.props[key] : undefined;
20
+
21
+ /**
22
+ * Tally `prop` across `children` elements rendered by `of` — the count
23
+ * summary behind `<Findings>`/`<Hypotheses>` headers. Values failing `is`
24
+ * bucket under `fallback`.
25
+ */
26
+ export const countByProp = <T extends string>(
27
+ children: ReactNode,
28
+ of: unknown,
29
+ prop: string,
30
+ is: (x: unknown) => x is T,
31
+ fallback: T
32
+ ): Map<T, number> => {
33
+ const counts = new Map<T, number>();
34
+ for (const node of flattenChildren(children)) {
35
+ if (!isEl(node, of)) {
36
+ continue;
37
+ }
38
+ const val = propOf(node, prop);
39
+ const key = is(val) ? val : fallback;
40
+ counts.set(key, (counts.get(key) ?? 0) + 1);
41
+ }
42
+ return counts;
43
+ };
@@ -0,0 +1,49 @@
1
+ import type { ReactElement } from "react";
2
+ import * as v from "valibot";
3
+
4
+ import { nonEmpty } from "../guards.js";
5
+ import { Due } from "./due.js";
6
+ import { Effort, EFFORT_SIZES } from "./effort.js";
7
+ import type { EffortSize } from "./effort.js";
8
+ import { Owner } from "./owner.js";
9
+ import { Priority, PRIORITY_LEVELS } from "./priority.js";
10
+ import type { PriorityLevel } from "./priority.js";
11
+
12
+ /**
13
+ * Schema entries matching ChipRow's props — spread into the schema of any
14
+ * component that renders a ChipRow so props and rendering stay in sync.
15
+ */
16
+ export const CHIP_PROPS = {
17
+ due: v.optional(v.string()),
18
+ effort: v.optional(v.picklist(EFFORT_SIZES)),
19
+ owner: v.optional(v.string()),
20
+ priority: v.optional(v.picklist(PRIORITY_LEVELS)),
21
+ } as const;
22
+
23
+ /**
24
+ * The `priority · effort · owner · due` chip row shared by task-like
25
+ * components (Step, BoardCard). Renders nothing when no chip prop is set —
26
+ * callers must not pre-check, so a lone chip can never leave an empty row.
27
+ */
28
+ export const ChipRow = (props: {
29
+ due?: string;
30
+ effort?: EffortSize;
31
+ owner?: string;
32
+ priority?: PriorityLevel;
33
+ }): ReactElement | null => {
34
+ const any =
35
+ props.priority !== undefined ||
36
+ props.effort !== undefined ||
37
+ nonEmpty(props.owner) ||
38
+ nonEmpty(props.due);
39
+ return any ? (
40
+ <div className="mt-2 flex flex-wrap items-center gap-1.5">
41
+ {props.priority === undefined ? null : (
42
+ <Priority level={props.priority} />
43
+ )}
44
+ {props.effort === undefined ? null : <Effort size={props.effort} />}
45
+ {nonEmpty(props.owner) ? <Owner name={props.owner} /> : null}
46
+ {nonEmpty(props.due) ? <Due date={props.due} /> : null}
47
+ </div>
48
+ ) : null;
49
+ };
package/src/ui/cmd.tsx ADDED
@@ -0,0 +1,27 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent, textOf } from "../define.js";
4
+ import { CodeChip, CopyButton } from "./bits.js";
5
+ import { Icon } from "./icon.js";
6
+
7
+ export const Cmd = defineComponent(
8
+ {
9
+ description:
10
+ "コマンドチップ。ターミナルアイコン + 等幅 + コピーボタン付き。rollout 手順などに",
11
+ schema: v.looseObject({}),
12
+ },
13
+ ({ children }) => (
14
+ <CodeChip>
15
+ <Icon
16
+ className="h-3.5 w-3.5 shrink-0 opacity-60"
17
+ name="lucide:terminal"
18
+ />
19
+ <span>{children}</span>
20
+ <CopyButton
21
+ className="-mr-0.5 opacity-40"
22
+ copy={textOf(children)}
23
+ title="Copy command"
24
+ />
25
+ </CodeChip>
26
+ )
27
+ );
@@ -0,0 +1,31 @@
1
+ import * as v from "valibot";
2
+
3
+ import { defineComponent } from "../define.js";
4
+ import { TRIM_CLS } from "./tones.js";
5
+
6
+ const COLS: Record<string, string> = {
7
+ "1": "",
8
+ "2": "sm:grid-cols-2",
9
+ "3": "sm:grid-cols-3",
10
+ "4": "sm:grid-cols-2 lg:grid-cols-4",
11
+ };
12
+
13
+ export const Columns = defineComponent(
14
+ {
15
+ description:
16
+ "横並びレイアウト。cols は 1|2|3|4 (デフォルト 2)。<Column> を子に取る",
17
+ schema: v.looseObject({
18
+ cols: v.optional(v.picklist(["1", "2", "3", "4"]), "2"),
19
+ }),
20
+ },
21
+ ({ cols, children }) => (
22
+ <div className={`my-6 grid gap-4 ${COLS[cols]}`}>{children}</div>
23
+ )
24
+ );
25
+
26
+ export const Column = defineComponent(
27
+ {
28
+ description: "<Columns> 内の1カラム",
29
+ },
30
+ ({ children }) => <div className={`min-w-0 ${TRIM_CLS}`}>{children}</div>
31
+ );