@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.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +2902 -0
- package/dist/components.d.mts +791 -0
- package/dist/components.mjs +2 -0
- package/dist/config-SI9IyFiC.mjs +184 -0
- package/dist/doc-context-CEqzMYKv.d.mts +568 -0
- package/dist/index.d.mts +20 -0
- package/dist/index.mjs +4 -0
- package/dist/ui-GKFD0mx5.mjs +15986 -0
- package/package.json +131 -0
- package/skill/SKILL.md +49 -0
- package/skill/references/components/charts.md +172 -0
- package/skill/references/components/document.md +98 -0
- package/skill/references/components/forms.md +35 -0
- package/skill/references/components/investigation.md +198 -0
- package/skill/references/components/layout.md +68 -0
- package/skill/references/components/output.md +158 -0
- package/skill/references/components/planning.md +155 -0
- package/skill/references/components/reports.md +252 -0
- package/skill/references/components/shadcn.md +19 -0
- package/skill/references/components.md +186 -0
- package/skill/references/extending.md +48 -0
- package/src/ask-sheet.ts +40 -0
- package/src/assets/css.ts +392 -0
- package/src/assets/scripts.ts +78 -0
- package/src/catalog.ts +287 -0
- package/src/cli.ts +178 -0
- package/src/client/doc-events.ts +1108 -0
- package/src/client/entry.ts +27 -0
- package/src/client-js.ts +47 -0
- package/src/component-map.ts +47 -0
- package/src/components/ui/accordion.tsx +77 -0
- package/src/components/ui/alert-dialog.tsx +185 -0
- package/src/components/ui/alert.tsx +76 -0
- package/src/components/ui/aspect-ratio.tsx +22 -0
- package/src/components/ui/attachment.tsx +208 -0
- package/src/components/ui/avatar.tsx +106 -0
- package/src/components/ui/badge.tsx +52 -0
- package/src/components/ui/breadcrumb.tsx +121 -0
- package/src/components/ui/bubble.tsx +128 -0
- package/src/components/ui/button-group.tsx +88 -0
- package/src/components/ui/button.tsx +58 -0
- package/src/components/ui/calendar.tsx +226 -0
- package/src/components/ui/card.tsx +102 -0
- package/src/components/ui/carousel.tsx +246 -0
- package/src/components/ui/chart.tsx +379 -0
- package/src/components/ui/checkbox.tsx +27 -0
- package/src/components/ui/collapsible.tsx +19 -0
- package/src/components/ui/combobox.tsx +298 -0
- package/src/components/ui/command.tsx +193 -0
- package/src/components/ui/context-menu.tsx +271 -0
- package/src/components/ui/dialog.tsx +159 -0
- package/src/components/ui/direction.tsx +4 -0
- package/src/components/ui/drawer.tsx +227 -0
- package/src/components/ui/dropdown-menu.tsx +269 -0
- package/src/components/ui/empty.tsx +104 -0
- package/src/components/ui/field.tsx +237 -0
- package/src/components/ui/fieldset.tsx +32 -0
- package/src/components/ui/frame.tsx +87 -0
- package/src/components/ui/hover-card.tsx +50 -0
- package/src/components/ui/input-group.tsx +159 -0
- package/src/components/ui/input-otp.tsx +83 -0
- package/src/components/ui/input.tsx +19 -0
- package/src/components/ui/item.tsx +202 -0
- package/src/components/ui/kbd.tsx +26 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/marker.tsx +71 -0
- package/src/components/ui/menubar.tsx +284 -0
- package/src/components/ui/message-scroller.tsx +128 -0
- package/src/components/ui/message.tsx +91 -0
- package/src/components/ui/meter.tsx +80 -0
- package/src/components/ui/native-select.tsx +64 -0
- package/src/components/ui/navigation-menu.tsx +170 -0
- package/src/components/ui/pagination.tsx +133 -0
- package/src/components/ui/popover.tsx +87 -0
- package/src/components/ui/progress.tsx +82 -0
- package/src/components/ui/questionnaire.tsx +328 -0
- package/src/components/ui/radio-group.tsx +35 -0
- package/src/components/ui/resizable.tsx +49 -0
- package/src/components/ui/scroll-area.tsx +50 -0
- package/src/components/ui/select.tsx +201 -0
- package/src/components/ui/separator.tsx +22 -0
- package/src/components/ui/sheet.tsx +135 -0
- package/src/components/ui/sidebar.tsx +730 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/slider.tsx +51 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/table.tsx +113 -0
- package/src/components/ui/tabs.tsx +82 -0
- package/src/components/ui/textarea.tsx +17 -0
- package/src/components/ui/toast.tsx +229 -0
- package/src/components/ui/toggle-group.tsx +87 -0
- package/src/components/ui/toggle.tsx +43 -0
- package/src/components/ui/tooltip.tsx +65 -0
- package/src/components.ts +78 -0
- package/src/config.ts +56 -0
- package/src/define.ts +122 -0
- package/src/doc-context.ts +20 -0
- package/src/editor.ts +94 -0
- package/src/format-error.ts +78 -0
- package/src/guards.ts +84 -0
- package/src/hooks/use-mobile.ts +21 -0
- package/src/html.ts +91 -0
- package/src/hydrate/export-index.ts +232 -0
- package/src/hydrate/import-scan.ts +169 -0
- package/src/hydrate/plugins.ts +118 -0
- package/src/hydrate/runtime-module.ts +145 -0
- package/src/hydrate-runtime.ts +67 -0
- package/src/hydrate.ts +147 -0
- package/src/index.ts +16 -0
- package/src/init.ts +57 -0
- package/src/langs.ts +148 -0
- package/src/lines.ts +53 -0
- package/src/load-user-module.ts +191 -0
- package/src/mdx.ts +248 -0
- package/src/paths.ts +36 -0
- package/src/rehype/shiki.ts +533 -0
- package/src/remark/alerts.ts +53 -0
- package/src/remark/ast.ts +96 -0
- package/src/remark/callouts.ts +25 -0
- package/src/remark/code-file.ts +85 -0
- package/src/remark/code-meta.ts +21 -0
- package/src/remark/directives.ts +170 -0
- package/src/remark/file-paths.ts +64 -0
- package/src/remark/headings.ts +131 -0
- package/src/remark/no-js.ts +40 -0
- package/src/render.ts +337 -0
- package/src/serve.ts +322 -0
- package/src/styles/globals.css +134 -0
- package/src/styles/shadcn.css +641 -0
- package/src/tailwind.ts +119 -0
- package/src/ui/approvals.tsx +76 -0
- package/src/ui/ask-question.tsx +386 -0
- package/src/ui/ask.tsx +206 -0
- package/src/ui/attrs.ts +51 -0
- package/src/ui/audit.tsx +139 -0
- package/src/ui/bar-chart.tsx +334 -0
- package/src/ui/benchmarks.tsx +143 -0
- package/src/ui/bits.tsx +537 -0
- package/src/ui/board.tsx +173 -0
- package/src/ui/bridge.tsx +207 -0
- package/src/ui/bumps.tsx +178 -0
- package/src/ui/callout.tsx +106 -0
- package/src/ui/changes.tsx +89 -0
- package/src/ui/chart-bits.tsx +52 -0
- package/src/ui/chart.ts +577 -0
- package/src/ui/checks.tsx +203 -0
- package/src/ui/child-index.tsx +44 -0
- package/src/ui/children.ts +43 -0
- package/src/ui/chips.tsx +49 -0
- package/src/ui/cmd.tsx +27 -0
- package/src/ui/columns.tsx +31 -0
- package/src/ui/comments.tsx +544 -0
- package/src/ui/compare.tsx +67 -0
- package/src/ui/decision.tsx +79 -0
- package/src/ui/deps.tsx +78 -0
- package/src/ui/details.tsx +43 -0
- package/src/ui/diff-parse.ts +307 -0
- package/src/ui/diff.tsx +458 -0
- package/src/ui/diffstat.tsx +58 -0
- package/src/ui/due.tsx +65 -0
- package/src/ui/effort.tsx +29 -0
- package/src/ui/endpoints.tsx +118 -0
- package/src/ui/envvars.tsx +96 -0
- package/src/ui/figure.tsx +37 -0
- package/src/ui/file-icon.ts +1020 -0
- package/src/ui/file-link.ts +32 -0
- package/src/ui/file-ref.tsx +39 -0
- package/src/ui/files.tsx +112 -0
- package/src/ui/findings.tsx +85 -0
- package/src/ui/flow.tsx +73 -0
- package/src/ui/funnel.tsx +121 -0
- package/src/ui/gantt.tsx +443 -0
- package/src/ui/gauges.tsx +135 -0
- package/src/ui/glossary.tsx +29 -0
- package/src/ui/graph-layout.ts +149 -0
- package/src/ui/graph-specs.tsx +102 -0
- package/src/ui/graph.tsx +278 -0
- package/src/ui/grid.tsx +119 -0
- package/src/ui/hypothesis.tsx +94 -0
- package/src/ui/icon.tsx +80 -0
- package/src/ui/incident.tsx +130 -0
- package/src/ui/index.ts +342 -0
- package/src/ui/ins-del.tsx +41 -0
- package/src/ui/json.tsx +190 -0
- package/src/ui/layout.ts +9 -0
- package/src/ui/line-chart.tsx +251 -0
- package/src/ui/matrix.tsx +208 -0
- package/src/ui/meta.tsx +73 -0
- package/src/ui/option.tsx +64 -0
- package/src/ui/owner.tsx +40 -0
- package/src/ui/packages.tsx +104 -0
- package/src/ui/pathway.tsx +96 -0
- package/src/ui/phase.tsx +41 -0
- package/src/ui/pie-chart.tsx +170 -0
- package/src/ui/plan.tsx +77 -0
- package/src/ui/pre.tsx +175 -0
- package/src/ui/priority.tsx +51 -0
- package/src/ui/props.tsx +77 -0
- package/src/ui/quadrant.tsx +172 -0
- package/src/ui/radar.tsx +200 -0
- package/src/ui/ref.tsx +160 -0
- package/src/ui/release.tsx +178 -0
- package/src/ui/req.tsx +42 -0
- package/src/ui/review.tsx +133 -0
- package/src/ui/risk.tsx +67 -0
- package/src/ui/sankey.tsx +287 -0
- package/src/ui/scatter.tsx +237 -0
- package/src/ui/schema.tsx +113 -0
- package/src/ui/score.tsx +105 -0
- package/src/ui/search.tsx +120 -0
- package/src/ui/series.tsx +38 -0
- package/src/ui/severity.tsx +69 -0
- package/src/ui/shadcn.tsx +216 -0
- package/src/ui/spark.tsx +86 -0
- package/src/ui/stack.tsx +57 -0
- package/src/ui/stats.tsx +63 -0
- package/src/ui/status-badge.tsx +66 -0
- package/src/ui/statuspage.tsx +238 -0
- package/src/ui/steps.tsx +74 -0
- package/src/ui/summary.tsx +41 -0
- package/src/ui/symbol-ref.tsx +73 -0
- package/src/ui/terminal.tsx +117 -0
- package/src/ui/tests.tsx +218 -0
- package/src/ui/timeline.tsx +63 -0
- package/src/ui/toc.tsx +56 -0
- package/src/ui/tones.ts +187 -0
- package/src/ui/trace.tsx +69 -0
- package/src/ui/tree.tsx +281 -0
- package/src/ui/treemap.tsx +128 -0
- package/src/ui/venn.tsx +258 -0
- package/src/ui/verdict.tsx +78 -0
- package/src/ui/waterfall.tsx +142 -0
|
@@ -0,0 +1,207 @@
|
|
|
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 { fmtNum, niceBounds } from "./chart.js";
|
|
10
|
+
import { isEl } from "./children.js";
|
|
11
|
+
import { RAIL_BG_CLS, TEXT, TEXT_MICRO, TEXT_SUB, TONE_TEXT } from "./tones.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Bridge / running-total waterfall — a start total carried to an end total
|
|
15
|
+
* by signed <Delta> contributions. `total` pillars re-anchor the running
|
|
16
|
+
* sum at their value; deltas float from the previous sum to the new one.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const DELTA_SCHEMA = v.looseObject({
|
|
20
|
+
name: v.string(),
|
|
21
|
+
note: v.optional(v.string()),
|
|
22
|
+
total: BOOLISH_PROP,
|
|
23
|
+
value: NUMISH,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
type DeltaSpec = v.InferOutput<typeof DELTA_SCHEMA>;
|
|
27
|
+
|
|
28
|
+
export const Delta = defineComponent(
|
|
29
|
+
{
|
|
30
|
+
description:
|
|
31
|
+
"ブリッジの1増減。<Bridge> の子。name/value は必須 (value は符号付き)、total で累計の柱 (0→value)、note で補足",
|
|
32
|
+
schema: DELTA_SCHEMA,
|
|
33
|
+
},
|
|
34
|
+
// Standalone use: a `name ±value` chip so misplaced deltas still render.
|
|
35
|
+
({ name, value, total }): ReactElement => (
|
|
36
|
+
<span className={`font-mono ${CODE_CHIP_CLS}`}>
|
|
37
|
+
{name}
|
|
38
|
+
<span className="opacity-70">
|
|
39
|
+
{attrTrue(total) ? "=" : ""}
|
|
40
|
+
{value}
|
|
41
|
+
</span>
|
|
42
|
+
</span>
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
interface Step {
|
|
47
|
+
/** Column top (larger of from/to). */
|
|
48
|
+
high: number;
|
|
49
|
+
/** Column bottom (smaller of from/to). */
|
|
50
|
+
low: number;
|
|
51
|
+
n: number;
|
|
52
|
+
spec: DeltaSpec;
|
|
53
|
+
/** Running total after this step. */
|
|
54
|
+
sum: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Bar color — neutral for totals, emerald/red for up/down deltas. */
|
|
58
|
+
const barCls = (isTotal: boolean, positive: boolean): string => {
|
|
59
|
+
if (isTotal) {
|
|
60
|
+
return "bg-neutral-400";
|
|
61
|
+
}
|
|
62
|
+
return positive ? "bg-emerald-500" : "bg-red-500";
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Value label color matching barCls. */
|
|
66
|
+
const valueCls = (isTotal: boolean, positive: boolean): string => {
|
|
67
|
+
if (isTotal) {
|
|
68
|
+
return TEXT.muted;
|
|
69
|
+
}
|
|
70
|
+
return positive ? TONE_TEXT.emerald : TONE_TEXT.red;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const collectDeltas = (
|
|
74
|
+
children: ReactNode
|
|
75
|
+
): { rest: ReactNode[]; steps: Step[] } => {
|
|
76
|
+
const steps: Step[] = [];
|
|
77
|
+
const rest: ReactNode[] = [];
|
|
78
|
+
let sum = 0;
|
|
79
|
+
for (const child of flattenChildren(children)) {
|
|
80
|
+
if (isEl(child, Delta)) {
|
|
81
|
+
const spec = parseProps(DELTA_SCHEMA, child.props, "Delta");
|
|
82
|
+
const n = numOf(spec.value) ?? 0;
|
|
83
|
+
const isTotal = attrTrue(spec.total);
|
|
84
|
+
const low = isTotal ? Math.min(0, n) : Math.min(sum, sum + n);
|
|
85
|
+
const high = isTotal ? Math.max(0, n) : Math.max(sum, sum + n);
|
|
86
|
+
sum = isTotal ? n : sum + n;
|
|
87
|
+
steps.push({ high, low, n, spec, sum });
|
|
88
|
+
} else {
|
|
89
|
+
rest.push(child);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { rest, steps };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const Bridge = defineComponent(
|
|
96
|
+
{
|
|
97
|
+
description:
|
|
98
|
+
"ブリッジコンテナ (開始値→増減→終了値の waterfall チャート)。<Delta name value> を並べる。value は符号付き、total で絶対値の柱。unit は値の単位",
|
|
99
|
+
schema: v.looseObject({
|
|
100
|
+
title: v.optional(v.string()),
|
|
101
|
+
unit: v.optional(v.string()),
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
104
|
+
({ title, unit, children }) => {
|
|
105
|
+
const { steps, rest } = collectDeltas(children);
|
|
106
|
+
const ext = niceBounds(
|
|
107
|
+
Math.min(0, ...steps.map((s) => s.low)),
|
|
108
|
+
Math.max(1, ...steps.map((s) => s.high))
|
|
109
|
+
);
|
|
110
|
+
const span = ext.max - ext.min || 1;
|
|
111
|
+
const pctOf = (val: number): number => ((val - ext.min) / span) * 100;
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<ChartPanel
|
|
115
|
+
figure={
|
|
116
|
+
nonEmpty(unit)
|
|
117
|
+
? `${fmtNum(ext.min)}–${fmtNum(ext.max)}${unit}`
|
|
118
|
+
: undefined
|
|
119
|
+
}
|
|
120
|
+
icon="lucide:chart-column-big"
|
|
121
|
+
title={title}
|
|
122
|
+
>
|
|
123
|
+
<div className="px-4 py-3">
|
|
124
|
+
<div className="relative">
|
|
125
|
+
<div aria-hidden className="absolute inset-0 h-36">
|
|
126
|
+
{ext.ticks
|
|
127
|
+
.filter((t) => t > ext.min && t < ext.max)
|
|
128
|
+
.map((t) => (
|
|
129
|
+
<div
|
|
130
|
+
className={`absolute h-px w-full ${RAIL_BG_CLS} ${t === 0 ? "" : "opacity-50"}`}
|
|
131
|
+
key={t}
|
|
132
|
+
style={{ bottom: `${pctOf(t)}%` }}
|
|
133
|
+
/>
|
|
134
|
+
))}
|
|
135
|
+
</div>
|
|
136
|
+
<div className="relative flex h-36 items-stretch gap-x-3">
|
|
137
|
+
{steps.map((s, i) => {
|
|
138
|
+
const isTotal = attrTrue(s.spec.total);
|
|
139
|
+
const cls = barCls(isTotal, s.n >= 0);
|
|
140
|
+
const prevSum = i === 0 ? undefined : steps[i - 1]?.sum;
|
|
141
|
+
return (
|
|
142
|
+
<div className="min-w-0 flex-1" key={i}>
|
|
143
|
+
<div className="relative h-full">
|
|
144
|
+
<div
|
|
145
|
+
className={`absolute w-full rounded-sm ${cls}`}
|
|
146
|
+
style={{
|
|
147
|
+
bottom: `${pctOf(s.low)}%`,
|
|
148
|
+
height: `${Math.max(1, pctOf(s.high) - pctOf(s.low))}%`,
|
|
149
|
+
}}
|
|
150
|
+
title={`${s.spec.name} · ${fmtNum(s.n)}${unit ?? ""} → ${fmtNum(s.sum)}`}
|
|
151
|
+
/>
|
|
152
|
+
{/* connector at the previous column's running total */}
|
|
153
|
+
{prevSum === undefined ? null : (
|
|
154
|
+
<div
|
|
155
|
+
aria-hidden
|
|
156
|
+
className={`absolute -left-3 h-px w-3 ${RAIL_BG_CLS}`}
|
|
157
|
+
style={{ bottom: `${pctOf(prevSum)}%` }}
|
|
158
|
+
/>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
})}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
<div className="flex gap-x-3">
|
|
167
|
+
{steps.map((s, i) => {
|
|
168
|
+
const isTotal = attrTrue(s.spec.total);
|
|
169
|
+
const positive = s.n >= 0;
|
|
170
|
+
return (
|
|
171
|
+
<div className="min-w-0 flex-1" key={i}>
|
|
172
|
+
<div className="mt-1 truncate text-center text-xs">
|
|
173
|
+
{s.spec.name}
|
|
174
|
+
</div>
|
|
175
|
+
<div
|
|
176
|
+
className={`truncate text-center font-mono ${TEXT_MICRO} tabular-nums ${valueCls(isTotal, positive)}`}
|
|
177
|
+
>
|
|
178
|
+
{isTotal
|
|
179
|
+
? fmtNum(s.n)
|
|
180
|
+
: `${positive ? "+" : ""}${fmtNum(s.n)}`}
|
|
181
|
+
</div>
|
|
182
|
+
{nonEmpty(s.spec.note) ? (
|
|
183
|
+
<div
|
|
184
|
+
className={`truncate text-center ${TEXT_SUB} ${TEXT.faint}`}
|
|
185
|
+
>
|
|
186
|
+
{s.spec.note}
|
|
187
|
+
</div>
|
|
188
|
+
) : null}
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
})}
|
|
192
|
+
</div>
|
|
193
|
+
<div
|
|
194
|
+
className={`mt-1 flex justify-between font-mono ${TEXT_MICRO} ${TEXT.faint} tabular-nums`}
|
|
195
|
+
>
|
|
196
|
+
<span>{fmtNum(ext.min)}</span>
|
|
197
|
+
<span>
|
|
198
|
+
{fmtNum(ext.max)}
|
|
199
|
+
{unit}
|
|
200
|
+
</span>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
{rest.length > 0 ? <div className="px-4 pb-3">{rest}</div> : null}
|
|
204
|
+
</ChartPanel>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
);
|
package/src/ui/bumps.tsx
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
import { defineComponent, flattenChildren } from "../define.js";
|
|
4
|
+
import { isOneOf, nonEmpty, safeHref } from "../guards.js";
|
|
5
|
+
import { attrTrue, BOOLISH_PROP, NUMISH } from "./attrs.js";
|
|
6
|
+
import { CaptionBar, ListPanel, ListRow, MaybeLink, RowNote } from "./bits.js";
|
|
7
|
+
import { isEl, propOf } from "./children.js";
|
|
8
|
+
import { Icon } from "./icon.js";
|
|
9
|
+
import {
|
|
10
|
+
CAPTION_TITLE_CLS,
|
|
11
|
+
MINI_CHIP_CLS,
|
|
12
|
+
TEXT,
|
|
13
|
+
TONE,
|
|
14
|
+
TRIM_CLS,
|
|
15
|
+
} from "./tones.js";
|
|
16
|
+
|
|
17
|
+
export const BUMP_KINDS = ["major", "minor", "patch"] as const;
|
|
18
|
+
export type BumpKind = (typeof BUMP_KINDS)[number];
|
|
19
|
+
|
|
20
|
+
const isBumpKind = isOneOf(BUMP_KINDS);
|
|
21
|
+
|
|
22
|
+
const KIND_STYLES: Record<BumpKind, { cls: string; label: string }> = {
|
|
23
|
+
major: { cls: TONE.red, label: "major" },
|
|
24
|
+
minor: { cls: TONE.amber, label: "minor" },
|
|
25
|
+
patch: { cls: TONE.neutral, label: "patch" },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** `"1.2.3"` → `[1, 2, 3]` — ignores pre-release/build suffixes. */
|
|
29
|
+
const semverParts = (x: unknown): number[] | undefined => {
|
|
30
|
+
if (typeof x !== "string" && typeof x !== "number") {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const m = /^v?(?<core>\d+(?:\.\d+){0,2})/u.exec(String(x).trim());
|
|
34
|
+
if (m?.groups === undefined) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const parts = m.groups.core.split(".").map(Number);
|
|
38
|
+
while (parts.length < 3) {
|
|
39
|
+
parts.push(0);
|
|
40
|
+
}
|
|
41
|
+
return parts.every(Number.isFinite) ? parts : undefined;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Compare `from`/`to` semver triples → the highest differing segment. */
|
|
45
|
+
const detectKind = (from: unknown, to: unknown): BumpKind | undefined => {
|
|
46
|
+
const a = semverParts(from);
|
|
47
|
+
const b = semverParts(to);
|
|
48
|
+
if (a === undefined || b === undefined) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
for (const [i, kind] of [
|
|
52
|
+
[0, "major"],
|
|
53
|
+
[1, "minor"],
|
|
54
|
+
[2, "patch"],
|
|
55
|
+
] as const) {
|
|
56
|
+
if (a[i] !== b[i]) {
|
|
57
|
+
return kind;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return "patch";
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Bump = defineComponent(
|
|
64
|
+
{
|
|
65
|
+
description:
|
|
66
|
+
"依存バージョンアップ1行。name は必須、from → to を表示し major|minor|patch を自動判定 (kind で上書き可)。breaking で警告チップ、cves に修正される脆弱性数、href で変更履歴リンク。children はメモ",
|
|
67
|
+
schema: v.looseObject({
|
|
68
|
+
breaking: BOOLISH_PROP,
|
|
69
|
+
cves: v.optional(NUMISH),
|
|
70
|
+
from: v.optional(v.string()),
|
|
71
|
+
href: v.optional(v.string()),
|
|
72
|
+
kind: v.optional(v.picklist(BUMP_KINDS)),
|
|
73
|
+
name: v.string(),
|
|
74
|
+
note: v.optional(v.string()),
|
|
75
|
+
to: v.optional(v.string()),
|
|
76
|
+
}),
|
|
77
|
+
},
|
|
78
|
+
({ name, from, to, kind, breaking, cves, note, href, children }) => {
|
|
79
|
+
const k = isBumpKind(kind) ? kind : detectKind(from, to);
|
|
80
|
+
const link = safeHref(href);
|
|
81
|
+
return (
|
|
82
|
+
<ListRow>
|
|
83
|
+
<Icon
|
|
84
|
+
className={`h-3.5 w-3.5 shrink-0 self-center ${TEXT.faint}`}
|
|
85
|
+
name="lucide:package"
|
|
86
|
+
/>
|
|
87
|
+
<MaybeLink
|
|
88
|
+
className={`font-mono text-[0.85em] font-medium ${TEXT.code} no-underline hover:underline`}
|
|
89
|
+
href={link}
|
|
90
|
+
>
|
|
91
|
+
{name}
|
|
92
|
+
</MaybeLink>
|
|
93
|
+
{nonEmpty(from) || nonEmpty(to) ? (
|
|
94
|
+
<span className="inline-flex items-center gap-1 font-mono text-xs">
|
|
95
|
+
<span className={TEXT.muted}>{from ?? "?"}</span>
|
|
96
|
+
<Icon
|
|
97
|
+
className={`h-3 w-3 ${TEXT.faint}`}
|
|
98
|
+
name="lucide:arrow-right"
|
|
99
|
+
/>
|
|
100
|
+
<span className={TEXT.code}>{to ?? "?"}</span>
|
|
101
|
+
</span>
|
|
102
|
+
) : null}
|
|
103
|
+
{k === undefined ? null : (
|
|
104
|
+
<span className={`${MINI_CHIP_CLS} ${KIND_STYLES[k].cls}`}>
|
|
105
|
+
{KIND_STYLES[k].label}
|
|
106
|
+
</span>
|
|
107
|
+
)}
|
|
108
|
+
{attrTrue(breaking) ? (
|
|
109
|
+
<span className={`${MINI_CHIP_CLS} ${TONE.orange}`}>
|
|
110
|
+
<Icon className="h-2.5 w-2.5" name="lucide:triangle-alert" />
|
|
111
|
+
breaking
|
|
112
|
+
</span>
|
|
113
|
+
) : null}
|
|
114
|
+
{cves === undefined || cves === "" ? null : (
|
|
115
|
+
<span className={`${MINI_CHIP_CLS} ${TONE.emerald}`}>
|
|
116
|
+
<Icon className="h-2.5 w-2.5" name="lucide:shield-check" />
|
|
117
|
+
{cves} {String(cves) === "1" ? "CVE" : "CVEs"}
|
|
118
|
+
</span>
|
|
119
|
+
)}
|
|
120
|
+
{nonEmpty(note) ? (
|
|
121
|
+
<span className={`text-sm ${TEXT.muted}`}>{note}</span>
|
|
122
|
+
) : null}
|
|
123
|
+
<RowNote className={TRIM_CLS}>{children}</RowNote>
|
|
124
|
+
</ListRow>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
export const Bumps = defineComponent(
|
|
130
|
+
{
|
|
131
|
+
description:
|
|
132
|
+
"依存バージョンアップ一覧のコンテナ。<Bump> を並べ、major|minor|patch 別の件数を自動集計。title はキャプション",
|
|
133
|
+
schema: v.looseObject({
|
|
134
|
+
title: v.optional(v.string()),
|
|
135
|
+
}),
|
|
136
|
+
},
|
|
137
|
+
({ title, children }) => {
|
|
138
|
+
const counts = new Map<BumpKind, number>();
|
|
139
|
+
for (const node of flattenChildren(children)) {
|
|
140
|
+
if (!isEl(node, Bump)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const k = propOf(node, "kind");
|
|
144
|
+
const kind = isBumpKind(k)
|
|
145
|
+
? k
|
|
146
|
+
: detectKind(propOf(node, "from"), propOf(node, "to"));
|
|
147
|
+
if (kind !== undefined) {
|
|
148
|
+
counts.set(kind, (counts.get(kind) ?? 0) + 1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const total = [...counts.values()].reduce((a, b) => a + b, 0);
|
|
152
|
+
const caption = nonEmpty(title) || total > 0;
|
|
153
|
+
return (
|
|
154
|
+
<ListPanel>
|
|
155
|
+
{caption ? (
|
|
156
|
+
<CaptionBar className={CAPTION_TITLE_CLS}>
|
|
157
|
+
<Icon className="h-3.5 w-3.5" name="lucide:package-open" />
|
|
158
|
+
{nonEmpty(title) ? title : "Dependency upgrades"}
|
|
159
|
+
<span className="ml-auto flex items-center gap-x-2 font-medium">
|
|
160
|
+
{BUMP_KINDS.map((k) => {
|
|
161
|
+
const c = counts.get(k);
|
|
162
|
+
return c === undefined ? null : (
|
|
163
|
+
<span
|
|
164
|
+
className={`${MINI_CHIP_CLS} ${KIND_STYLES[k].cls}`}
|
|
165
|
+
key={k}
|
|
166
|
+
>
|
|
167
|
+
{c} {k}
|
|
168
|
+
</span>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
</span>
|
|
172
|
+
</CaptionBar>
|
|
173
|
+
) : null}
|
|
174
|
+
{children}
|
|
175
|
+
</ListPanel>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
import { defineComponent } from "../define.js";
|
|
4
|
+
import { TrimBody } from "./bits.js";
|
|
5
|
+
import { Icon } from "./icon.js";
|
|
6
|
+
import { EDGE_PANEL_CLS } from "./tones.js";
|
|
7
|
+
|
|
8
|
+
export const CALLOUT_KINDS = [
|
|
9
|
+
"note",
|
|
10
|
+
"tip",
|
|
11
|
+
"important",
|
|
12
|
+
"warning",
|
|
13
|
+
"caution",
|
|
14
|
+
"danger",
|
|
15
|
+
"decision",
|
|
16
|
+
"goal",
|
|
17
|
+
"nongoal",
|
|
18
|
+
"question",
|
|
19
|
+
"answer",
|
|
20
|
+
] as const;
|
|
21
|
+
export type CalloutKind = (typeof CALLOUT_KINDS)[number];
|
|
22
|
+
|
|
23
|
+
const KINDS: Record<CalloutKind, { label: string; cls: string; icon: string }> =
|
|
24
|
+
{
|
|
25
|
+
answer: {
|
|
26
|
+
cls: "border-cyan-500 bg-cyan-50 text-cyan-950 dark:bg-cyan-950/40 dark:text-cyan-100",
|
|
27
|
+
icon: "lucide:message-circle-check",
|
|
28
|
+
label: "Answer",
|
|
29
|
+
},
|
|
30
|
+
caution: {
|
|
31
|
+
cls: "border-orange-500 bg-orange-50 text-orange-950 dark:bg-orange-950/40 dark:text-orange-100",
|
|
32
|
+
icon: "lucide:octagon-alert",
|
|
33
|
+
label: "Caution",
|
|
34
|
+
},
|
|
35
|
+
danger: {
|
|
36
|
+
cls: "border-red-600 bg-red-50 text-red-950 dark:bg-red-950/40 dark:text-red-100",
|
|
37
|
+
icon: "lucide:octagon-x",
|
|
38
|
+
label: "Danger",
|
|
39
|
+
},
|
|
40
|
+
decision: {
|
|
41
|
+
cls: "border-indigo-500 bg-indigo-50 text-indigo-950 dark:bg-indigo-950/40 dark:text-indigo-100",
|
|
42
|
+
icon: "lucide:scale",
|
|
43
|
+
label: "Decision",
|
|
44
|
+
},
|
|
45
|
+
goal: {
|
|
46
|
+
cls: "border-teal-500 bg-teal-50 text-teal-950 dark:bg-teal-950/40 dark:text-teal-100",
|
|
47
|
+
icon: "lucide:target",
|
|
48
|
+
label: "Goal",
|
|
49
|
+
},
|
|
50
|
+
important: {
|
|
51
|
+
cls: "border-violet-500 bg-violet-50 text-violet-950 dark:bg-violet-950/40 dark:text-violet-100",
|
|
52
|
+
icon: "lucide:message-square-warning",
|
|
53
|
+
label: "Important",
|
|
54
|
+
},
|
|
55
|
+
nongoal: {
|
|
56
|
+
cls: "border-neutral-400 bg-neutral-50 text-neutral-800 dark:bg-neutral-900/40 dark:text-neutral-200",
|
|
57
|
+
icon: "lucide:circle-slash",
|
|
58
|
+
label: "Non-goal",
|
|
59
|
+
},
|
|
60
|
+
note: {
|
|
61
|
+
cls: "border-sky-500 bg-sky-50 text-sky-950 dark:bg-sky-950/40 dark:text-sky-100",
|
|
62
|
+
icon: "lucide:info",
|
|
63
|
+
label: "Note",
|
|
64
|
+
},
|
|
65
|
+
question: {
|
|
66
|
+
cls: "border-fuchsia-500 bg-fuchsia-50 text-fuchsia-950 dark:bg-fuchsia-950/40 dark:text-fuchsia-100",
|
|
67
|
+
icon: "lucide:circle-question-mark",
|
|
68
|
+
label: "Question",
|
|
69
|
+
},
|
|
70
|
+
tip: {
|
|
71
|
+
cls: "border-emerald-500 bg-emerald-50 text-emerald-950 dark:bg-emerald-950/40 dark:text-emerald-100",
|
|
72
|
+
icon: "lucide:lightbulb",
|
|
73
|
+
label: "Tip",
|
|
74
|
+
},
|
|
75
|
+
warning: {
|
|
76
|
+
cls: "border-amber-500 bg-amber-50 text-amber-950 dark:bg-amber-950/40 dark:text-amber-100",
|
|
77
|
+
icon: "lucide:triangle-alert",
|
|
78
|
+
label: "Warning",
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const Callout = defineComponent(
|
|
83
|
+
{
|
|
84
|
+
description:
|
|
85
|
+
"強調ブロック。`:::note` / `> [!NOTE]` などの規約からも生成される",
|
|
86
|
+
schema: v.looseObject({
|
|
87
|
+
kind: v.optional(v.picklist(CALLOUT_KINDS), "note"),
|
|
88
|
+
title: v.optional(v.string()),
|
|
89
|
+
}),
|
|
90
|
+
},
|
|
91
|
+
({ kind, title, children }) => {
|
|
92
|
+
const k = KINDS[kind];
|
|
93
|
+
return (
|
|
94
|
+
<aside className={`${EDGE_PANEL_CLS} ${k.cls}`}>
|
|
95
|
+
<div className="flex items-center gap-1.5 font-semibold">
|
|
96
|
+
<Icon className="h-4 w-4 shrink-0" name={k.icon} />
|
|
97
|
+
<span>{k.label}</span>
|
|
98
|
+
{title !== undefined && title !== "" ? (
|
|
99
|
+
<span className="font-normal opacity-75">{title}</span>
|
|
100
|
+
) : null}
|
|
101
|
+
</div>
|
|
102
|
+
<TrimBody className="mt-1.5">{children}</TrimBody>
|
|
103
|
+
</aside>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
import { defineComponent } from "../define.js";
|
|
4
|
+
import { nonEmpty } from "../guards.js";
|
|
5
|
+
import {
|
|
6
|
+
ListPanel,
|
|
7
|
+
ListRow,
|
|
8
|
+
MaybeLink,
|
|
9
|
+
RowIcon,
|
|
10
|
+
RowNote,
|
|
11
|
+
Tag,
|
|
12
|
+
} from "./bits.js";
|
|
13
|
+
import { fileIcon } from "./file-icon.js";
|
|
14
|
+
import { useFileLink } from "./file-link.js";
|
|
15
|
+
import { LINK_CLS, MONO_CLS, TEXT, TONE_TEXT } from "./tones.js";
|
|
16
|
+
|
|
17
|
+
export const CHANGE_KINDS = ["add", "modify", "delete", "rename"] as const;
|
|
18
|
+
export type ChangeKind = (typeof CHANGE_KINDS)[number];
|
|
19
|
+
|
|
20
|
+
const KINDS: Record<ChangeKind, { cls: string; icon: string; label: string }> =
|
|
21
|
+
{
|
|
22
|
+
add: {
|
|
23
|
+
cls: TONE_TEXT.emerald,
|
|
24
|
+
icon: "lucide:file-plus",
|
|
25
|
+
label: "Add",
|
|
26
|
+
},
|
|
27
|
+
delete: {
|
|
28
|
+
cls: TONE_TEXT.red,
|
|
29
|
+
icon: "lucide:file-x",
|
|
30
|
+
label: "Delete",
|
|
31
|
+
},
|
|
32
|
+
modify: {
|
|
33
|
+
cls: TONE_TEXT.sky,
|
|
34
|
+
icon: "lucide:file-pen",
|
|
35
|
+
label: "Modify",
|
|
36
|
+
},
|
|
37
|
+
rename: {
|
|
38
|
+
cls: TONE_TEXT.violet,
|
|
39
|
+
icon: "lucide:file-symlink",
|
|
40
|
+
label: "Rename",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const Changes = defineComponent(
|
|
45
|
+
{
|
|
46
|
+
description: "変更ファイル一覧のコンテナ。<Change> を並べる",
|
|
47
|
+
},
|
|
48
|
+
({ children }) => <ListPanel as="div">{children}</ListPanel>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
export const Change = defineComponent(
|
|
52
|
+
{
|
|
53
|
+
description:
|
|
54
|
+
"変更ファイル1行。kind は add|modify|delete|rename。rename 時は to に変更後パス。実在ファイルはエディタリンク(既定 vscode://)になり、href で上書き可。children は注記",
|
|
55
|
+
schema: v.looseObject({
|
|
56
|
+
href: v.optional(v.string()),
|
|
57
|
+
kind: v.optional(v.picklist(CHANGE_KINDS), "modify"),
|
|
58
|
+
path: v.string(),
|
|
59
|
+
to: v.optional(v.string()),
|
|
60
|
+
}),
|
|
61
|
+
},
|
|
62
|
+
({ kind, path, to, href, children }) => {
|
|
63
|
+
const k = KINDS[kind];
|
|
64
|
+
const link = useFileLink(nonEmpty(to) ? to : path, undefined, href);
|
|
65
|
+
const label = (
|
|
66
|
+
<code className={MONO_CLS}>
|
|
67
|
+
{path}
|
|
68
|
+
{nonEmpty(to) ? (
|
|
69
|
+
<span className={TEXT.faint}>
|
|
70
|
+
{" → "}
|
|
71
|
+
{to}
|
|
72
|
+
</span>
|
|
73
|
+
) : null}
|
|
74
|
+
</code>
|
|
75
|
+
);
|
|
76
|
+
return (
|
|
77
|
+
<ListRow>
|
|
78
|
+
<Tag className={k.cls} icon={k.icon}>
|
|
79
|
+
{k.label}
|
|
80
|
+
</Tag>
|
|
81
|
+
<RowIcon name={fileIcon(nonEmpty(to) ? to : path)} />
|
|
82
|
+
<MaybeLink className={LINK_CLS} href={link}>
|
|
83
|
+
{label}
|
|
84
|
+
</MaybeLink>
|
|
85
|
+
<RowNote>{children}</RowNote>
|
|
86
|
+
</ListRow>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { CaptionBar, Panel } from "./bits.js";
|
|
4
|
+
import type { SeriesStyle } from "./chart.js";
|
|
5
|
+
import { Icon } from "./icon.js";
|
|
6
|
+
import { CAPTION_TITLE_CLS, MONO_NUM_CLS, TEXT, TEXT_MICRO } from "./tones.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Shared chart furniture — the `Panel` + `CaptionBar` frame every chart
|
|
10
|
+
* panel uses, and the legend row mapping series names to palette swatches.
|
|
11
|
+
* Layout math lives in `chart.ts`; this file holds the JSX half.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Titled chart frame — icon + title + an optional right-side figure. */
|
|
15
|
+
export const ChartPanel = (props: {
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
/** Right-aligned caption figure (range total, max, …). */
|
|
18
|
+
figure?: ReactNode;
|
|
19
|
+
icon: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
}): ReactElement => (
|
|
22
|
+
<Panel>
|
|
23
|
+
{props.title === undefined || props.title === "" ? null : (
|
|
24
|
+
<CaptionBar className={CAPTION_TITLE_CLS}>
|
|
25
|
+
<Icon className="h-3.5 w-3.5" name={props.icon} />
|
|
26
|
+
<span className="min-w-0 flex-1 truncate">{props.title}</span>
|
|
27
|
+
{props.figure === undefined ? null : (
|
|
28
|
+
<span className={MONO_NUM_CLS}>{props.figure}</span>
|
|
29
|
+
)}
|
|
30
|
+
</CaptionBar>
|
|
31
|
+
)}
|
|
32
|
+
{props.children}
|
|
33
|
+
</Panel>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
/** Legend row — colored square + series/item name, wrapping centered. */
|
|
37
|
+
export const ChartLegend = (props: {
|
|
38
|
+
items: { name: string; style: SeriesStyle }[];
|
|
39
|
+
}): ReactElement | null =>
|
|
40
|
+
props.items.length === 0 ? null : (
|
|
41
|
+
<div className="flex flex-wrap items-center justify-center gap-x-4 gap-y-1 px-4 pb-3">
|
|
42
|
+
{props.items.map((item) => (
|
|
43
|
+
<span
|
|
44
|
+
className={`inline-flex items-center gap-1.5 ${TEXT_MICRO} ${TEXT.muted}`}
|
|
45
|
+
key={item.name}
|
|
46
|
+
>
|
|
47
|
+
<span className={`h-2 w-2 rounded-[2px] ${item.style.bg}`} />
|
|
48
|
+
{item.name}
|
|
49
|
+
</span>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
);
|