@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
package/src/ui/stats.tsx
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
import { defineComponent } from "../define.js";
|
|
4
|
+
import { nonEmpty } from "../guards.js";
|
|
5
|
+
import { NUMISH } from "./attrs.js";
|
|
6
|
+
import { Icon } from "./icon.js";
|
|
7
|
+
import { BORDER_CLS, TEXT, TONE_TEXT } from "./tones.js";
|
|
8
|
+
|
|
9
|
+
export const Stats = defineComponent(
|
|
10
|
+
{
|
|
11
|
+
description: "指標カードのグリッド。<Stat> を並べる",
|
|
12
|
+
},
|
|
13
|
+
({ children }) => (
|
|
14
|
+
<div className="not-prose my-6 grid grid-cols-[repeat(auto-fit,minmax(9rem,1fr))] gap-3">
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const deltaStyle = (delta: string): { cls: string; icon: string } => {
|
|
21
|
+
if (delta.startsWith("+")) {
|
|
22
|
+
return {
|
|
23
|
+
cls: TONE_TEXT.emerald,
|
|
24
|
+
icon: "lucide:trending-up",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (delta.startsWith("-")) {
|
|
28
|
+
return {
|
|
29
|
+
cls: TONE_TEXT.red,
|
|
30
|
+
icon: "lucide:trending-down",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
cls: TEXT.muted,
|
|
35
|
+
icon: "lucide:minus",
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Stat = defineComponent(
|
|
40
|
+
{
|
|
41
|
+
description:
|
|
42
|
+
'指標カード。value/label は必須。delta に "+12%" 等を書くと符号で色付く',
|
|
43
|
+
schema: v.looseObject({
|
|
44
|
+
delta: v.optional(v.string()),
|
|
45
|
+
label: v.string(),
|
|
46
|
+
value: NUMISH,
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
({ value, label, delta }) => (
|
|
50
|
+
<div className={`rounded-lg border px-4 py-3 ${BORDER_CLS}`}>
|
|
51
|
+
<div className="text-2xl font-semibold tabular-nums">{value}</div>
|
|
52
|
+
<div className={`mt-0.5 text-sm ${TEXT.muted}`}>{label}</div>
|
|
53
|
+
{nonEmpty(delta) ? (
|
|
54
|
+
<div
|
|
55
|
+
className={`mt-1.5 flex items-center gap-1 text-xs font-medium ${deltaStyle(delta).cls}`}
|
|
56
|
+
>
|
|
57
|
+
<Icon className="h-3.5 w-3.5" name={deltaStyle(delta).icon} />
|
|
58
|
+
{delta}
|
|
59
|
+
</div>
|
|
60
|
+
) : null}
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
import { defineComponent } from "../define.js";
|
|
4
|
+
import { isOneOf } from "../guards.js";
|
|
5
|
+
import { Pill } from "./bits.js";
|
|
6
|
+
import { TONE, TONE_TEXT } from "./tones.js";
|
|
7
|
+
|
|
8
|
+
export const STATUSES = ["todo", "doing", "done", "blocked"] as const;
|
|
9
|
+
export type Status = (typeof STATUSES)[number];
|
|
10
|
+
|
|
11
|
+
export const isStatus = isOneOf(STATUSES);
|
|
12
|
+
|
|
13
|
+
/** Schema entry for an optional `status` prop — shared by every task/status component. */
|
|
14
|
+
export const STATUS_PROP = v.optional(v.picklist(STATUSES));
|
|
15
|
+
|
|
16
|
+
/** Iconify names for each status — shared by StatusBadge, Step and Event. */
|
|
17
|
+
export const STATUS_ICONS: Record<Status, string> = {
|
|
18
|
+
blocked: "lucide:circle-x",
|
|
19
|
+
doing: "lucide:loader-circle",
|
|
20
|
+
done: "lucide:circle-check",
|
|
21
|
+
todo: "lucide:circle",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** `text-*` color for status icons used standalone (Step, Event, Graph nodes). */
|
|
25
|
+
export const STATUS_ICON_CLS: Record<Status, string> = {
|
|
26
|
+
blocked: TONE_TEXT.red,
|
|
27
|
+
doing: TONE_TEXT.sky,
|
|
28
|
+
done: TONE_TEXT.emerald,
|
|
29
|
+
todo: TONE_TEXT.neutral,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const STYLES: Record<Status, { label: string; cls: string }> = {
|
|
33
|
+
blocked: {
|
|
34
|
+
cls: TONE.red,
|
|
35
|
+
label: "Blocked",
|
|
36
|
+
},
|
|
37
|
+
doing: {
|
|
38
|
+
cls: TONE.sky,
|
|
39
|
+
label: "In progress",
|
|
40
|
+
},
|
|
41
|
+
done: {
|
|
42
|
+
cls: TONE.emerald,
|
|
43
|
+
label: "Done",
|
|
44
|
+
},
|
|
45
|
+
todo: {
|
|
46
|
+
cls: TONE.neutral,
|
|
47
|
+
label: "Todo",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const StatusBadge = defineComponent(
|
|
52
|
+
{
|
|
53
|
+
description: "ステータスを示す小さなバッジ",
|
|
54
|
+
schema: v.looseObject({
|
|
55
|
+
status: v.optional(v.picklist(STATUSES), "todo"),
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
58
|
+
({ status }) => {
|
|
59
|
+
const s = STYLES[status];
|
|
60
|
+
return (
|
|
61
|
+
<Pill className={s.cls} icon={STATUS_ICONS[status]}>
|
|
62
|
+
{s.label}
|
|
63
|
+
</Pill>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
);
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
|
|
4
|
+
import { defineComponent, flattenChildren } from "../define.js";
|
|
5
|
+
import { isOneOf, nonEmpty } from "../guards.js";
|
|
6
|
+
import { NUMISH } from "./attrs.js";
|
|
7
|
+
import {
|
|
8
|
+
CaptionBar,
|
|
9
|
+
ListPanel,
|
|
10
|
+
ListRow,
|
|
11
|
+
Panel,
|
|
12
|
+
Pill,
|
|
13
|
+
RowNote,
|
|
14
|
+
} from "./bits.js";
|
|
15
|
+
import { isEl, propOf } from "./children.js";
|
|
16
|
+
import { Icon } from "./icon.js";
|
|
17
|
+
import {
|
|
18
|
+
CAPTION_TITLE_CLS,
|
|
19
|
+
LOC_CLS,
|
|
20
|
+
MONO_NUM_CLS,
|
|
21
|
+
TEXT,
|
|
22
|
+
TEXT_SUB,
|
|
23
|
+
TONE,
|
|
24
|
+
TRIM_CLS,
|
|
25
|
+
} from "./tones.js";
|
|
26
|
+
|
|
27
|
+
export const SERVICE_STATUSES = [
|
|
28
|
+
"operational",
|
|
29
|
+
"degraded",
|
|
30
|
+
"outage",
|
|
31
|
+
"maintenance",
|
|
32
|
+
] as const;
|
|
33
|
+
export type ServiceStatus = (typeof SERVICE_STATUSES)[number];
|
|
34
|
+
|
|
35
|
+
const isServiceStatus = isOneOf(SERVICE_STATUSES);
|
|
36
|
+
|
|
37
|
+
const SERVICE_STYLES: Record<
|
|
38
|
+
ServiceStatus,
|
|
39
|
+
{ cls: string; dot: string; icon: string; label: string }
|
|
40
|
+
> = {
|
|
41
|
+
degraded: {
|
|
42
|
+
cls: TONE.amber,
|
|
43
|
+
dot: "bg-amber-500",
|
|
44
|
+
icon: "lucide:triangle-alert",
|
|
45
|
+
label: "Degraded",
|
|
46
|
+
},
|
|
47
|
+
maintenance: {
|
|
48
|
+
cls: TONE.sky,
|
|
49
|
+
dot: "bg-sky-500",
|
|
50
|
+
icon: "lucide:wrench",
|
|
51
|
+
label: "Maintenance",
|
|
52
|
+
},
|
|
53
|
+
operational: {
|
|
54
|
+
cls: TONE.emerald,
|
|
55
|
+
dot: "bg-emerald-500",
|
|
56
|
+
icon: "lucide:circle-check",
|
|
57
|
+
label: "Operational",
|
|
58
|
+
},
|
|
59
|
+
outage: {
|
|
60
|
+
cls: TONE.red,
|
|
61
|
+
dot: "bg-red-500",
|
|
62
|
+
icon: "lucide:circle-x",
|
|
63
|
+
label: "Outage",
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** Worst status across services → the page-level rollup. */
|
|
68
|
+
const rollup = (statuses: readonly ServiceStatus[]): ServiceStatus => {
|
|
69
|
+
if (statuses.includes("outage")) {
|
|
70
|
+
return "outage";
|
|
71
|
+
}
|
|
72
|
+
if (statuses.includes("degraded")) {
|
|
73
|
+
return "degraded";
|
|
74
|
+
}
|
|
75
|
+
if (statuses.includes("maintenance")) {
|
|
76
|
+
return "maintenance";
|
|
77
|
+
}
|
|
78
|
+
return "operational";
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const Service = defineComponent(
|
|
82
|
+
{
|
|
83
|
+
description:
|
|
84
|
+
'サービス稼働状況1行。name は必須、status は operational|degraded|outage|maintenance、uptime に稼働率 ("99.98%")。children は補足 (影響内容など)',
|
|
85
|
+
schema: v.looseObject({
|
|
86
|
+
name: v.string(),
|
|
87
|
+
status: v.optional(v.picklist(SERVICE_STATUSES), "operational"),
|
|
88
|
+
uptime: v.optional(NUMISH),
|
|
89
|
+
}),
|
|
90
|
+
},
|
|
91
|
+
({ name, status, uptime, children }) => {
|
|
92
|
+
const s = SERVICE_STYLES[status];
|
|
93
|
+
return (
|
|
94
|
+
<ListRow>
|
|
95
|
+
<span
|
|
96
|
+
aria-hidden
|
|
97
|
+
className={`h-2.5 w-2.5 shrink-0 self-center rounded-full ${s.dot}`}
|
|
98
|
+
/>
|
|
99
|
+
<span className="text-sm font-medium">{name}</span>
|
|
100
|
+
<Pill className={s.cls} icon={s.icon}>
|
|
101
|
+
{s.label}
|
|
102
|
+
</Pill>
|
|
103
|
+
{uptime === undefined || uptime === "" ? null : (
|
|
104
|
+
<span className={`${LOC_CLS} ml-auto tabular-nums`}>
|
|
105
|
+
{String(uptime)}
|
|
106
|
+
</span>
|
|
107
|
+
)}
|
|
108
|
+
<RowNote className={TRIM_CLS}>{children}</RowNote>
|
|
109
|
+
</ListRow>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
export const StatusPage = defineComponent(
|
|
115
|
+
{
|
|
116
|
+
description:
|
|
117
|
+
"サービスステータス一覧のコンテナ。<Service> を並べ、最悪ステータスをロールアップ表示 (全稼働/一部障害/停止)。title はキャプション",
|
|
118
|
+
schema: v.looseObject({
|
|
119
|
+
title: v.optional(v.string()),
|
|
120
|
+
updated: v.optional(v.string()),
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
123
|
+
({ title, updated, children }) => {
|
|
124
|
+
const statuses: ServiceStatus[] = [];
|
|
125
|
+
for (const node of flattenChildren(children)) {
|
|
126
|
+
if (!isEl(node, Service)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const st = propOf(node, "status");
|
|
130
|
+
statuses.push(isServiceStatus(st) ? st : "operational");
|
|
131
|
+
}
|
|
132
|
+
const overall = rollup(statuses);
|
|
133
|
+
const o = SERVICE_STYLES[overall];
|
|
134
|
+
return (
|
|
135
|
+
<ListPanel>
|
|
136
|
+
<CaptionBar className={CAPTION_TITLE_CLS}>
|
|
137
|
+
<Icon className="h-3.5 w-3.5" name="lucide:activity" />
|
|
138
|
+
{nonEmpty(title) ? title : "Status"}
|
|
139
|
+
<span className="ml-auto flex items-center gap-x-2">
|
|
140
|
+
{nonEmpty(updated) ? (
|
|
141
|
+
<span className={`${TEXT_SUB} font-normal ${TEXT.faint}`}>
|
|
142
|
+
{updated}
|
|
143
|
+
</span>
|
|
144
|
+
) : null}
|
|
145
|
+
{statuses.length > 0 ? (
|
|
146
|
+
<Pill className={o.cls} icon={o.icon}>
|
|
147
|
+
{overall === "operational"
|
|
148
|
+
? "All systems operational"
|
|
149
|
+
: o.label}
|
|
150
|
+
</Pill>
|
|
151
|
+
) : null}
|
|
152
|
+
</span>
|
|
153
|
+
</CaptionBar>
|
|
154
|
+
{children}
|
|
155
|
+
</ListPanel>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
export const DAY_STATUSES = [
|
|
161
|
+
"up",
|
|
162
|
+
"degraded",
|
|
163
|
+
"down",
|
|
164
|
+
"maint",
|
|
165
|
+
"none",
|
|
166
|
+
] as const;
|
|
167
|
+
export type DayStatus = (typeof DAY_STATUSES)[number];
|
|
168
|
+
|
|
169
|
+
const DAY_CLS: Record<DayStatus, string> = {
|
|
170
|
+
degraded: "bg-amber-400 dark:bg-amber-500",
|
|
171
|
+
down: "bg-red-500",
|
|
172
|
+
maint: "bg-sky-400 dark:bg-sky-500",
|
|
173
|
+
none: "bg-neutral-200 dark:bg-neutral-700",
|
|
174
|
+
up: "bg-emerald-500",
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const DAY_LABEL: Record<DayStatus, string> = {
|
|
178
|
+
degraded: "degraded",
|
|
179
|
+
down: "down",
|
|
180
|
+
maint: "maintenance",
|
|
181
|
+
none: "no data",
|
|
182
|
+
up: "operational",
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const Day = defineComponent(
|
|
186
|
+
{
|
|
187
|
+
description:
|
|
188
|
+
"稼働バー1セル。status は up|degraded|down|maint|none。date/note はツールチップ。<Uptime> 内に並べる",
|
|
189
|
+
schema: v.looseObject({
|
|
190
|
+
date: v.optional(v.string()),
|
|
191
|
+
note: v.optional(v.string()),
|
|
192
|
+
status: v.optional(v.picklist(DAY_STATUSES), "up"),
|
|
193
|
+
}),
|
|
194
|
+
},
|
|
195
|
+
({ status, date, note }) => {
|
|
196
|
+
const tip = [date, DAY_LABEL[status], note].filter(nonEmpty).join(" — ");
|
|
197
|
+
return (
|
|
198
|
+
<span
|
|
199
|
+
className={`inline-block h-6 w-1.5 shrink-0 rounded-sm ${DAY_CLS[status]}`}
|
|
200
|
+
title={nonEmpty(tip) ? tip : DAY_LABEL[status]}
|
|
201
|
+
/>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
export const Uptime = defineComponent(
|
|
207
|
+
{
|
|
208
|
+
description:
|
|
209
|
+
"稼働率バー (ステータスページ風)。<Day> セルを日付順に並べる。title はラベル、pct は稼働率表示、from/to で期間の端ラベル",
|
|
210
|
+
schema: v.looseObject({
|
|
211
|
+
from: v.optional(v.string()),
|
|
212
|
+
pct: v.optional(NUMISH),
|
|
213
|
+
title: v.optional(v.string()),
|
|
214
|
+
to: v.optional(v.string()),
|
|
215
|
+
}),
|
|
216
|
+
},
|
|
217
|
+
({ title, pct, from, to, children }): ReactElement => (
|
|
218
|
+
<Panel className="px-4 py-3">
|
|
219
|
+
<div className="mb-2 flex flex-wrap items-baseline gap-x-2">
|
|
220
|
+
<span className="text-sm font-medium">
|
|
221
|
+
{nonEmpty(title) ? title : "Uptime"}
|
|
222
|
+
</span>
|
|
223
|
+
{pct === undefined || pct === "" ? null : (
|
|
224
|
+
<span className={`ml-auto ${MONO_NUM_CLS} text-sm ${TEXT.muted}`}>
|
|
225
|
+
{String(pct)}%
|
|
226
|
+
</span>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
<div className="flex flex-wrap gap-0.5">{children}</div>
|
|
230
|
+
{nonEmpty(from) || nonEmpty(to) ? (
|
|
231
|
+
<div className={`mt-2 flex justify-between ${TEXT_SUB} ${TEXT.faint}`}>
|
|
232
|
+
<span>{from ?? ""}</span>
|
|
233
|
+
<span>{to ?? ""}</span>
|
|
234
|
+
</div>
|
|
235
|
+
) : null}
|
|
236
|
+
</Panel>
|
|
237
|
+
)
|
|
238
|
+
);
|
package/src/ui/steps.tsx
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
import { defineComponent, flattenChildren } from "../define.js";
|
|
4
|
+
import { attrTrue, BOOLISH_PROP } from "./attrs.js";
|
|
5
|
+
import { isEl, propOf } from "./children.js";
|
|
6
|
+
import { CHIP_PROPS, ChipRow } from "./chips.js";
|
|
7
|
+
import { Icon } from "./icon.js";
|
|
8
|
+
import {
|
|
9
|
+
isStatus,
|
|
10
|
+
STATUS_ICON_CLS,
|
|
11
|
+
STATUS_ICONS,
|
|
12
|
+
STATUSES,
|
|
13
|
+
} from "./status-badge.js";
|
|
14
|
+
import type { Status } from "./status-badge.js";
|
|
15
|
+
import { Summary } from "./summary.js";
|
|
16
|
+
import { TRIM_CLS } from "./tones.js";
|
|
17
|
+
|
|
18
|
+
export const Step = defineComponent(
|
|
19
|
+
{
|
|
20
|
+
description:
|
|
21
|
+
"単一の手順。status は todo|doing|done|blocked。owner/effort/priority/due でチップを付けられる",
|
|
22
|
+
schema: v.looseObject({
|
|
23
|
+
...CHIP_PROPS,
|
|
24
|
+
status: v.optional(v.picklist(STATUSES), "todo"),
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
({ status, owner, effort, priority, due, children }) => (
|
|
28
|
+
<div className="flex gap-3">
|
|
29
|
+
<Icon
|
|
30
|
+
className={`mt-1 h-4.5 w-4.5 shrink-0 ${STATUS_ICON_CLS[status]}`}
|
|
31
|
+
label={status}
|
|
32
|
+
name={STATUS_ICONS[status]}
|
|
33
|
+
/>
|
|
34
|
+
<div className="min-w-0 flex-1">
|
|
35
|
+
<div className={TRIM_CLS}>{children}</div>
|
|
36
|
+
<ChipRow due={due} effort={effort} owner={owner} priority={priority} />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
/** Resolve a child's effective status — only `<Step>` elements count. */
|
|
43
|
+
const stepStatus = (node: unknown): Status | undefined => {
|
|
44
|
+
if (!isEl(node, Step)) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const s = propOf(node, "status");
|
|
48
|
+
return isStatus(s) ? s : "todo";
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Steps = defineComponent(
|
|
52
|
+
{
|
|
53
|
+
description:
|
|
54
|
+
"手順リストのコンテナ。<Step> を並べる。progress で自動進捗バーを表示",
|
|
55
|
+
schema: v.looseObject({
|
|
56
|
+
progress: BOOLISH_PROP,
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
({ progress, children }) => {
|
|
60
|
+
const show = attrTrue(progress);
|
|
61
|
+
const items = flattenChildren(children)
|
|
62
|
+
.map((node) => stepStatus(node))
|
|
63
|
+
.filter((s): s is Status => s !== undefined);
|
|
64
|
+
const done = items.filter((s) => s === "done").length;
|
|
65
|
+
return (
|
|
66
|
+
<div className="my-6 space-y-3">
|
|
67
|
+
{show && items.length > 0 ? (
|
|
68
|
+
<Summary done={done} label="Steps" total={items.length} />
|
|
69
|
+
) : null}
|
|
70
|
+
{children}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
import { defineComponent } from "../define.js";
|
|
4
|
+
import { NUMISH } from "./attrs.js";
|
|
5
|
+
import { TEXT, TRACK_CLS } from "./tones.js";
|
|
6
|
+
|
|
7
|
+
export const Summary = defineComponent(
|
|
8
|
+
{
|
|
9
|
+
description:
|
|
10
|
+
"進捗バー。done/total は文字列でもよい (MDX の属性は文字列のため)",
|
|
11
|
+
schema: v.looseObject({
|
|
12
|
+
done: v.optional(NUMISH, "0"),
|
|
13
|
+
label: v.optional(v.string()),
|
|
14
|
+
total: v.optional(NUMISH, "0"),
|
|
15
|
+
}),
|
|
16
|
+
},
|
|
17
|
+
({ done, total, label }) => {
|
|
18
|
+
// Unparseable values read as 0 — `NaN` would leak into the label text
|
|
19
|
+
// and produce an invalid `width: NaN%` style. Negative counts are
|
|
20
|
+
// clamped too: `width: -50%` is just as invalid.
|
|
21
|
+
const d = Math.max(0, Number.isFinite(Number(done)) ? Number(done) : 0);
|
|
22
|
+
const t = Math.max(0, Number.isFinite(Number(total)) ? Number(total) : 0);
|
|
23
|
+
const pct = t > 0 ? Math.min(100, Math.round((d / t) * 100)) : 0;
|
|
24
|
+
return (
|
|
25
|
+
<div className="not-prose my-6">
|
|
26
|
+
<div className="mb-1.5 flex items-baseline justify-between text-sm">
|
|
27
|
+
<span className="font-medium">{label ?? "Progress"}</span>
|
|
28
|
+
<span className={`tabular-nums ${TEXT.muted}`}>
|
|
29
|
+
{d}/{t}
|
|
30
|
+
</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div className={`h-2 overflow-hidden rounded-full ${TRACK_CLS}`}>
|
|
33
|
+
<div
|
|
34
|
+
className="h-full rounded-full bg-emerald-500 transition-all"
|
|
35
|
+
style={{ width: `${pct}%` }}
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
);
|
|
@@ -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 { FILE_LINK_PROPS } from "./attrs.js";
|
|
6
|
+
import { CodeChip, CopyButton, MaybeLink, PathLabel } from "./bits.js";
|
|
7
|
+
import { useFileLink } from "./file-link.js";
|
|
8
|
+
import { Icon } from "./icon.js";
|
|
9
|
+
import { LINK_CLS } from "./tones.js";
|
|
10
|
+
|
|
11
|
+
export const SYMBOL_KINDS = [
|
|
12
|
+
"class",
|
|
13
|
+
"component",
|
|
14
|
+
"const",
|
|
15
|
+
"enum",
|
|
16
|
+
"fn",
|
|
17
|
+
"interface",
|
|
18
|
+
"prop",
|
|
19
|
+
"type",
|
|
20
|
+
] as const;
|
|
21
|
+
export type SymbolKind = (typeof SYMBOL_KINDS)[number];
|
|
22
|
+
|
|
23
|
+
const KIND_ICONS: Record<SymbolKind, string> = {
|
|
24
|
+
class: "lucide:box",
|
|
25
|
+
component: "lucide:puzzle",
|
|
26
|
+
const: "lucide:variable",
|
|
27
|
+
enum: "lucide:list-ordered",
|
|
28
|
+
fn: "lucide:square-function",
|
|
29
|
+
interface: "lucide:braces",
|
|
30
|
+
prop: "lucide:tag",
|
|
31
|
+
type: "lucide:type",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const SymbolRef = defineComponent(
|
|
35
|
+
{
|
|
36
|
+
description:
|
|
37
|
+
"コードシンボル (関数/型など) への参照チップ。kind でアイコンを出し分け、path/lines で定義場所を併記できる。実在する path はエディタリンク(既定 vscode://)になり、href で上書き可",
|
|
38
|
+
schema: v.looseObject({
|
|
39
|
+
...FILE_LINK_PROPS,
|
|
40
|
+
kind: v.optional(v.picklist(SYMBOL_KINDS)),
|
|
41
|
+
name: v.string(),
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
({ name, kind, path, lines, href }) => {
|
|
45
|
+
const link = useFileLink(path, lines, href);
|
|
46
|
+
const label = (
|
|
47
|
+
<>
|
|
48
|
+
{name}
|
|
49
|
+
{nonEmpty(path) ? (
|
|
50
|
+
<span className="opacity-60">
|
|
51
|
+
{" "}
|
|
52
|
+
· <PathLabel lines={lines} path={path} />
|
|
53
|
+
</span>
|
|
54
|
+
) : null}
|
|
55
|
+
</>
|
|
56
|
+
);
|
|
57
|
+
return (
|
|
58
|
+
<CodeChip>
|
|
59
|
+
{kind === undefined ? null : (
|
|
60
|
+
<Icon className="h-3.5 w-3.5 opacity-60" name={KIND_ICONS[kind]} />
|
|
61
|
+
)}
|
|
62
|
+
<MaybeLink className={LINK_CLS} href={link}>
|
|
63
|
+
{label}
|
|
64
|
+
</MaybeLink>
|
|
65
|
+
<CopyButton
|
|
66
|
+
className="-mr-0.5 opacity-40"
|
|
67
|
+
copy={nonEmpty(path) ? path : name}
|
|
68
|
+
title="Copy"
|
|
69
|
+
/>
|
|
70
|
+
</CodeChip>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
|
|
4
|
+
import { defineComponent, flattenChildren, textOf } from "../define.js";
|
|
5
|
+
import { nonEmpty } from "../guards.js";
|
|
6
|
+
import { NUMISH } from "./attrs.js";
|
|
7
|
+
import { CopyButton } from "./bits.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fence languages rendered as a command transcript instead of highlighted
|
|
11
|
+
* code: ` ```console `, ` ```terminal `, ` ```shellsession `. Shared from
|
|
12
|
+
* langs.ts — rehype/shiki.ts SKIP_LANGS includes them so the raw text
|
|
13
|
+
* reaches `Pre` untouched.
|
|
14
|
+
*/
|
|
15
|
+
export { TERMINAL_LANGS } from "../langs.js";
|
|
16
|
+
|
|
17
|
+
const PROMPT_RE = /^\$(?:\s|$)/u;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Flatten children to transcript text. A fenced code block child contributes
|
|
21
|
+
* its raw code (highlight spans are `\n`-separated, so `textOf` preserves
|
|
22
|
+
* line breaks); paragraphs and text join on newlines.
|
|
23
|
+
*/
|
|
24
|
+
const transcriptText = (children: ReactNode): string =>
|
|
25
|
+
flattenChildren(children)
|
|
26
|
+
.map((child) => textOf(child))
|
|
27
|
+
.join("\n")
|
|
28
|
+
.replaceAll(/\n{3,}/gu, "\n\n")
|
|
29
|
+
.replaceAll(/^\n+|\n+$/gu, "");
|
|
30
|
+
|
|
31
|
+
export interface TranscriptProps {
|
|
32
|
+
/** Prepended as a `$ …` prompt line when given. */
|
|
33
|
+
cmd?: string;
|
|
34
|
+
/** Exit code badge in the title bar — `0`/empty renders green, others red. */
|
|
35
|
+
exit?: string;
|
|
36
|
+
/** Transcript text; `$ `-prefixed lines render as command prompts. */
|
|
37
|
+
text: string;
|
|
38
|
+
/** Title bar label (defaults to "Terminal"). */
|
|
39
|
+
title?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Shared terminal-window renderer — used by `Terminal` and `Pre` console fences. */
|
|
43
|
+
export const Transcript = ({
|
|
44
|
+
cmd,
|
|
45
|
+
exit,
|
|
46
|
+
text,
|
|
47
|
+
title,
|
|
48
|
+
}: TranscriptProps): ReactElement => {
|
|
49
|
+
const body = nonEmpty(cmd) ? `$ ${cmd}\n${text}` : text;
|
|
50
|
+
const lines = body.replace(/\n+$/u, "").split("\n");
|
|
51
|
+
const succeeded = exit === "" || exit === "0";
|
|
52
|
+
return (
|
|
53
|
+
<figure className="not-prose my-6 overflow-hidden rounded-lg border border-neutral-800 bg-neutral-950">
|
|
54
|
+
<figcaption className="flex items-center gap-2 border-b border-neutral-800 bg-neutral-900 px-4 py-2 text-xs">
|
|
55
|
+
<span aria-hidden className="flex shrink-0 gap-1.5">
|
|
56
|
+
<span className="h-2.5 w-2.5 rounded-full bg-red-500/70" />
|
|
57
|
+
<span className="h-2.5 w-2.5 rounded-full bg-amber-400/70" />
|
|
58
|
+
<span className="h-2.5 w-2.5 rounded-full bg-emerald-500/70" />
|
|
59
|
+
</span>
|
|
60
|
+
<span className="min-w-0 flex-1 truncate font-mono text-neutral-500">
|
|
61
|
+
{nonEmpty(title) ? title : "Terminal"}
|
|
62
|
+
</span>
|
|
63
|
+
{exit === undefined ? null : (
|
|
64
|
+
<span
|
|
65
|
+
className={`shrink-0 rounded px-1.5 py-0.5 font-mono text-[0.7rem] font-medium ${
|
|
66
|
+
succeeded
|
|
67
|
+
? "bg-emerald-900/70 text-emerald-300"
|
|
68
|
+
: "bg-red-900/70 text-red-300"
|
|
69
|
+
}`}
|
|
70
|
+
>
|
|
71
|
+
exit {exit === "" ? "0" : exit}
|
|
72
|
+
</span>
|
|
73
|
+
)}
|
|
74
|
+
<CopyButton
|
|
75
|
+
className="shrink-0 text-neutral-500 opacity-60"
|
|
76
|
+
copy={body}
|
|
77
|
+
doneClassName="text-emerald-400"
|
|
78
|
+
title="Copy transcript"
|
|
79
|
+
/>
|
|
80
|
+
</figcaption>
|
|
81
|
+
<pre className="m-0 overflow-x-auto p-4 font-mono text-sm leading-relaxed">
|
|
82
|
+
{lines.map((line, i) =>
|
|
83
|
+
PROMPT_RE.test(line) ? (
|
|
84
|
+
<span className="block text-neutral-100" key={i}>
|
|
85
|
+
<span className="text-emerald-400 select-none">$</span>
|
|
86
|
+
{line.slice(1)}
|
|
87
|
+
</span>
|
|
88
|
+
) : (
|
|
89
|
+
<span className="block text-neutral-400" key={i}>
|
|
90
|
+
{nonEmpty(line) ? line : " "}
|
|
91
|
+
</span>
|
|
92
|
+
)
|
|
93
|
+
)}
|
|
94
|
+
</pre>
|
|
95
|
+
</figure>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const Terminal = defineComponent(
|
|
100
|
+
{
|
|
101
|
+
description:
|
|
102
|
+
"ターミナル実行結果ブロック。cmd は `$ ` プロンプト行として先頭に表示、exit は終了コードバッジ (0=緑/他=赤)、title はタイトルバー。children は出力(フェンスドコードブロック推奨)。本文中 `$ ` 始まりの行はコマンド行として強調",
|
|
103
|
+
schema: v.looseObject({
|
|
104
|
+
cmd: v.optional(v.string()),
|
|
105
|
+
exit: v.optional(NUMISH),
|
|
106
|
+
title: v.optional(v.string()),
|
|
107
|
+
}),
|
|
108
|
+
},
|
|
109
|
+
({ cmd, exit, title, children }) => (
|
|
110
|
+
<Transcript
|
|
111
|
+
cmd={cmd}
|
|
112
|
+
exit={exit === undefined ? undefined : String(exit)}
|
|
113
|
+
text={transcriptText(children)}
|
|
114
|
+
title={title}
|
|
115
|
+
/>
|
|
116
|
+
)
|
|
117
|
+
);
|