authorgravatar for jakeleventhal@me.comJake Leventhal <jakeleventhal@me.com> 2026-10-03 20:10:58-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-03 21:10:58-03:00
logf2cc80a7a44ee05c1f39442779f2b719e31faeb5
tree07756e7afceb14f517345132523aed31b5349a8d
parent44bd4c9cafa61a415f76fbb261f321a158660577
signature Signed by PGP key B5690EEEBB952194

feat(web): morph composer and panel action icons (#14924)

Co-authored-by: maria-rcks <maria@kuuro.net>

31 files changed, 278 insertions(+), 230 deletions(-)

apps/web/package.json+2
......@@ -48,8 +48,10 @@
4848 "jose": "catalog:",
4949 "jsonc-parser": "3.3.1",
5050 "jszip": "3.10.1",
51 "lucide": "^0.564.0",
5152 "lucide-react": "^0.564.0",
5253 "mermaid": "^11.17.2",
54 "morphicons": "^1.7.1",
5355 "react": "19.2.6",
5456 "react-dom": "19.2.6",
5557 "react-markdown": "^10.1.0",
apps/web/src/components/ChatMarkdown.tsx+5-7
......@@ -5,10 +5,8 @@ import {
55 encodeComposerContextClipboardHtml,
66} from "@t3tools/shared/composerContextClipboard";
77import {
8 CheckIcon,
98 ChevronRightIcon,
109 CodeIcon,
11 CopyIcon,
1210 FileSpreadsheetIcon,
1311 FileTextIcon,
1412 GlobeIcon,
......@@ -16,10 +14,8 @@ import {
1614 InfoIcon,
1715 LightbulbIcon,
1816 MailIcon,
19 Maximize2Icon,
2017 MessageSquareIcon,
2118 MessageSquareWarningIcon,
22 Minimize2Icon,
2319 OctagonAlertIcon,
2420 PlayIcon,
2521 PresentationIcon,
......@@ -29,6 +25,7 @@ import {
2925 WrapTextIcon,
3026 type LucideIcon,
3127} from "lucide-react";
28import { Check, Copy, Maximize2, Minimize2 } from "lucide";
3229import type {
3330 AssetResource,
3431 EnvironmentId,
......@@ -123,6 +120,7 @@ import {
123120import { hasSpecificPierreIconForFileName, syntheticFileNameForLanguageId } from "../pierre-icons";
124121import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
125122import { Button } from "./ui/button";
123import { MorphIcon } from "~/components/MorphIcon";
126124import { ContextChip } from "./ContextChip";
127125import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "./ui/collapsible";
128126import { ScrollArea } from "./ui/scroll-area";
......@@ -849,7 +847,7 @@ function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) {
849847 />
850848 }
851849 >
852 {expanded ? <Minimize2Icon className="size-3" /> : <Maximize2Icon className="size-3" />}
850 <MorphIcon className="size-3" icon={expanded ? Minimize2 : Maximize2} />
853851 </TooltipTrigger>
854852 <TooltipPopup side="top">{expandLabel}</TooltipPopup>
855853 </Tooltip>
......@@ -869,7 +867,7 @@ function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) {
869867 />
870868 }
871869 >
872 {copied ? <CheckIcon className="size-3" /> : <CopyIcon className="size-3" />}
870 <MorphIcon className="size-3" icon={copied ? Check : Copy} />
873871 </TooltipTrigger>
874872 <TooltipPopup side="top">{copyLabel}</TooltipPopup>
875873 </Tooltip>
......@@ -1063,7 +1061,7 @@ function MarkdownCodeBlock({
10631061 />
10641062 }
10651063 >
1066 {copied ? <CheckIcon className="size-3" /> : <CopyIcon className="size-3" />}
1064 <MorphIcon className="size-3" icon={copied ? Check : Copy} />
10671065 </TooltipTrigger>
10681066 <TooltipPopup side="top">{copyLabel}</TooltipPopup>
10691067 </Tooltip>
apps/web/src/components/DiffFilePathCopyButton.tsx+7-2
......@@ -1,12 +1,14 @@
1import { CheckIcon, CopyIcon } from "lucide-react";
1import { Check, Copy } from "lucide";
22import { useRef } from "react";
33import { useCopyToClipboard } from "../hooks/useCopyToClipboard";
4import { cn } from "../lib/utils";
45import {
56 ANCHORED_COPY_TOAST_TIMEOUT_MS,
67 showAnchoredCopyErrorToast,
78 showAnchoredCopySuccessToast,
89} from "./ui/anchoredCopyToast";
910import { Button } from "./ui/button";
11import { MorphIcon } from "~/components/MorphIcon";
1012import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
1113
1214export function DiffFilePathCopyButton({ filePath }: { filePath: string }) {
......@@ -30,7 +32,10 @@ export function DiffFilePathCopyButton({ filePath }: { filePath: string }) {
3032 />
3133 }
3234 >
33 {isCopied ? <CheckIcon className="size-3 text-success" /> : <CopyIcon className="size-3" />}
35 <MorphIcon
36 className={cn("size-3", isCopied && "text-success")}
37 icon={isCopied ? Check : Copy}
38 />
3439 </TooltipTrigger>
3540 <TooltipPopup>
3641 <p>{isCopied ? "Copied" : "Copy path"}</p>
apps/web/src/components/DiffPanel.tsx+10-17
......@@ -12,15 +12,13 @@ import {
1212 ArrowRightIcon,
1313 CheckIcon,
1414 ChevronDownIcon,
15 ChevronRightIcon,
16 ChevronsDownUpIcon,
17 ChevronsUpDownIcon,
1815 Columns2Icon,
1916 FolderTreeIcon,
2017 PilcrowIcon,
2118 Rows3Icon,
2219 TextWrapIcon,
2320} from "lucide-react";
21import { ChevronDown, ChevronRight, ChevronsDownUp, ChevronsUpDown } from "lucide";
2422import * as Schema from "effect/Schema";
2523import * as DateTime from "effect/DateTime";
2624import { useCallback, useEffect, useMemo, useRef, useState } from "react";
......@@ -58,6 +56,7 @@ import { AnnotatableCodeView, type AnnotatableCodeViewHandle } from "./diffs/Ann
5856import { DiffFileTree } from "./diffs/DiffFileTree";
5957import { diffFileTreeEntries } from "./diffs/diffFileTree.logic";
6058import { Button } from "./ui/button";
59import { MorphIcon } from "~/components/MorphIcon";
6160import { ToggleGroup, Toggle } from "./ui/toggle-group";
6261import { Switch } from "./ui/switch";
6362import {
......@@ -873,11 +872,10 @@ export default function DiffPanel({
873872 />
874873 }
875874 >
876 {allDiffFilesCollapsed ? (
877 <ChevronsUpDownIcon className="size-3.5" />
878 ) : (
879 <ChevronsDownUpIcon className="size-3.5" />
880 )}
875 <MorphIcon
876 className="size-3.5"
877 icon={allDiffFilesCollapsed ? ChevronsUpDown : ChevronsDownUp}
878 />
881879 </TooltipTrigger>
882880 <TooltipPopup side="top">
883881 {allDiffFilesCollapsed ? "Expand all files" : "Collapse all files"}
......@@ -1133,15 +1131,10 @@ export default function DiffPanel({
11331131 />
11341132 }
11351133 >
1136 {collapsed ? (
1137 <ChevronRightIcon
1138 className={cn("size-4", getDiffCollapseIconClassName(fileDiff))}
1139 />
1140 ) : (
1141 <ChevronDownIcon
1142 className={cn("size-4", getDiffCollapseIconClassName(fileDiff))}
1143 />
1144 )}
1134 <MorphIcon
1135 className={cn("size-4", getDiffCollapseIconClassName(fileDiff))}
1136 icon={collapsed ? ChevronRight : ChevronDown}
1137 />
11451138 </TooltipTrigger>
11461139 <TooltipPopup side="top">
11471140 {collapsed ? "Expand diff" : "Collapse diff"}
apps/web/src/components/MorphIcon.tsx created+6
......@@ -0,0 +1,6 @@
1import { MorphIcon as BaseMorphIcon, type MorphIconProps } from "morphicons/react";
2
3/** Renders `lucide` icon data and morphs between shapes when `icon` changes. */
4export function MorphIcon({ reducedMotion = "user", ...props }: MorphIconProps) {
5 return <BaseMorphIcon reducedMotion={reducedMotion} {...props} />;
6}
apps/web/src/components/RightPanelTabs.tsx+6-7
......@@ -23,9 +23,8 @@ import {
2323 Globe2,
2424 Plus,
2525 TerminalSquare,
26 Volume2,
27 VolumeOff,
2826} from "lucide-react";
27import { Volume2, VolumeOff } from "lucide";
2928import {
3029 type KeyboardEvent as ReactKeyboardEvent,
3130 type MouseEvent as ReactMouseEvent,
......@@ -44,6 +43,7 @@ import type { RightPanelSurface } from "~/rightPanelStore";
4443import { cn } from "~/lib/utils";
4544import { readLocalApi } from "~/localApi";
4645import { Button } from "~/components/ui/button";
46import { MorphIcon } from "~/components/MorphIcon";
4747import { AndroidIcon, AppleIcon } from "~/components/Icons";
4848import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip";
4949import { Kbd } from "~/components/ui/kbd";
......@@ -1149,11 +1149,10 @@ export function RightPanelTabs(props: RightPanelTabsProps) {
11491149 .catch(() => undefined);
11501150 }}
11511151 >
1152 {audio === "muted" ? (
1153 <VolumeOff className="size-3" />
1154 ) : (
1155 <Volume2 className="size-3" />
1156 )}
1152 <MorphIcon
1153 className="size-3"
1154 icon={audio === "muted" ? VolumeOff : Volume2}
1155 />
11571156 </button>
11581157 }
11591158 />
apps/web/src/components/chat/ChangedFilesTree.tsx+11-18
......@@ -6,20 +6,15 @@ import {
66 summarizeTurnDiffStats,
77 type TurnDiffTreeNode,
88} from "../../lib/turnDiffTree";
9import {
10 ChevronsDownUpIcon,
11 ChevronsUpDownIcon,
12 ChevronRightIcon,
13 FileDiffIcon,
14 FolderIcon,
15 FolderClosedIcon,
16} from "lucide-react";
9import { ChevronRightIcon, FileDiffIcon } from "lucide-react";
10import { ChevronsDownUp, ChevronsUpDown, Folder, FolderClosed } from "lucide";
1711import { cn } from "~/lib/utils";
1812import { DiffStatLabel, hasNonZeroStat } from "./DiffStatLabel";
1913import { PierreEntryIcon } from "./PierreEntryIcon";
2014import { Button } from "../ui/button";
2115import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
2216import { MiddleTruncate } from "../ui/middle-truncate";
17import { MorphIcon } from "~/components/MorphIcon";
2318
2419const EMPTY_DIRECTORY_OVERRIDES: Record<string, boolean> = {};
2520
......@@ -86,11 +81,10 @@ export const ChangedFilesCard = memo(function ChangedFilesCard(props: {
8681 />
8782 }
8883 >
89 {allDirectoriesExpanded ? (
90 <ChevronsDownUpIcon className="size-3" />
91 ) : (
92 <ChevronsUpDownIcon className="size-3" />
93 )}
84 <MorphIcon
85 className="size-3"
86 icon={allDirectoriesExpanded ? ChevronsDownUp : ChevronsUpDown}
87 />
9488 </TooltipTrigger>
9589 <TooltipPopup side="top">
9690 {allDirectoriesExpanded ? "Collapse all folders" : "Expand all folders"}
......@@ -195,11 +189,10 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {
195189 isExpanded && "rotate-90",
196190 )}
197191 />
198 {isExpanded ? (
199 <FolderIcon className="size-3.5 shrink-0 text-muted-foreground/75" />
200 ) : (
201 <FolderClosedIcon className="size-3.5 shrink-0 text-muted-foreground/75" />
202 )}
192 <MorphIcon
193 className="size-3.5 shrink-0 text-muted-foreground/75"
194 icon={isExpanded ? Folder : FolderClosed}
195 />
203196 <span className="truncate font-mono text-2xs text-muted-foreground/90 group-hover:text-foreground/90">
204197 {node.name}
205198 </span>
apps/web/src/components/chat/ComposerPrimaryActions.tsx+4-11
......@@ -1,12 +1,7 @@
11import { memo, type MouseEventHandler, type PointerEventHandler } from "react";
2import {
3 CheckIcon,
4 ChevronDownIcon,
5 ChevronLeftIcon,
6 CornerUpRightIcon,
7 ListPlusIcon,
8 PlayIcon,
9} from "lucide-react";
2import { CheckIcon, ChevronDownIcon, ChevronLeftIcon, PlayIcon } from "lucide-react";
3import { CornerUpRight, ListPlus } from "lucide";
4import { MorphIcon } from "~/components/MorphIcon";
105import { useEnvironmentIdentificationMode } from "~/hooks/useSettings";
116import { cn } from "~/lib/utils";
127import { useShortcutModifierState } from "../../shortcutModifierState";
......@@ -315,10 +310,8 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({
315310 <PlayIcon className="size-4 fill-current" aria-hidden="true" />
316311 ) : isEditingQueuedMessage ? (
317312 <CheckIcon className="size-4" aria-hidden="true" />
318 ) : isQueuing ? (
319 <ListPlusIcon className="size-4" aria-hidden="true" />
320313 ) : isRunning ? (
321 <CornerUpRightIcon className="size-4" aria-hidden="true" />
314 <MorphIcon className="size-4" icon={isQueuing ? ListPlus : CornerUpRight} />
322315 ) : (
323316 <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
324317 <path
apps/web/src/components/chat/ExpandedImageDialog.tsx+8-3
......@@ -7,8 +7,10 @@ import {
77 type ReactNode,
88 type KeyboardEvent,
99} from "react";
10import { ChevronLeftIcon, ChevronRightIcon, ImageIcon, TextIcon, XIcon } from "lucide-react";
10import { ChevronLeftIcon, ChevronRightIcon, XIcon } from "lucide-react";
11import { Image as ImageGlyph, Text as TextGlyph } from "lucide";
1112import { Button } from "../ui/button";
13import { MorphIcon } from "~/components/MorphIcon";
1214import { Dialog, DialogPopup, DialogTitle } from "../ui/dialog";
1315import type { ExpandedImageItem, ExpandedImagePreview } from "./ExpandedImagePreview";
1416import { resolveExternalWebLinkHost } from "./externalLinkContextMenu";
......@@ -165,7 +167,6 @@ export const ExpandedImageDialog = memo(function ExpandedImageDialog({
165167 : accessibilityDetails?.format === "json"
166168 ? "Show accessibility JSON"
167169 : "Show extracted text";
168 const ContentsIcon = showingAccessibilityDetails ? ImageIcon : TextIcon;
169170
170171 return (
171172 <Dialog
......@@ -260,7 +261,11 @@ export const ExpandedImageDialog = memo(function ExpandedImageDialog({
260261 />
261262 }
262263 >
263 <ContentsIcon className="size-3" aria-hidden="true" />
264 <MorphIcon
265 className="size-3"
266 aria-hidden="true"
267 icon={showingAccessibilityDetails ? ImageGlyph : TextGlyph}
268 />
264269 </TooltipTrigger>
265270 <TooltipPopup side="top">{contentsLabel}</TooltipPopup>
266271 </Tooltip>
apps/web/src/components/chat/MessageCopyButton.tsx+7-2
......@@ -1,6 +1,8 @@
11import { memo, useRef } from "react";
2import { CopyIcon, CheckIcon } from "lucide-react";
2import { Check, Copy } from "lucide";
33import { Button } from "../ui/button";
4import { MorphIcon } from "~/components/MorphIcon";
5import { cn } from "~/lib/utils";
46import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
57import {
68 ANCHORED_COPY_TOAST_TIMEOUT_MS,
......@@ -47,7 +49,10 @@ export const MessageCopyButton = memo(function MessageCopyButton({
4749 />
4850 }
4951 >
50 {isCopied ? <CheckIcon className="size-3 text-primary" /> : <CopyIcon className="size-3" />}
52 <MorphIcon
53 className={cn("size-3", isCopied && "text-primary")}
54 icon={isCopied ? Check : Copy}
55 />
5156 </TooltipTrigger>
5257 <TooltipPopup>
5358 <p>Copy message</p>
apps/web/src/components/chat/MessagesTimeline.tsx+7-4
......@@ -140,12 +140,14 @@ import {
140140 XIcon,
141141 ZapIcon,
142142} from "lucide-react";
143import { ChevronDown, ChevronRight } from "lucide";
143144import type {
144145 ComposerContextId,
145146 ComposerContextRecord,
146147 KnownComposerContextRecord,
147148} from "@t3tools/contracts";
148149import { Button, InlineButton } from "../ui/button";
150import { MorphIcon } from "~/components/MorphIcon";
149151import { useAssetUrlRefresh, useAssetUrls, useAssetUrlState } from "../../assets/assetUrls";
150152import { MediaVideoPlayer } from "../media/MediaVideoPlayer";
151153import { getVirtualizedScrollFadeClassName } from "../ui/scroll-area";
......@@ -2426,7 +2428,6 @@ function TimelineRowTimestamp({
24262428
24272429function TurnFoldTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "turn-fold" }> }) {
24282430 const ctx = use(TimelineRowCtx);
2429 const Icon = row.expanded ? ChevronDownIcon : ChevronRightIcon;
24302431
24312432 return (
24322433 <div className="group/timeline-row relative flex items-center gap-1 border-b border-border/60 pb-2 pe-0.5 pt-1">
......@@ -2438,7 +2439,7 @@ function TurnFoldTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "turn-
24382439 className="flex cursor-pointer select-none items-center gap-1 rounded-md px-1 text-sm leading-relaxed text-muted-foreground tabular-nums transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"
24392440 >
24402441 <span>{row.label}</span>
2441 <Icon className="size-3.5" />
2442 <MorphIcon className="size-3.5" icon={row.expanded ? ChevronDown : ChevronRight} />
24422443 </button>
24432444 <TimelineRowTimestamp
24442445 createdAt={row.createdAt}
......@@ -2451,7 +2452,6 @@ function TurnFoldTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "turn-
24512452
24522453function AttemptFoldTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "attempt-fold" }> }) {
24532454 const ctx = use(TimelineRowCtx);
2454 const Icon = row.expanded ? ChevronDownIcon : ChevronRightIcon;
24552455
24562456 return (
24572457 <button
......@@ -2462,7 +2462,10 @@ function AttemptFoldTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "at
24622462 onClick={() => ctx.onToggleAttemptFold(row.attemptId)}
24632463 className="flex w-full cursor-pointer select-none items-center gap-2 rounded-md border border-border/60 bg-muted/20 px-2.5 py-2 text-left transition-colors hover:bg-muted/35 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"
24642464 >
2465 <Icon className="size-3.5 shrink-0 text-muted-foreground" />
2465 <MorphIcon
2466 className="size-3.5 shrink-0 text-muted-foreground"
2467 icon={row.expanded ? ChevronDown : ChevronRight}
2468 />
24662469 <span className="text-xs font-medium text-foreground/80">{row.label}</span>
24672470 <span className="text-2xs text-muted-foreground">Partial output retained</span>
24682471 </button>
apps/web/src/components/chat/PanelLayoutControls.tsx+4-12
......@@ -1,10 +1,6 @@
1import {
2 Maximize2Icon,
3 Minimize2Icon,
4 PanelBottomIcon,
5 PanelRightIcon,
6 SquareMenuIcon,
7} from "lucide-react";
1import { PanelBottomIcon, PanelRightIcon, SquareMenuIcon } from "lucide-react";
2import { Maximize2, Minimize2 } from "lucide";
3import { MorphIcon } from "~/components/MorphIcon";
84import { memo, type ReactElement } from "react";
95
106import type { ThreadPanelPresentation } from "../../rightPanelLayout";
......@@ -163,11 +159,7 @@ export const RightPanelMaximizeControl = memo(function RightPanelMaximizeControl
163159 variant="ghost"
164160 size="sm"
165161 >
166 {maximized ? (
167 <Minimize2Icon className="size-4" />
168 ) : (
169 <Maximize2Icon className="size-4" />
170 )}
162 <MorphIcon className="size-4" icon={maximized ? Minimize2 : Maximize2} />
171163 </Toggle>
172164 }
173165 />
apps/web/src/components/chat/ThreadDetailsPrRows.tsx+3-6
......@@ -5,7 +5,7 @@ import {
55 threadPullRequestKeyOf,
66 visibleThreadPullRequests,
77} from "@t3tools/shared/threadPullRequests";
8import { MinusIcon, PlusIcon } from "lucide-react";
8import { Minus, Plus } from "lucide";
99import { useState, type ComponentProps, type MouseEvent as ReactMouseEvent } from "react";
1010
1111import { findProjectOnChangeRequestHost, parseChangeRequestUrl } from "~/lib/openPullRequestLink";
......@@ -13,6 +13,7 @@ import { findProjectOnChangeRequestHost, parseChangeRequestUrl } from "~/lib/ope
1313import { useProjects } from "~/state/entities";
1414
1515import { pullRequestListLines } from "../pullRequest/pullRequestListLines";
16import { MorphIcon } from "~/components/MorphIcon";
1617import { linkedPullRequestSnapshotStatus, prStatusIndicator } from "../ThreadStatusIndicators";
1718
1819import { ThreadDetailsPrRow } from "./ThreadDetailsPrRow";
......@@ -96,11 +97,7 @@ export function ThreadDetailsPrRows({
9697 tone="muted"
9798 className="w-full active:scale-100"
9899 >
99 {expanded ? (
100 <MinusIcon aria-hidden className="size-4 shrink-0" />
101 ) : (
102 <PlusIcon aria-hidden className="size-4 shrink-0" />
103 )}
100 <MorphIcon aria-hidden className="size-4 shrink-0" icon={expanded ? Minus : Plus} />
104101 {expanded ? "Show less" : `Show ${rest.length} more`}
105102 </ThreadDetailsControl>
106103 </>
apps/web/src/components/chat/WorktreeSetupCard.tsx+3-3
......@@ -6,8 +6,6 @@ import {
66import { formatDuration } from "@t3tools/shared/orchestrationTiming";
77import {
88 CheckIcon,
9 ChevronDownIcon,
10 ChevronRightIcon,
119 CircleAlertIcon,
1210 CircleIcon,
1311 LaptopIcon,
......@@ -15,9 +13,11 @@ import {
1513 TerminalIcon,
1614 XIcon,
1715} from "lucide-react";
16import { ChevronDown, ChevronRight } from "lucide";
1817import { useEffect, useState, type ReactNode } from "react";
1918
2019import { Button } from "~/components/ui/button";
20import { MorphIcon } from "~/components/MorphIcon";
2121import { Spinner } from "~/components/ui/spinner";
2222import { MiddleTruncate } from "../ui/middle-truncate";
2323import { observeVisibleAnimation } from "~/lib/visibleAnimation";
......@@ -409,7 +409,7 @@ export function WorktreeSetupCard({
409409 aria-expanded={detailsOpen}
410410 onClick={() => setDetailsOpen((open) => !open)}
411411 >
412 {detailsOpen ? <ChevronDownIcon aria-hidden /> : <ChevronRightIcon aria-hidden />}
412 <MorphIcon aria-hidden icon={detailsOpen ? ChevronDown : ChevronRight} />
413413 Details
414414 </Button>
415415 {showTerminal ? (
apps/web/src/components/device/DeviceControlsRail.tsx+3-3
......@@ -6,7 +6,6 @@ import {
66 Keyboard,
77 Box,
88 Maximize,
9 Moon,
109 MoreHorizontal,
1110 PictureInPicture2,
1211 Power,
......@@ -14,12 +13,13 @@ import {
1413 SlidersHorizontal,
1514 Smartphone,
1615 Square,
17 Sun,
1816 Type,
1917 X,
2018} from "lucide-react";
19import { Moon, Sun } from "lucide";
2120import type { ReactNode } from "react";
2221import { Button } from "~/components/ui/button";
22import { MorphIcon } from "~/components/MorphIcon";
2323import {
2424 Menu,
2525 MenuItem,
......@@ -132,7 +132,7 @@ export function DeviceControlsRail(props: {
132132 disabled={controls.disabled || !settings?.appearance}
133133 onClick={() => void controls.act({ type: "setAppearance", value: nextAppearance })}
134134 >
135 {settings?.appearance === "dark" ? <Sun /> : <Moon />}
135 <MorphIcon icon={settings?.appearance === "dark" ? Sun : Moon} />
136136 </RailButton>
137137 <Menu>
138138 <MenuTrigger
apps/web/src/components/device/DeviceSetup.tsx+5-3
......@@ -1,10 +1,12 @@
11import { DeviceHostUpdates } from "./DeviceHostUpdates";
22import type { DevicePlatform, DeviceServiceState, EnvironmentId } from "@t3tools/contracts";
3import { Check, CircleAlert } from "lucide-react";
3import { Check } from "lucide-react";
4import { Check as CheckGlyph, CircleAlert } from "lucide";
45import { useState } from "react";
56
67import { Button } from "~/components/ui/button";
78import { DialogClose } from "~/components/ui/dialog";
9import { MorphIcon } from "~/components/MorphIcon";
810import { WizardHeader, WizardPanel, WizardSteps, WizardFooter } from "~/components/ui/wizard";
911import { Spinner } from "~/components/ui/spinner";
1012import { Switch } from "~/components/ui/switch";
......@@ -287,16 +289,16 @@ export function PlatformStatus(props: {
287289 readonly status: { readonly ready: boolean; readonly message: string };
288290 readonly compact?: boolean;
289291}) {
290 const Icon = props.status.ready ? Check : CircleAlert;
291292 return (
292293 <div
293294 className={cn("flex gap-2", !props.compact && "rounded-md border border-border/60 px-3 py-2")}
294295 >
295 <Icon
296 <MorphIcon
296297 className={cn(
297298 "mt-0.5 size-4 shrink-0",
298299 props.status.ready ? "text-success" : "text-muted-foreground",
299300 )}
301 icon={props.status.ready ? CheckGlyph : CircleAlert}
300302 />
301303 <div className={cn(props.compact && props.status.ready && "flex items-center gap-2")}>
302304 <p className="font-medium">{props.platform}</p>
apps/web/src/components/diffs/DiffFileTree.tsx+6-6
......@@ -1,6 +1,6 @@
11import type { GitStatusEntry } from "@pierre/trees";
22import { FileTree, useFileTree, useFileTreeSelector } from "@pierre/trees/react";
3import { ChevronsDownUpIcon, ChevronsUpDownIcon } from "lucide-react";
3import { ChevronsDownUp, ChevronsUpDown } from "lucide";
44import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
55
66import { useTheme } from "~/hooks/useTheme";
......@@ -10,6 +10,7 @@ import { PIERRE_TREE_UNSAFE_CSS, pierreTreeStyle } from "~/pierre-tree-theme";
1010
1111import { areAllDirectoriesExpanded, setAllDirectoriesExpanded } from "../files/fileTreeExpansion";
1212import { Button } from "../ui/button";
13import { MorphIcon } from "~/components/MorphIcon";
1314import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
1415import {
1516 buildDiffFileTreeUpdates,
......@@ -189,11 +190,10 @@ export function DiffFileTree({
189190 />
190191 }
191192 >
192 {allDirectoriesExpanded ? (
193 <ChevronsDownUpIcon className="size-3.5" />
194 ) : (
195 <ChevronsUpDownIcon className="size-3.5" />
196 )}
193 <MorphIcon
194 className="size-3.5"
195 icon={allDirectoriesExpanded ? ChevronsDownUp : ChevronsUpDown}
196 />
197197 </TooltipTrigger>
198198 <TooltipPopup>
199199 {allDirectoriesExpanded ? "Collapse all folders" : "Expand all folders"}
apps/web/src/components/files/AttachmentFilePreview.tsx+8-20
......@@ -3,22 +3,13 @@ import type { EnvironmentId } from "@t3tools/contracts";
33import { formatAttachmentSize } from "@t3tools/client-runtime/state/attachments";
44import { readFilePreviewResponse } from "@t3tools/client-runtime/file-preview";
55import { filePreviewKind, FILE_TEXT_PREVIEW_MAX_BYTES } from "@t3tools/shared/filePreview";
6import {
7 CheckIcon,
8 ChevronRightIcon,
9 Code2,
10 CopyIcon,
11 DownloadIcon,
12 Eye,
13 Table2,
14 Trash2Icon,
15 WrapTextIcon,
16 XIcon,
17} from "lucide-react";
6import { ChevronRightIcon, DownloadIcon, Trash2Icon, WrapTextIcon, XIcon } from "lucide-react";
7import { Check, Code2, Copy, Eye, Table2 } from "lucide";
188import { lazy, Suspense, useEffect, useMemo, useRef, useState } from "react";
199
2010import { useAssetUrlRefresh } from "~/assets/assetUrls";
2111import ChatMarkdown from "~/components/ChatMarkdown";
12import { MorphIcon } from "~/components/MorphIcon";
2213import { ScrollArea } from "~/components/ui/scroll-area";
2314import { toastManager } from "~/components/ui/toast";
2415import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
......@@ -305,13 +296,10 @@ export function AttachmentFilePreview(props: {
305296 pressed={rendered}
306297 onPress={() => setRendered((value) => !value)}
307298 >
308 {rendered ? (
309 <Code2 className="size-3.5" />
310 ) : renderedMode === "table" ? (
311 <Table2 className="size-3.5" />
312 ) : (
313 <Eye className="size-3.5" />
314 )}
299 <MorphIcon
300 className="size-3.5"
301 icon={rendered ? Code2 : renderedMode === "table" ? Table2 : Eye}
302 />
315303 </FileSurfaceAction>
316304 ) : null}
317305 {showsRawText ? (
......@@ -328,7 +316,7 @@ export function AttachmentFilePreview(props: {
328316 label={isCopied ? "Copied" : content.truncated ? "Copy preview" : "Copy contents"}
329317 onPress={() => copyToClipboard(content.text, undefined)}
330318 >
331 {isCopied ? <CheckIcon className="size-3.5" /> : <CopyIcon className="size-3.5" />}
319 <MorphIcon className="size-3.5" icon={isCopied ? Check : Copy} />
332320 </FileSurfaceAction>
333321 ) : null}
334322 {url ? (
apps/web/src/components/files/FileBrowserPanel.tsx+6-6
......@@ -6,11 +6,12 @@ import type {
66import type { EnvironmentId, ProjectEntry } from "@t3tools/contracts";
77import { FileTree, useFileTree, useFileTreeSearch, useFileTreeSelector } from "@pierre/trees/react";
88import { serializeComposerFileLink } from "@t3tools/shared/composerTrigger";
9import { ChevronsDownUpIcon, ChevronsUpDownIcon } from "lucide-react";
9import { ChevronsDownUp, ChevronsUpDown } from "lucide";
1010import { useEffect, useMemo, useRef, useState } from "react";
1111
1212import { Button } from "~/components/ui/button";
1313import { InputGroup, InputGroupInput } from "~/components/ui/input-group";
14import { MorphIcon } from "~/components/MorphIcon";
1415import { toastManager } from "~/components/ui/toast";
1516import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip";
1617import { useComposerHandleContext } from "~/composerHandleContext";
......@@ -516,11 +517,10 @@ export default function FileBrowserPanel({
516517 />
517518 }
518519 >
519 {allDirectoriesExpanded ? (
520 <ChevronsDownUpIcon className="size-3.5" />
521 ) : (
522 <ChevronsUpDownIcon className="size-3.5" />
523 )}
520 <MorphIcon
521 className="size-3.5"
522 icon={allDirectoriesExpanded ? ChevronsDownUp : ChevronsUpDown}
523 />
524524 </TooltipTrigger>
525525 <TooltipPopup>
526526 {expandAll || allDirectoriesExpanded ? "Collapse all folders" : "Expand all folders"}
apps/web/src/components/files/FilePreviewPanel.tsx+7-8
......@@ -21,7 +21,8 @@ import {
2121 squashAtomCommandFailure,
2222} from "@t3tools/client-runtime/state/runtime";
2323import { mediaFileReference } from "@t3tools/client-runtime/media-reference";
24import { Code2, Eye, FolderTree, Globe2, Table2, WrapTextIcon } from "lucide-react";
24import { FolderTree, Globe2, WrapTextIcon } from "lucide-react";
25import { Code2, Eye, Table2 } from "lucide";
2526import * as Schema from "effect/Schema";
2627import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2728
......@@ -30,6 +31,7 @@ import { useAssetUrlRefresh, useAssetUrlState } from "~/assets/assetUrls";
3031import { OpenInPicker } from "~/components/chat/OpenInPicker";
3132import { MediaVideoPlayer } from "~/components/media/MediaVideoPlayer";
3233import { MediaActions, type MediaActionSource } from "~/components/media/MediaActions";
34import { MorphIcon } from "~/components/MorphIcon";
3335import { useRemoteOpenState } from "~/remoteOpen";
3436import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings";
3537import { useTheme } from "~/hooks/useTheme";
......@@ -1140,13 +1142,10 @@ export default function FilePreviewPanel({
11401142 );
11411143 }}
11421144 >
1143 {rendered ? (
1144 <Code2 className="size-3.5" />
1145 ) : renderedMode === "table" ? (
1146 <Table2 className="size-3.5" />
1147 ) : (
1148 <Eye className="size-3.5" />
1149 )}
1145 <MorphIcon
1146 className="size-3.5"
1147 icon={rendered ? Code2 : renderedMode === "table" ? Table2 : Eye}
1148 />
11501149 </FileSurfaceAction>
11511150 ) : null}
11521151 {showsRawText ? (
apps/web/src/components/onboarding/WelcomeWizard.tsx+3-2
......@@ -26,11 +26,11 @@ import {
2626 CheckIcon,
2727 ChevronRightIcon,
2828 CloudIcon,
29 CopyIcon,
3029 LinkIcon,
3130 MonitorIcon,
3231 TerminalIcon,
3332} from "lucide-react";
33import { Check, Copy } from "lucide";
3434import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3535
3636import { TYPOGRAPHY_ADVANCED_STORAGE_KEY } from "../../appearanceFonts";
......@@ -77,6 +77,7 @@ import { T3Wordmark } from "../T3Wordmark";
7777import { Alert, AlertDescription } from "../ui/alert";
7878import { Button } from "../ui/button";
7979import { Checkbox } from "../ui/checkbox";
80import { MorphIcon } from "~/components/MorphIcon";
8081import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible";
8182import { Input } from "../ui/input";
8283import { Tooltip, TooltipTrigger, TooltipPopup } from "../ui/tooltip";
......@@ -1744,7 +1745,7 @@ function CommandBlock({
17441745 aria-label="Copy command"
17451746 onClick={() => copyToClipboard(command, undefined)}
17461747 >
1747 {isCopied ? <CheckIcon className="size-3.5" /> : <CopyIcon className="size-3.5" />}
1748 <MorphIcon className="size-3.5" icon={isCopied ? Check : Copy} />
17481749 </Button>
17491750 </div>
17501751 );
apps/web/src/components/pullRequest/PullRequestCodeTab.tsx+7-12
......@@ -13,8 +13,6 @@ import type {
1313import {
1414 ChevronDownIcon,
1515 ChevronRightIcon,
16 ChevronsDownUpIcon,
17 ChevronsUpDownIcon,
1816 Columns2Icon,
1917 FolderTreeIcon,
2018 InfoIcon,
......@@ -24,6 +22,7 @@ import {
2422 TextWrapIcon,
2523 TriangleAlertIcon,
2624} from "lucide-react";
25import { ChevronDown, ChevronRight, ChevronsDownUp, ChevronsUpDown } from "lucide";
2726import { useAtomRefresh } from "@effect/atom-react";
2827import * as Schema from "effect/Schema";
2928import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
......@@ -64,6 +63,7 @@ import { useCodeViewFileReveal } from "../diffs/useCodeViewFileReveal";
6463import { diffFileTreeEntries } from "../diffs/diffFileTree.logic";
6564import { StyledDiffCodeView } from "../diffs/StyledDiffCodeView";
6665import { Button } from "../ui/button";
66import { MorphIcon } from "~/components/MorphIcon";
6767import { Checkbox } from "../ui/checkbox";
6868import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible";
6969import {
......@@ -791,11 +791,7 @@ function PullRequestCodeTab({
791791 toggleFile(item.id);
792792 }}
793793 >
794 {collapsed ? (
795 <ChevronRightIcon className="size-4" />
796 ) : (
797 <ChevronDownIcon className="size-4" />
798 )}
794 <MorphIcon className="size-4" icon={collapsed ? ChevronRight : ChevronDown} />
799795 </Button>
800796 );
801797 },
......@@ -1249,11 +1245,10 @@ function PullRequestCodeTab({
12491245 />
12501246 }
12511247 >
1252 {allFilesCollapsed ? (
1253 <ChevronsUpDownIcon className="size-3.5" />
1254 ) : (
1255 <ChevronsDownUpIcon className="size-3.5" />
1256 )}
1248 <MorphIcon
1249 className="size-3.5"
1250 icon={allFilesCollapsed ? ChevronsUpDown : ChevronsDownUp}
1251 />
12571252 </TooltipTrigger>
12581253 <TooltipPopup side="top">
12591254 {allFilesCollapsed ? "Expand all files" : "Collapse all files"}
apps/web/src/components/pullRequest/PullRequestReviewAnnotation.tsx+7-12
......@@ -9,19 +9,15 @@ import type {
99 PullRequestThreadCommentsResult,
1010 PullRequestThreadComment,
1111} from "@t3tools/contracts";
12import {
13 CheckCircle2Icon,
14 CircleIcon,
15 HammerIcon,
16 MessageSquareIcon,
17 Trash2Icon,
18} from "lucide-react";
12import { HammerIcon, MessageSquareIcon, Trash2Icon } from "lucide-react";
13import { Circle, CircleCheck } from "lucide";
1914import { useRef, useState } from "react";
2015
2116import { formatRelativeTimeLabel } from "~/timestampFormat";
2217import { cn } from "~/lib/utils";
2318
2419import { Button } from "../ui/button";
20import { MorphIcon } from "~/components/MorphIcon";
2521import { PullRequestEditButton } from "./PullRequestEditButton";
2622import { Textarea } from "../ui/textarea";
2723import { isCommentSubmitShortcut } from "../diffs/commentSubmitShortcut";
......@@ -219,11 +215,10 @@ export function ReviewThreadCard({
219215 onPointerDown={(event) => event.stopPropagation()}
220216 >
221217 <div className="flex items-center gap-2 text-xs text-muted-foreground">
222 {thread.isResolved ? (
223 <CheckCircle2Icon className="size-3.5 text-success-foreground" />
224 ) : (
225 <CircleIcon className="size-3.5" />
226 )}
218 <MorphIcon
219 className={cn("size-3.5", thread.isResolved && "text-success-foreground")}
220 icon={thread.isResolved ? CircleCheck : Circle}
221 />
227222 <button
228223 type="button"
229224 className="hover:text-foreground"
apps/web/src/components/settings/DiagnosticsSettings.tsx+4-10
......@@ -1,13 +1,7 @@
11import { ProcessSignalActions } from "./ProcessSignalActions";
22import { RefreshIcon } from "~/components/ui/refresh-icon";
3import {
4 AlertTriangleIcon,
5 ChevronDownIcon,
6 ChevronRightIcon,
7 CopyIcon,
8 FolderOpenIcon,
9 InfoIcon,
10} from "lucide-react";
3import { AlertTriangleIcon, CopyIcon, FolderOpenIcon, InfoIcon } from "lucide-react";
4import { ChevronDown, ChevronRight } from "lucide";
115import {
126 isAtomCommandInterrupted,
137 squashAtomCommandFailure,
......@@ -30,6 +24,7 @@ import { serverEnvironment } from "../../state/server";
3024import { shellEnvironment } from "../../state/shell";
3125import { useCopyToClipboard } from "../../hooks/useCopyToClipboard";
3226import { Button } from "../ui/button";
27import { MorphIcon } from "~/components/MorphIcon";
3328import { ScrollArea } from "../ui/scroll-area";
3429import { Toggle, ToggleGroup } from "../ui/toggle-group";
3530import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
......@@ -268,7 +263,6 @@ function ProcessNameCell({
268263}) {
269264 const name = formatProcessName(process.command);
270265 const hasChildren = process.childPids.length > 0;
271 const ChevronIcon = isExpanded ? ChevronDownIcon : ChevronRightIcon;
272266
273267 return (
274268 <div
......@@ -282,7 +276,7 @@ function ProcessNameCell({
282276 aria-label={isExpanded ? `Collapse ${name}` : `Expand ${name}`}
283277 onClick={() => onToggle(process.pid)}
284278 >
285 <ChevronIcon className="size-3.5" />
279 <MorphIcon className="size-3.5" icon={isExpanded ? ChevronDown : ChevronRight} />
286280 </Button>
287281 ) : (
288282 <span className="size-5 shrink-0" aria-hidden="true" />
apps/web/src/components/settings/ProviderInstanceCard.tsx+6-7
......@@ -7,13 +7,12 @@ import {
77 ArrowUpCircleIcon,
88 CopyIcon,
99 DownloadIcon,
10 LockIcon,
11 LockOpenIcon,
1210 ExternalLinkIcon,
1311 PlusIcon,
1412 Trash2Icon,
1513 XIcon,
1614} from "lucide-react";
15import { Lock as LockGlyph, LockOpen } from "lucide";
1716import * as Arr from "effect/Array";
1817import * as Result from "effect/Result";
1918import { useEffect, useRef, useState, type ReactElement, type ReactNode } from "react";
......@@ -41,6 +40,7 @@ import { useCopyToClipboard } from "../../hooks/useCopyToClipboard";
4140import { normalizeProviderAccentColor } from "../../providerInstances";
4241import { Badge } from "../ui/badge";
4342import { Button } from "../ui/button";
43import { MorphIcon } from "~/components/MorphIcon";
4444import { DraftInput } from "../ui/draft-input";
4545import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";
4646import { Switch } from "../ui/switch";
......@@ -438,11 +438,10 @@ function ProviderEnvironmentSection(props: {
438438 aria-pressed={variable.sensitive}
439439 aria-label={`Mark environment variable ${variable.name || index + 1} as sensitive`}
440440 >
441 {variable.sensitive ? (
442 <LockIcon className="size-3" />
443 ) : (
444 <LockOpenIcon className="size-3" />
445 )}
441 <MorphIcon
442 className="size-3"
443 icon={variable.sensitive ? LockGlyph : LockOpen}
444 />
446445 </Button>
447446 }
448447 />
apps/web/src/components/settings/ResourceTelemetryDiagnostics.tsx+3-4
......@@ -4,14 +4,13 @@ import {
44 ActivityIcon,
55 AlertTriangleIcon,
66 BatteryIcon,
7 ChevronDownIcon,
8 ChevronRightIcon,
97 CpuIcon,
108 DatabaseIcon,
119 GaugeIcon,
1210 HardDriveIcon,
1311 MemoryStickIcon,
1412} from "lucide-react";
13import { ChevronDown, ChevronRight } from "lucide";
1514import type {
1615 BackgroundBooleanState,
1716 EnvironmentId,
......@@ -44,6 +43,7 @@ import { serverEnvironment } from "../../state/server";
4443import { useAtomCommand } from "../../state/use-atom-command";
4544import { formatRelativeTime } from "../../timestampFormat";
4645import { Button } from "../ui/button";
46import { MorphIcon } from "~/components/MorphIcon";
4747import { ScrollArea } from "../ui/scroll-area";
4848import { Toggle, ToggleGroup } from "../ui/toggle-group";
4949import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
......@@ -480,7 +480,6 @@ function ProcessTreeName({
480480}) {
481481 const name = formatProcessName(process);
482482 const hasChildren = process.childPids.length > 0;
483 const ChevronIcon = collapsed ? ChevronRightIcon : ChevronDownIcon;
484483 return (
485484 <div
486485 className="grid min-w-0 grid-cols-[1.25rem_0.375rem_minmax(0,1fr)] items-center gap-2"
......@@ -493,7 +492,7 @@ function ProcessTreeName({
493492 onClick={() => onToggle(process)}
494493 aria-label={collapsed ? `Expand ${name}` : `Collapse ${name}`}
495494 >
496 <ChevronIcon className="size-3.5" />
495 <MorphIcon className="size-3.5" icon={collapsed ? ChevronRight : ChevronDown} />
497496 </Button>
498497 ) : (
499498 <span className="size-5" aria-hidden />
apps/web/src/components/settings/ThemeEditorPanel.tsx+4-9
......@@ -1,11 +1,5 @@
1import {
2 ChevronDownIcon,
3 ChevronUpIcon,
4 MousePointer2Icon,
5 PaintbrushIcon,
6 PlusIcon,
7 XIcon,
8} from "lucide-react";
1import { MousePointer2Icon, PaintbrushIcon, PlusIcon, XIcon } from "lucide-react";
2import { ChevronDown, ChevronUp } from "lucide";
93import {
104 useCallback,
115 useEffect,
......@@ -36,6 +30,7 @@ import {
3630} from "../../themePalette";
3731import { cn } from "../../lib/utils";
3832import { Button } from "../ui/button";
33import { MorphIcon } from "~/components/MorphIcon";
3934import { Input } from "../ui/input";
4035import { Switch } from "../ui/switch";
4136import { Toggle, ToggleGroup } from "../ui/toggle-group";
......@@ -1221,7 +1216,7 @@ export function ThemeEditorPanel({
12211216 variant="ghost"
12221217 onClick={() => setIsMinimized(!isMinimized)}
12231218 >
1224 {isMinimized ? <ChevronUpIcon /> : <ChevronDownIcon />}
1219 <MorphIcon icon={isMinimized ? ChevronUp : ChevronDown} />
12251220 </Button>
12261221 <Button
12271222 aria-label="Close the theme editor"
apps/web/src/components/ui/sidebar.tsx+3-2
......@@ -1,11 +1,12 @@
11import { mergeProps } from "@base-ui/react/merge-props";
22import { useRender } from "@base-ui/react/use-render";
33import { cva, type VariantProps } from "class-variance-authority";
4import { PanelLeftCloseIcon, PanelLeftIcon } from "lucide-react";
4import { PanelLeft, PanelLeftClose } from "lucide";
55import * as React from "react";
66import { cn } from "~/lib/utils";
77import { Button } from "~/components/ui/button";
88import { Input, type InputProps } from "~/components/ui/input";
9import { MorphIcon } from "~/components/MorphIcon";
910import { ScrollArea } from "~/components/ui/scroll-area";
1011import {
1112 Sheet,
......@@ -342,7 +343,7 @@ function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<t
342343 variant="ghost"
343344 {...props}
344345 >
345 {isOpen ? <PanelLeftCloseIcon className="size-4" /> : <PanelLeftIcon className="size-4" />}
346 <MorphIcon className="size-4" icon={isOpen ? PanelLeftClose : PanelLeft} />
346347 <span className="sr-only">Toggle Sidebar</span>
347348 </Button>
348349 );
apps/web/src/components/ui/toast.tsx+18-30
......@@ -14,20 +14,12 @@ import {
1414} from "react";
1515import { useParams } from "@tanstack/react-router";
1616import { type ScopedThreadRef, type ThreadId } from "@t3tools/contracts";
17import {
18 CheckIcon,
19 ChevronDownIcon,
20 ChevronUpIcon,
21 CircleAlertIcon,
22 CircleCheckIcon,
23 CopyIcon,
24 InfoIcon,
25 TriangleAlertIcon,
26 XIcon,
27} from "lucide-react";
17import { CircleAlertIcon, CircleCheckIcon, InfoIcon, TriangleAlertIcon, XIcon } from "lucide-react";
18import { Check, ChevronDown, ChevronUp, Copy } from "lucide";
2819
2920import { cn } from "~/lib/utils";
3021import { Button, buttonVariants } from "~/components/ui/button";
22import { MorphIcon } from "~/components/MorphIcon";
3123import { useComposerDraftStore } from "~/composerDraftStore";
3224import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
3325import { resolveThreadRouteTarget } from "~/threadRoutes";
......@@ -135,7 +127,10 @@ function CopyErrorButton({ text }: { text: string }) {
135127 />
136128 }
137129 >
138 {isCopied ? <CheckIcon className="size-3 text-success" /> : <CopyIcon className="size-3" />}
130 <MorphIcon
131 className={cn("size-3", isCopied && "text-success")}
132 icon={isCopied ? Check : Copy}
133 />
139134 </TooltipTrigger>
140135 <TooltipPopup side="top">{label}</TooltipPopup>
141136 </Tooltip>
......@@ -165,11 +160,11 @@ function ToastExpandableSection({
165160 onClick={() => setOpen((prev) => !prev)}
166161 type="button"
167162 >
168 {open ? (
169 <ChevronUpIcon className="size-3.5 shrink-0 opacity-80" strokeWidth={2.25} />
170 ) : (
171 <ChevronDownIcon className="size-3.5 shrink-0 opacity-80" strokeWidth={2.25} />
172 )}
163 <MorphIcon
164 className="size-3.5 shrink-0 opacity-80"
165 icon={open ? ChevronUp : ChevronDown}
166 strokeWidth={2.25}
167 />
173168 {open ? collapseLabel : expandLabel}
174169 </button>
175170 {open ? <div className={toastExpandablePanelClassName}>{children}</div> : null}
......@@ -249,19 +244,12 @@ function ToastDescriptionAndExpandable({
249244 data-slot="toast-description"
250245 />
251246 </div>
252 {open ? (
253 <ChevronUpIcon
254 aria-hidden
255 className="mt-0.5 size-3.5 shrink-0 text-muted-foreground opacity-80"
256 strokeWidth={2.25}
257 />
258 ) : (
259 <ChevronDownIcon
260 aria-hidden
261 className="mt-0.5 size-3.5 shrink-0 text-muted-foreground opacity-80"
262 strokeWidth={2.25}
263 />
264 )}
247 <MorphIcon
248 aria-hidden
249 className="mt-0.5 size-3.5 shrink-0 text-muted-foreground opacity-80"
250 icon={open ? ChevronUp : ChevronDown}
251 strokeWidth={2.25}
252 />
265253 </TooltipTrigger>
266254 <TooltipPopup side="top">{open ? collapseLabel : expandLabel}</TooltipPopup>
267255 </Tooltip>
apps/web/src/routes/__root.tsx+4-2
......@@ -11,7 +11,7 @@ import {
1111 useNavigate,
1212 useRouter,
1313} from "@tanstack/react-router";
14import { CheckIcon, CopyIcon } from "lucide-react";
14import { Check, Copy } from "lucide";
1515import { useEffect, useEffectEvent, useMemo, useRef, useState } from "react";
1616
1717import { APP_BASE_NAME, APP_DISPLAY_NAME, APP_STAGE_LABEL, APP_VERSION } from "../branding";
......@@ -40,6 +40,7 @@ import { useCopyToClipboard } from "../hooks/useCopyToClipboard";
4040import { useDefaultThemeAdoption } from "../hooks/useDefaultTheme";
4141import { useEnvironmentThemeSync } from "../hooks/useEnvironmentTheme";
4242import { Button } from "../components/ui/button";
43import { MorphIcon } from "~/components/MorphIcon";
4344import { StandalonePage, StandalonePageHeader } from "../components/ui/standalone-page";
4445import {
4546 AnchoredToastProvider,
......@@ -49,6 +50,7 @@ import {
4950} from "../components/ui/toast";
5051import { resolveAndPersistPreferredEditor } from "../editorPreferences";
5152import { isElectron } from "../env";
53import { cn } from "../lib/utils";
5254import { applyAppearanceFontVariables } from "~/appearanceFonts";
5355import { applyAppearanceContrast } from "~/appearanceContrast";
5456import { useClientSettings } from "../hooks/useSettings";
......@@ -418,7 +420,7 @@ function CopyErrorButton({ report }: { report: string }) {
418420
419421 return (
420422 <Button size="sm" variant="outline" onClick={() => copyToClipboard(report)}>
421 {isCopied ? <CheckIcon className="text-success" /> : <CopyIcon />}
423 <MorphIcon className={cn(isCopied && "text-success")} icon={isCopied ? Check : Copy} />
422424 {isCopied ? "Copied" : "Copy error"}
423425 </Button>
424426 );
pnpm-lock.yaml+101-2
......@@ -640,7 +640,7 @@ importers:
640640 version: 0.9.0
641641 '@legendapp/list':
642642 specifier: 'catalog:'
643 version: 3.3.5(patch_hash=680cc6a5c5b4a4032e467e7b3fde22f89a84c0ee2e6eac6fda737d6277cc0806)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
643 version: 3.3.5(patch_hash=680cc6a5c5b4a4032e467e7b3fde22f89a84c0ee2e6eac6fda737d6277cc0806)(react-dom@19.2.6(react@19.2.6))(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6)
644644 '@noble/hashes':
645645 specifier: 'catalog:'
646646 version: 1.8.0
......@@ -716,12 +716,18 @@ importers:
716716 jszip:
717717 specifier: 3.10.1
718718 version: 3.10.1
719 lucide:
720 specifier: ^0.564.0
721 version: 0.564.0
719722 lucide-react:
720723 specifier: ^0.564.0
721724 version: 0.564.0(react@19.2.6)
722725 mermaid:
723726 specifier: ^11.17.2
724727 version: 11.17.2
728 morphicons:
729 specifier: ^1.7.1
730 version: 1.7.1(react-native-svg@15.15.5(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6))(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6)
725731 react:
726732 specifier: 19.2.6
727733 version: 19.2.6
......@@ -8814,6 +8820,9 @@ packages:
88148820 peerDependencies:
88158821 react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
88168822
8823 lucide@0.564.0:
8824 resolution: {integrity: sha512-FasyXKHWon773WIl3HeCQpd5xS6E0aLjqxiQStlHNKktni+HDncc1sqY+6vRUbCfmDsIaKQz43EEQLAUDLZO0g==}
8825
88178826 lz-string@1.5.0:
88188827 resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
88198828 hasBin: true
......@@ -9200,6 +9209,26 @@ packages:
92009209 socks:
92019210 optional: true
92029211
9212 morphicons@1.7.1:
9213 resolution: {integrity: sha512-q5ylxy5/d7vBg0OAzanlooXf05PekovMYDuuQVpr6vAQZxl99lrJbaIi+jJ32PXQf9WEEaDO2pbNBsx1ZhEnFQ==}
9214 peerDependencies:
9215 react: '>=18'
9216 react-native: '>=0.71'
9217 react-native-svg: '>=14'
9218 svelte: '>=5'
9219 vue: '>=3.3'
9220 peerDependenciesMeta:
9221 react:
9222 optional: true
9223 react-native:
9224 optional: true
9225 react-native-svg:
9226 optional: true
9227 svelte:
9228 optional: true
9229 vue:
9230 optional: true
9231
92039232 mrmime@2.0.1:
92049233 resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
92059234 engines: {node: '>=10'}
......@@ -13988,12 +14017,13 @@ snapshots:
1398814017 dependencies:
1398914018 jsbi: 4.3.2
1399014019
13991 '@legendapp/list@3.3.5(patch_hash=680cc6a5c5b4a4032e467e7b3fde22f89a84c0ee2e6eac6fda737d6277cc0806)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
14020 '@legendapp/list@3.3.5(patch_hash=680cc6a5c5b4a4032e467e7b3fde22f89a84c0ee2e6eac6fda737d6277cc0806)(react-dom@19.2.6(react@19.2.6))(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6)':
1399214021 dependencies:
1399314022 react: 19.2.6
1399414023 use-sync-external-store: 1.6.0(react@19.2.6)
1399514024 optionalDependencies:
1399614025 react-dom: 19.2.6(react@19.2.6)
14026 react-native: 0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6)
1399714027
1399814028 '@legendapp/list@3.3.5(patch_hash=680cc6a5c5b4a4032e467e7b3fde22f89a84c0ee2e6eac6fda737d6277cc0806)(react-dom@19.3.0(react@19.3.0))(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(utf-8-validate@6.0.6))(react@19.3.0)':
1399914029 dependencies:
......@@ -14921,6 +14951,16 @@ snapshots:
1492114951
1492214952 '@react-native/normalize-colors@0.88.0-rc.3': {}
1492314953
14954 '@react-native/virtualized-lists@0.88.0-rc.3(@types/react@19.3.0)(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6)':
14955 dependencies:
14956 invariant: 2.2.4
14957 nullthrows: 1.1.1
14958 react: 19.2.6
14959 react-native: 0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6)
14960 optionalDependencies:
14961 '@types/react': 19.3.0
14962 optional: true
14963
1492414964 '@react-native/virtualized-lists@0.88.0-rc.3(@types/react@19.3.0)(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(utf-8-validate@6.0.6))(react@19.3.0)':
1492514965 dependencies:
1492614966 invariant: 2.2.4
......@@ -19285,6 +19325,8 @@ snapshots:
1928519325 dependencies:
1928619326 react: 19.2.6
1928719327
19328 lucide@0.564.0: {}
19329
1928819330 lz-string@1.5.0: {}
1928919331
1929019332 magic-string@0.30.21:
......@@ -19986,6 +20028,12 @@ snapshots:
1998620028 socks: 2.8.9
1998720029 optional: true
1998820030
20031 morphicons@1.7.1(react-native-svg@15.15.5(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6))(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6):
20032 optionalDependencies:
20033 react: 19.2.6
20034 react-native: 0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6)
20035 react-native-svg: 15.15.5(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6)
20036
1998920037 mrmime@2.0.1: {}
1999020038
1999120039 ms@2.0.0: {}
......@@ -20885,6 +20933,14 @@ snapshots:
2088520933 react: 19.3.0
2088620934 react-native: 0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(utf-8-validate@6.0.6)
2088720935
20936 react-native-svg@15.15.5(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6):
20937 dependencies:
20938 css-select: 5.2.2
20939 css-tree: 1.1.3
20940 react: 19.2.6
20941 react-native: 0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6)
20942 optional: true
20943
2088820944 react-native-svg@15.15.5(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(utf-8-validate@6.0.6))(react@19.3.0):
2088920945 dependencies:
2089020946 css-select: 5.2.2
......@@ -20928,6 +20984,49 @@ snapshots:
2092820984 transitivePeerDependencies:
2092920985 - supports-color
2093020986
20987 react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6):
20988 dependencies:
20989 '@react-native/asset-utils': 0.88.0-rc.3
20990 '@react-native/codegen': 0.88.0-rc.3(@babel/core@7.29.7)
20991 '@react-native/community-cli-plugin': 0.88.0-rc.3(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bufferutil@4.1.0)(utf-8-validate@6.0.6)
20992 '@react-native/gradle-plugin': 0.88.0-rc.3
20993 '@react-native/normalize-colors': 0.88.0-rc.3
20994 '@react-native/virtualized-lists': 0.88.0-rc.3(@types/react@19.3.0)(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.2.6)(utf-8-validate@6.0.6))(react@19.2.6)
20995 anser: 1.4.10
20996 ansi-regex: 5.0.1
20997 base64-js: 1.5.1
20998 flow-enums-runtime: 0.0.6
20999 flow-parser: 0.327.0
21000 hermes-compiler: 260318099.0.4
21001 invariant: 2.2.4
21002 memoize-one: 5.2.1
21003 metro-runtime: 0.87.1
21004 metro-source-map: 0.87.1
21005 nullthrows: 1.1.1
21006 pretty-format: 29.7.0
21007 promise: 8.3.0
21008 react: 19.2.6
21009 react-devtools-core: 6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6)
21010 react-refresh: 0.14.2
21011 regenerator-runtime: 0.13.11
21012 scheduler: 0.28.0
21013 semver: 7.8.5
21014 stacktrace-parser: 0.1.11
21015 tinyglobby: 0.2.17
21016 whatwg-fetch: 3.6.20
21017 ws: 7.5.11(bufferutil@4.1.0)(utf-8-validate@6.0.6)
21018 yargs: 17.7.2
21019 optionalDependencies:
21020 '@types/react': 19.3.0
21021 transitivePeerDependencies:
21022 - '@babel/core'
21023 - '@react-native-community/cli'
21024 - '@react-native/metro-config'
21025 - bufferutil
21026 - supports-color
21027 - utf-8-validate
21028 optional: true
21029
2093121030 react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(utf-8-validate@6.0.6):
2093221031 dependencies:
2093321032 '@react-native/asset-utils': 0.88.0-rc.3