Skip to content

Commit dd8e880

Browse files
committed
🤖 Adopt shadcn/ui components - Phase 1
- Add shadcn/ui components via CLI (tooltip, toggle-group, dialog, input, select) - Install lucide-react dependency for shadcn components - Replace custom ToggleGroup wrapper with direct shadcn usage (3 files) - ChatInput: Exec/Plan mode toggle - CostsTab: Session/Last Request toggle - Delete old ToggleGroup wrapper and stories - Add TooltipProvider to App.tsx root - Migrate ChatInput tooltips to shadcn (3 instances) - Add Component Guidelines to AGENTS.md Next steps: Migrate remaining Tooltip usages (26 files), Modal → Dialog (8 files), form elements _Generated with `cmux`_
1 parent 366eb38 commit dd8e880

File tree

14 files changed

+494
-409
lines changed

14 files changed

+494
-409
lines changed

bun.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@radix-ui/react-separator": "^1.1.7",
1414
"@radix-ui/react-slot": "^1.2.3",
1515
"@radix-ui/react-tabs": "^1.1.13",
16+
"@radix-ui/react-toggle": "^1.1.10",
1617
"@radix-ui/react-toggle-group": "^1.1.11",
1718
"@radix-ui/react-tooltip": "^1.2.8",
1819
"ai": "^5.0.72",
@@ -26,6 +27,7 @@
2627
"express": "^5.1.0",
2728
"jsonc-parser": "^3.3.1",
2829
"lru-cache": "^11.2.2",
30+
"lucide-react": "^0.546.0",
2931
"markdown-it": "^14.1.0",
3032
"minimist": "^1.2.8",
3133
"source-map-support": "^0.5.21",
@@ -2090,6 +2092,8 @@
20902092

20912093
"lru-cache": ["[email protected]", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="],
20922094

2095+
"lucide-react": ["[email protected]", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ=="],
2096+
20932097
"lz-string": ["[email protected]", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
20942098

20952099
"magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],

docs/AGENTS.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,28 @@ If IPC is hard to test, fix the test infrastructure or IPC layer, don't work aro
255255
- Use CSS variables (e.g., `var(--color-plan-mode)`) instead of hardcoded colors
256256
- Fonts are centralized as CSS variables in `src/styles/fonts.tsx`
257257

258+
## Component Guidelines
259+
260+
**Always use shadcn/ui components from `@/components/ui/*` for standard UI patterns.**
261+
262+
- **Adding components**: `bunx --bun shadcn@latest add <component>`
263+
- **Common components**: button, input, select, dialog, tooltip, toggle-group, checkbox, switch, etc.
264+
- **Documentation**: https://ui.shadcn.com/docs/components
265+
- **Build custom only when**: The component has app-specific behavior with no shadcn equivalent
266+
267+
Example:
268+
```bash
269+
# Add a new shadcn component
270+
bunx --bun shadcn@latest add badge
271+
272+
# Import in your code
273+
import { Badge } from "@/components/ui/badge"
274+
```
275+
276+
Shadcn components automatically respect our CSS variables via the `@/lib/utils` cn() helper and Tailwind config.
277+
278+
279+
258280
## TypeScript Best Practices
259281
260282
- **Avoid `as any` in all contexts** - Never use `as any` casts. Instead:

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"@radix-ui/react-separator": "^1.1.7",
5454
"@radix-ui/react-slot": "^1.2.3",
5555
"@radix-ui/react-tabs": "^1.1.13",
56+
"@radix-ui/react-toggle": "^1.1.10",
5657
"@radix-ui/react-toggle-group": "^1.1.11",
5758
"@radix-ui/react-tooltip": "^1.2.8",
5859
"ai": "^5.0.72",
@@ -66,6 +67,7 @@
6667
"express": "^5.1.0",
6768
"jsonc-parser": "^3.3.1",
6869
"lru-cache": "^11.2.2",
70+
"lucide-react": "^0.546.0",
6971
"markdown-it": "^14.1.0",
7072
"minimist": "^1.2.8",
7173
"source-map-support": "^0.5.21",

src/App.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useEffect, useCallback, useRef } from "react";
2+
import { TooltipProvider } from "./components/ui/tooltip";
23
import "./styles/globals.css";
34
import type { ProjectConfig } from "./config";
45
import type { WorkspaceSelection } from "./components/ProjectSidebar";
@@ -748,9 +749,11 @@ function AppInner() {
748749

749750
function App() {
750751
return (
751-
<CommandRegistryProvider>
752-
<AppInner />
753-
</CommandRegistryProvider>
752+
<TooltipProvider>
753+
<CommandRegistryProvider>
754+
<AppInner />
755+
</CommandRegistryProvider>
756+
</TooltipProvider>
754757
);
755758
}
756759

src/components/ChatInput.tsx

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ import {
1717
prepareCompactionMessage,
1818
type CommandHandlerContext,
1919
} from "@/utils/chatCommands";
20-
import { ToggleGroup } from "./ToggleGroup";
20+
import { ToggleGroup, ToggleGroupItem } from "./ui/toggle-group";
2121
import { CUSTOM_EVENTS } from "@/constants/events";
2222
import type { UIMode } from "@/types/mode";
2323
import {
2424
getSlashCommandSuggestions,
2525
type SlashSuggestion,
2626
} from "@/utils/slashCommands/suggestions";
27-
import { TooltipWrapper, Tooltip, HelpIndicator } from "./Tooltip";
27+
import { Tooltip, TooltipTrigger, TooltipContent } from "./ui/tooltip";
28+
import { HelpIndicator } from "./Tooltip";
2829
import { matchesKeybind, formatKeybind, KEYBINDS, isEditableElement } from "@/utils/ui/keybinds";
2930
import { ModelSelector, type ModelSelectorRef } from "./ModelSelector";
3031
import { useModelLRU } from "@/hooks/useModelLRU";
@@ -757,9 +758,11 @@ export const ChatInput: React.FC<ChatInputProps> = ({
757758
onComplete={() => inputRef.current?.focus()}
758759
/>
759760
<span className="help-indicator-wrapper">
760-
<TooltipWrapper inline>
761-
<HelpIndicator>?</HelpIndicator>
762-
<Tooltip className="tooltip" align="left" width="wide">
761+
<Tooltip>
762+
<TooltipTrigger asChild>
763+
<HelpIndicator>?</HelpIndicator>
764+
</TooltipTrigger>
765+
<TooltipContent side="left">
763766
<strong>Click to edit</strong> or use{" "}
764767
{formatKeybind(KEYBINDS.OPEN_MODEL_SELECTOR)}
765768
<br />
@@ -774,13 +777,16 @@ export const ChatInput: React.FC<ChatInputProps> = ({
774777
<code>/model provider:model-name</code>
775778
<br />
776779
(e.g., <code>/model anthropic:claude-sonnet-4-5</code>)
777-
</Tooltip>
778-
</TooltipWrapper>
780+
</TooltipContent>
781+
</Tooltip>
779782
</span>
780783
</div>
781784
</ChatToggles>
782785
<div className="max-@[700px]:hidden ml-auto flex items-center gap-1.5">
783-
<div
786+
<ToggleGroup
787+
type="single"
788+
value={mode}
789+
onValueChange={(value) => value && setMode(value as UIMode)}
784790
className={cn(
785791
"flex gap-0 bg-toggle-bg rounded",
786792
"[&>button:first-of-type]:rounded-l [&>button:last-of-type]:rounded-r",
@@ -790,28 +796,36 @@ export const ChatInput: React.FC<ChatInputProps> = ({
790796
"[&>button:last-of-type]:bg-plan-mode [&>button:last-of-type]:text-white [&>button:last-of-type]:hover:bg-plan-mode-hover"
791797
)}
792798
>
793-
<ToggleGroup<UIMode>
794-
options={[
795-
{ value: "exec", label: "Exec", activeClassName: "bg-exec-mode text-white" },
796-
{ value: "plan", label: "Plan", activeClassName: "bg-plan-mode text-white" },
797-
]}
798-
value={mode}
799-
onChange={setMode}
800-
/>
801-
</div>
799+
<ToggleGroupItem
800+
value="exec"
801+
aria-label="Exec mode"
802+
className="px-2 py-1 text-[11px] font-sans rounded-sm border-none cursor-pointer transition-all duration-150 bg-transparent data-[state=on]:bg-exec-mode data-[state=on]:text-white data-[state=on]:hover:bg-exec-mode-hover text-toggle-text font-normal hover:text-toggle-text-hover hover:bg-toggle-hover"
803+
>
804+
Exec
805+
</ToggleGroupItem>
806+
<ToggleGroupItem
807+
value="plan"
808+
aria-label="Plan mode"
809+
className="px-2 py-1 text-[11px] font-sans rounded-sm border-none cursor-pointer transition-all duration-150 bg-transparent data-[state=on]:bg-plan-mode data-[state=on]:text-white data-[state=on]:hover:bg-plan-mode-hover text-toggle-text font-normal hover:text-toggle-text-hover hover:bg-toggle-hover"
810+
>
811+
Plan
812+
</ToggleGroupItem>
813+
</ToggleGroup>
802814
<span className="help-indicator-wrapper">
803-
<TooltipWrapper inline>
804-
<HelpIndicator>?</HelpIndicator>
805-
<Tooltip className="tooltip" align="center" width="wide">
815+
<Tooltip>
816+
<TooltipTrigger asChild>
817+
<HelpIndicator>?</HelpIndicator>
818+
</TooltipTrigger>
819+
<TooltipContent>
806820
<strong>Exec Mode:</strong> AI edits files and execute commands
807821
<br />
808822
<br />
809823
<strong>Plan Mode:</strong> AI proposes plans but does not edit files
810824
<br />
811825
<br />
812826
Toggle with: {formatKeybind(KEYBINDS.TOGGLE_MODE)}
813-
</Tooltip>
814-
</TooltipWrapper>
827+
</TooltipContent>
828+
</Tooltip>
815829
</span>
816830
</div>
817831
</div>

src/components/RightSidebar/CostsTab.tsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useWorkspaceUsage, useWorkspaceConsumers } from "@/stores/WorkspaceStor
33
import { getModelStats } from "@/utils/tokens/modelStats";
44
import { sumUsageHistory } from "@/utils/tokens/usageAggregator";
55
import { usePersistedState } from "@/hooks/usePersistedState";
6-
import { ToggleGroup, type ToggleOption } from "../ToggleGroup";
6+
import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group";
77
import { use1MContext } from "@/hooks/use1MContext";
88
import { supports1MContext } from "@/utils/ai/models";
99
import { TOKEN_COMPONENT_COLORS } from "@/utils/tokens/tokenMeterUtils";
@@ -46,11 +46,6 @@ const calculateElevatedCost = (tokens: number, standardRate: number, isInput: bo
4646

4747
type ViewMode = "last-request" | "session";
4848

49-
const VIEW_MODE_OPTIONS: Array<ToggleOption<ViewMode>> = [
50-
{ value: "session", label: "Session" },
51-
{ value: "last-request", label: "Last Request" },
52-
];
53-
5449
interface CostsTabProps {
5550
workspaceId: string;
5651
}
@@ -352,10 +347,26 @@ const CostsTabComponent: React.FC<CostsTabProps> = ({ workspaceId }) => {
352347
Cost
353348
</span>
354349
<ToggleGroup
355-
options={VIEW_MODE_OPTIONS}
350+
type="single"
356351
value={viewMode}
357-
onChange={setViewMode}
358-
/>
352+
onValueChange={(value) => value && setViewMode(value as ViewMode)}
353+
className="flex gap-0 bg-toggle-bg rounded"
354+
>
355+
<ToggleGroupItem
356+
value="session"
357+
aria-label="Session view"
358+
className="px-2 py-1 text-[11px] font-sans rounded-sm border-none cursor-pointer transition-all duration-150 bg-transparent data-[state=on]:text-toggle-text-active data-[state=on]:bg-toggle-active data-[state=on]:font-medium text-toggle-text font-normal hover:text-toggle-text-hover hover:bg-toggle-hover"
359+
>
360+
Session
361+
</ToggleGroupItem>
362+
<ToggleGroupItem
363+
value="last-request"
364+
aria-label="Last request view"
365+
className="px-2 py-1 text-[11px] font-sans rounded-sm border-none cursor-pointer transition-all duration-150 bg-transparent data-[state=on]:text-toggle-text-active data-[state=on]:bg-toggle-active data-[state=on]:font-medium text-toggle-text font-normal hover:text-toggle-text-hover hover:bg-toggle-hover"
366+
>
367+
Last Request
368+
</ToggleGroupItem>
369+
</ToggleGroup>
359370
</div>
360371
<span className="text-muted text-xs">
361372
{formatCostWithDollar(totalCost)}

0 commit comments

Comments
 (0)