Skip to content

Commit 11f60ea

Browse files
committed
🤖 feat: responsive ChatInput controls with compact display
Changes chat controls to use compact display on narrow viewports instead of hiding elements entirely: 1. **1M Context Label**: Changed from '1M Context' to just '1M' (always) 2. **Thinking Slider**: Slider hidden on narrow viewports (<550px), but text label remains visible and is always clickable to cycle through levels (off → low → medium → high → off) 3. **Mode Switch**: Shows both Exec/Plan buttons on wide viewports, shows only active mode as clickable toggle on narrow viewports (<550px) All controls remain functional at all viewport sizes. Compact versions use click-to-cycle pattern to switch between options. _Generated with `cmux`_
1 parent c1f6440 commit 11f60ea

File tree

4 files changed

+85
-13
lines changed

4 files changed

+85
-13
lines changed

src/components/ChatInput.tsx

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -847,18 +847,19 @@ export const ChatInput: React.FC<ChatInputProps> = ({
847847
</TooltipWrapper>
848848
</div>
849849

850-
{/* Thinking Slider - hide on small viewports */}
850+
{/* Thinking Slider - slider hidden on narrow viewports, label always clickable */}
851851
<div
852-
className="flex items-center max-[550px]:hidden"
852+
className="flex items-center [&_.thinking-slider]:max-[550px]:hidden"
853853
data-component="ThinkingSliderGroup"
854854
>
855855
<ThinkingSliderComponent modelString={preferredModel} />
856856
</div>
857857

858-
{/* Context 1M Checkbox - hide on smaller viewports */}
859-
<div className="flex items-center max-[450px]:hidden" data-component="Context1MGroup">
858+
{/* Context 1M Checkbox - always visible */}
859+
<div className="flex items-center" data-component="Context1MGroup">
860860
<Context1MCheckbox modelString={preferredModel} />
861861
</div>
862+
862863
{preferredModel && (
863864
<div className={hasTypedText ? "block" : "hidden"}>
864865
<Suspense
@@ -875,6 +876,8 @@ export const ChatInput: React.FC<ChatInputProps> = ({
875876
</Suspense>
876877
</div>
877878
)}
879+
880+
{/* Mode Switch - full version for wide viewports */}
878881
<div className="ml-auto flex items-center gap-1.5 max-[550px]:hidden">
879882
<div
880883
className={cn(
@@ -908,6 +911,38 @@ export const ChatInput: React.FC<ChatInputProps> = ({
908911
</Tooltip>
909912
</TooltipWrapper>
910913
</div>
914+
915+
{/* Mode Switch - compact version for narrow viewports */}
916+
<div className="ml-auto hidden items-center gap-1.5 max-[550px]:flex">
917+
<div
918+
className={cn(
919+
mode === "exec" && "bg-exec-mode text-white",
920+
mode === "plan" && "bg-plan-mode text-white"
921+
)}
922+
>
923+
<ToggleGroup<UIMode>
924+
options={[
925+
{ value: "exec", label: "Exec", activeClassName: "bg-exec-mode text-white" },
926+
{ value: "plan", label: "Plan", activeClassName: "bg-plan-mode text-white" },
927+
]}
928+
value={mode}
929+
onChange={setMode}
930+
compact
931+
/>
932+
</div>
933+
<TooltipWrapper inline>
934+
<HelpIndicator>?</HelpIndicator>
935+
<Tooltip className="tooltip" align="center" width="wide">
936+
<strong>Exec Mode:</strong> AI edits files and execute commands
937+
<br />
938+
<br />
939+
<strong>Plan Mode:</strong> AI proposes plans but does not edit files
940+
<br />
941+
<br />
942+
Toggle with: {formatKeybind(KEYBINDS.TOGGLE_MODE)}
943+
</Tooltip>
944+
</TooltipWrapper>
945+
</div>
911946
</div>
912947
</div>
913948
</div>

src/components/Context1MCheckbox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const Context1MCheckbox: React.FC<Context1MCheckboxProps> = ({ modelStrin
1919
<div className="flex items-center gap-1.5">
2020
<label className="text-foreground flex cursor-pointer items-center gap-1 truncate text-[10px] select-none hover:text-white">
2121
<input type="checkbox" checked={use1M} onChange={(e) => setUse1M(e.target.checked)} />
22-
1M Context
22+
1M
2323
</label>
2424
<TooltipWrapper inline>
2525
<span className="text-muted flex cursor-help items-center text-[10px] leading-none">?</span>

src/components/ThinkingSlider.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ export const ThinkingSliderComponent: React.FC<ThinkingControlProps> = ({ modelS
124124
}
125125
};
126126

127+
// Cycle through allowed thinking levels: off → low → medium → high → off
128+
const cycleThinkingLevel = () => {
129+
const currentIndex = THINKING_LEVELS.indexOf(thinkingLevel);
130+
const nextIndex = (currentIndex + 1) % THINKING_LEVELS.length;
131+
handleThinkingLevelChange(THINKING_LEVELS[nextIndex]);
132+
};
133+
127134
return (
128135
<TooltipWrapper>
129136
<div className="flex items-center gap-2">
@@ -154,16 +161,23 @@ export const ThinkingSliderComponent: React.FC<ThinkingControlProps> = ({ modelS
154161
} as React.CSSProperties
155162
}
156163
/>
157-
<span
158-
className="min-w-11 uppercase transition-all duration-200 select-none"
159-
style={textStyle}
160-
aria-live="polite"
164+
<button
165+
type="button"
166+
onClick={cycleThinkingLevel}
167+
className="cursor-pointer border-none bg-transparent p-0"
168+
aria-label={`Thinking level: ${thinkingLevel}. Click to cycle.`}
161169
>
162-
{thinkingLevel}
163-
</span>
170+
<span
171+
className="min-w-11 uppercase transition-all duration-200 select-none"
172+
style={textStyle}
173+
aria-live="polite"
174+
>
175+
{thinkingLevel}
176+
</span>
177+
</button>
164178
</div>
165179
<Tooltip align="center">
166-
Thinking: {formatKeybind(KEYBINDS.TOGGLE_THINKING)} to toggle
180+
Thinking: {formatKeybind(KEYBINDS.TOGGLE_THINKING)} to toggle. Click level to cycle.
167181
</Tooltip>
168182
</TooltipWrapper>
169183
);

src/components/ToggleGroup.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,32 @@ interface ToggleGroupProps<T extends string> {
1010
options: Array<ToggleOption<T>>;
1111
value: T;
1212
onChange: (value: T) => void;
13+
compact?: boolean; // If true, show only active option as clickable badge
1314
}
1415

15-
export function ToggleGroup<T extends string>({ options, value, onChange }: ToggleGroupProps<T>) {
16+
export function ToggleGroup<T extends string>({ options, value, onChange, compact = false }: ToggleGroupProps<T>) {
17+
// Compact mode: show only active option, click cycles to next option
18+
if (compact) {
19+
const currentIndex = options.findIndex(opt => opt.value === value);
20+
const activeOption = options[currentIndex];
21+
const nextOption = options[(currentIndex + 1) % options.length];
22+
23+
return (
24+
<button
25+
onClick={() => onChange(nextOption.value)}
26+
type="button"
27+
className={cn(
28+
"px-2 py-1 text-[11px] font-sans rounded border-none cursor-pointer transition-all duration-150",
29+
"text-toggle-text-active bg-toggle-active font-medium",
30+
activeOption?.activeClassName
31+
)}
32+
aria-label={`${activeOption.label} mode. Click to switch to ${nextOption.label}.`}
33+
>
34+
{activeOption.label}
35+
</button>
36+
);
37+
}
38+
1639
return (
1740
<div className="bg-toggle-bg flex gap-0 rounded">
1841
{options.map((option) => {

0 commit comments

Comments
 (0)