claude-code

This commit is contained in:
ashutoshpythoncs@gmail.com
2026-03-31 18:58:05 +05:30
parent a2a44a5841
commit b564857c0b
2148 changed files with 564518 additions and 2 deletions

View File

@@ -0,0 +1,170 @@
"use client";
/**
* Web-adapted Markdown renderer.
*
* The terminal Markdown (src/components/Markdown.tsx) uses marked + Ink's
* <Ansi> / <Box> to render tokenised markdown as coloured ANSI output.
* This web version uses react-markdown + remark-gfm + rehype-highlight, which
* are already present in the web package, to render proper HTML with Tailwind
* prose styles.
*
* Props are intentionally compatible with the terminal version so callers can
* swap between them via the platform conditional.
*/
import * as React from "react";
import { useMemo } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { cn } from "@/lib/utils";
// ─── Types ────────────────────────────────────────────────────────────────────
export interface MarkdownProps {
/** Markdown source string — matches the terminal component's children prop. */
children: string;
/** When true, render all text as visually dimmed (muted colour). */
dimColor?: boolean;
/** Extra class names applied to the prose wrapper. */
className?: string;
}
// ─── Inline code / pre renderers ─────────────────────────────────────────────
function InlineCode({ children }: { children?: React.ReactNode }) {
return (
<code className="px-1 py-0.5 rounded text-xs font-mono bg-surface-850 text-brand-300 border border-surface-700">
{children}
</code>
);
}
interface PreProps {
children?: React.ReactNode;
}
function Pre({ children }: PreProps) {
return (
<pre className="overflow-x-auto rounded-md bg-surface-900 border border-surface-700 p-3 my-2 text-xs font-mono leading-relaxed">
{children}
</pre>
);
}
// ─── Component ────────────────────────────────────────────────────────────────
export function Markdown({ children, dimColor = false, className }: MarkdownProps) {
// Memoised to avoid re-parsing on every parent render.
const content = useMemo(() => children, [children]);
return (
<div
className={cn(
"markdown-body text-sm leading-relaxed font-mono",
dimColor ? "text-surface-500" : "text-surface-100",
// Headings
"[&_h1]:text-base [&_h1]:font-bold [&_h1]:mb-2 [&_h1]:mt-3 [&_h1]:text-surface-50",
"[&_h2]:text-sm [&_h2]:font-semibold [&_h2]:mb-1.5 [&_h2]:mt-2.5 [&_h2]:text-surface-100",
"[&_h3]:text-sm [&_h3]:font-semibold [&_h3]:mb-1 [&_h3]:mt-2 [&_h3]:text-surface-200",
// Paragraphs
"[&_p]:my-1 [&_p]:leading-relaxed",
// Lists
"[&_ul]:my-1 [&_ul]:pl-4 [&_ul]:list-disc",
"[&_ol]:my-1 [&_ol]:pl-4 [&_ol]:list-decimal",
"[&_li]:my-0.5",
// Blockquote
"[&_blockquote]:border-l-2 [&_blockquote]:border-brand-500 [&_blockquote]:pl-3",
"[&_blockquote]:my-2 [&_blockquote]:text-surface-400 [&_blockquote]:italic",
// Horizontal rule
"[&_hr]:border-surface-700 [&_hr]:my-3",
// Tables (GFM)
"[&_table]:w-full [&_table]:text-xs [&_table]:border-collapse [&_table]:my-2",
"[&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:border [&_th]:border-surface-700 [&_th]:bg-surface-800 [&_th]:font-semibold",
"[&_td]:px-2 [&_td]:py-1 [&_td]:border [&_td]:border-surface-700",
"[&_tr:nth-child(even)_td]:bg-surface-900/40",
// Links
"[&_a]:text-brand-400 [&_a]:no-underline [&_a:hover]:underline",
// Strong / em
"[&_strong]:font-bold [&_strong]:text-surface-50",
"[&_em]:italic",
className
)}
>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
code({ className: cls, children: codeChildren, ...rest }) {
const isBlock = /language-/.test(cls ?? "");
if (isBlock) {
return (
<code className={cn("block text-surface-200", cls)} {...rest}>
{codeChildren}
</code>
);
}
return <InlineCode {...rest}>{codeChildren}</InlineCode>;
},
pre: ({ children: preChildren }) => <Pre>{preChildren}</Pre>,
}}
>
{content}
</ReactMarkdown>
</div>
);
}
// ─── Table component (matches MarkdownTable.tsx surface) ─────────────────────
export interface MarkdownTableProps {
headers: string[];
rows: string[][];
className?: string;
}
export function MarkdownTable({ headers, rows, className }: MarkdownTableProps) {
return (
<div className={cn("overflow-x-auto my-2", className)}>
<table className="w-full text-xs border-collapse font-mono">
<thead>
<tr>
{headers.map((h, i) => (
<th
key={i}
className="px-2 py-1 text-left border border-surface-700 bg-surface-800 font-semibold text-surface-200"
>
{h}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, ri) => (
<tr key={ri}>
{row.map((cell, ci) => (
<td
key={ci}
className={cn(
"px-2 py-1 border border-surface-700 text-surface-300",
ri % 2 === 1 && "bg-surface-900/40"
)}
>
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}

View File

@@ -0,0 +1,151 @@
"use client";
/**
* Web-adapted Spinner.
*
* The terminal Spinner (src/components/Spinner.tsx) drives animation via
* useAnimationFrame and renders Unicode braille/block characters with ANSI
* colour via Ink's <Text>. In the browser we replace that with a pure-CSS
* spinning ring, preserving the same optional `tip` text and `mode` prop
* surface so callers can swap in this component without changing props.
*/
import * as React from "react";
import { cn } from "@/lib/utils";
// ─── Types ────────────────────────────────────────────────────────────────────
/** Mirrors the SpinnerMode type from src/components/Spinner/index.ts */
export type SpinnerMode =
| "queued"
| "loading"
| "thinking"
| "auto"
| "disabled";
export interface SpinnerProps {
/** Visual mode — controls colour/appearance. */
mode?: SpinnerMode;
/** Optional tip text shown next to the spinner. */
spinnerTip?: string;
/** Override message replaces the default verb. */
overrideMessage?: string | null;
/** Additional suffix appended after the main label. */
spinnerSuffix?: string | null;
/** When true the spinner renders inline instead of as a block row. */
inline?: boolean;
/** Extra class names for the wrapper element. */
className?: string;
}
// ─── Colour map ───────────────────────────────────────────────────────────────
const MODE_RING_CLASS: Record<SpinnerMode, string> = {
queued: "border-surface-500",
loading: "border-brand-400",
thinking: "border-brand-500",
auto: "border-brand-400",
disabled: "border-surface-600",
};
const MODE_TEXT_CLASS: Record<SpinnerMode, string> = {
queued: "text-surface-400",
loading: "text-brand-300",
thinking: "text-brand-300",
auto: "text-brand-300",
disabled: "text-surface-500",
};
const MODE_LABEL: Record<SpinnerMode, string> = {
queued: "Queued…",
loading: "Loading…",
thinking: "Thinking…",
auto: "Working…",
disabled: "",
};
// ─── Component ────────────────────────────────────────────────────────────────
export function Spinner({
mode = "loading",
spinnerTip,
overrideMessage,
spinnerSuffix,
inline = false,
className,
}: SpinnerProps) {
if (mode === "disabled") return null;
const label =
overrideMessage ??
spinnerTip ??
MODE_LABEL[mode];
const ringClass = MODE_RING_CLASS[mode];
const textClass = MODE_TEXT_CLASS[mode];
return (
<span
role="status"
aria-label={label || "Loading"}
className={cn(
"flex items-center gap-2",
inline ? "inline-flex" : "flex",
className
)}
>
{/* CSS spinning ring */}
<span
className={cn(
"block w-3.5 h-3.5 rounded-full border-2 border-transparent animate-spin flex-shrink-0",
ringClass,
// Top border only — creates the "gap" in the ring for the spinning effect
"[border-top-color:currentColor]"
)}
style={{ borderTopColor: undefined }}
aria-hidden
>
{/* Inner ring for the visible arc — achieved via box-shadow trick */}
</span>
{(label || spinnerSuffix) && (
<span className={cn("text-sm font-mono", textClass)}>
{label}
{spinnerSuffix && (
<span className="text-surface-500 ml-1">{spinnerSuffix}</span>
)}
</span>
)}
</span>
);
}
// ─── Shimmer / glimmer variant ────────────────────────────────────────────────
/** Pulsing shimmer bar — web replacement for GlimmerMessage / ShimmerChar. */
export function ShimmerBar({ className }: { className?: string }) {
return (
<div
className={cn(
"h-2 rounded-full bg-gradient-to-r from-surface-700 via-surface-500 to-surface-700",
"bg-[length:200%_100%] animate-shimmer",
className
)}
aria-hidden
/>
);
}
/** Inline flashing cursor dot — web replacement for FlashingChar. */
export function FlashingCursor({ className }: { className?: string }) {
return (
<span
className={cn(
"inline-block w-1.5 h-4 bg-current align-text-bottom ml-0.5",
"animate-pulse-soft",
className
)}
aria-hidden
/>
);
}