Files
AgentCompany/src/renderer/components/communication/composer-shell/ComposerShell.tsx
T
ZacharyZhang-NY 75022f82c5 refactor(renderer): route all Phosphor icon imports through ui/icons barrel
Create src/renderer/components/ui/icons.ts as the single re-export point
for every Phosphor icon the renderer touches, then rewrite the 95 files
that previously imported directly from "@phosphor-icons/react" to pull
from the relative-path-to-barrel instead. The TitleBar commit earlier
in this series already used ui/icons; this commit extends the pattern
to the rest of the renderer.

Benefits now wired in:
  - one location where bundle tree-shaking can be reasoned about,
  - typos like Briefcase vs BriefCase show up against a finite list,
  - re-theming or swapping icon sets is a one-file edit,
  - a lint rule that flags raw @phosphor-icons/react imports can be
    added later without mass-rewriting call sites.

The barrel also re-exports the `Icon` type so call sites that type
icon props against the Phosphor component type work unchanged.

Test count stays at 363 passing; typecheck stays at 156 errors.
2026-04-16 23:37:35 -04:00

97 lines
2.8 KiB
TypeScript

import { forwardRef, useCallback, type ReactNode, type KeyboardEvent, type ChangeEvent } from "react";
import { PaperPlaneTilt } from "../../ui/icons";
import { ActionButton, InlineNotice } from "../../ui";
export interface ComposerShellProps {
value: string;
onChange: (value: string, event: ChangeEvent<HTMLTextAreaElement>) => void;
onSend: () => void;
placeholder: string;
sending: boolean;
error?: string | null;
shortcutHint?: string;
sendLabel?: string;
sendDisabled?: boolean;
leading?: ReactNode;
belowHeader?: ReactNode;
onKeyDownExtra?: (event: KeyboardEvent<HTMLTextAreaElement>) => boolean;
textareaAriaLabel?: string;
rows?: number;
}
function SendButton({ sending, disabled, onClick, label }: { sending: boolean; disabled: boolean; onClick: () => void; label: string }) {
return (
<ActionButton
label={label}
icon={PaperPlaneTilt}
tone="accent"
iconOnly
loading={sending}
disabled={disabled}
onClick={onClick}
/>
);
}
export const ComposerShell = forwardRef<HTMLTextAreaElement, ComposerShellProps>(function ComposerShell(
{
value,
onChange,
onSend,
placeholder,
sending,
error = null,
shortcutHint,
sendLabel = "Send message",
sendDisabled,
leading,
belowHeader,
onKeyDownExtra,
textareaAriaLabel,
rows = 2,
},
ref,
) {
const handleChange = useCallback(
(event: ChangeEvent<HTMLTextAreaElement>) => {
onChange(event.target.value, event);
},
[onChange],
);
const handleKeyDown = useCallback(
(event: KeyboardEvent<HTMLTextAreaElement>) => {
if (onKeyDownExtra && onKeyDownExtra(event)) return;
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
onSend();
}
},
[onKeyDownExtra, onSend],
);
const disabled = sendDisabled ?? !value.trim();
return (
<>
{error ? <div className="mb-3"><InlineNotice message={error} /></div> : null}
{belowHeader}
<div className="relative flex items-end gap-2">
{leading}
<textarea
ref={ref}
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder={placeholder}
aria-label={textareaAriaLabel ?? placeholder}
rows={rows}
className="min-h-[40px] min-w-0 flex-1 resize-none rounded-lg border border-border bg-muted px-3 py-2 text-[13px] text-foreground placeholder:text-muted-foreground focus:border-transparent focus:outline-none focus:ring-2 focus:ring-ring"
/>
<SendButton sending={sending} disabled={disabled} onClick={onSend} label={sendLabel} />
</div>
{shortcutHint ? <div className="mt-1 text-[11px] text-muted-foreground">{shortcutHint}</div> : null}
</>
);
});