Skip to content

Commit fa29266

Browse files
committed
fix: mobile UX polish — cleaner connect form, hide input when disconnected, icon inputs, focus rings
1 parent 4204ab8 commit fa29266

1 file changed

Lines changed: 96 additions & 83 deletions

File tree

components/agent-panel.tsx

Lines changed: 96 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ import {
7171
function AgentConnectPrompt() {
7272
const { status, error, connect } = useGateway()
7373
const isMobileDevice = typeof window !== 'undefined' && window.innerWidth <= 768
74-
const [showManual, setShowManual] = useState(isMobileDevice) // auto-expand on mobile
7574
const [url, setUrl] = useState(isMobileDevice ? '' : 'ws://localhost:18789')
7675
const [password, setPassword] = useState('')
7776

@@ -83,121 +82,135 @@ function AgentConnectPrompt() {
8382
}
8483

8584
return (
86-
<div className="flex flex-col items-center justify-center text-center py-8 px-4">
87-
<div className="relative mb-5">
88-
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[color-mix(in_srgb,var(--brand)_20%,transparent)] to-[color-mix(in_srgb,var(--brand)_6%,transparent)] border border-[color-mix(in_srgb,var(--brand)_25%,transparent)] flex items-center justify-center shadow-lg">
85+
<div className="flex flex-1 flex-col items-center justify-center text-center px-6">
86+
{/* Animated connection icon */}
87+
<div className="relative mb-6">
88+
<div
89+
className={`w-16 h-16 rounded-[20px] flex items-center justify-center transition-all duration-500 ${
90+
isConnecting
91+
? 'bg-[color-mix(in_srgb,var(--warning,#eab308)_12%,transparent)] border border-[color-mix(in_srgb,var(--warning,#eab308)_30%,transparent)]'
92+
: 'bg-[color-mix(in_srgb,var(--brand)_10%,transparent)] border border-[color-mix(in_srgb,var(--brand)_20%,transparent)]'
93+
}`}
94+
>
8995
<Icon
90-
icon={isConnecting ? 'lucide:loader-2' : 'lucide:cpu'}
96+
icon={isConnecting ? 'lucide:radio' : 'lucide:radio'}
9197
width={28}
9298
height={28}
93-
className={`text-[var(--brand)] ${isConnecting ? 'animate-spin' : ''}`}
99+
className={`transition-colors duration-500 ${
100+
isConnecting ? 'text-[var(--warning,#eab308)] animate-pulse' : 'text-[var(--brand)]'
101+
}`}
94102
/>
95103
</div>
96-
<span
97-
className={`absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 rounded-full border-2 border-[var(--bg)] ${
98-
isConnecting ? 'bg-[var(--warning,#eab308)] animate-pulse' : 'bg-[var(--text-disabled)]'
99-
}`}
100-
/>
101104
</div>
102105

103106
{isConnecting ? (
104107
<>
105-
<h3 className="text-[16px] font-semibold text-[var(--text-primary)] mb-1.5">
108+
<h3 className="text-[17px] font-semibold text-[var(--text-primary)] mb-1">
106109
Connecting…
107110
</h3>
108-
<p className="text-[13px] text-[var(--text-tertiary)]">Looking for OpenClaw gateway</p>
111+
<p className="text-[13px] text-[var(--text-tertiary)]">Looking for your gateway</p>
109112
</>
110113
) : (
111114
<>
112-
<h3 className="text-[16px] font-semibold text-[var(--text-primary)] mb-1.5">
113-
Gateway not found
115+
<h3 className="text-[17px] font-semibold text-[var(--text-primary)] mb-1">
116+
Connect to Gateway
114117
</h3>
115-
<p className="text-[13px] text-[var(--text-tertiary)] leading-relaxed mb-4 max-w-[320px]">
118+
<p className="text-[13px] text-[var(--text-tertiary)] leading-relaxed mb-6 max-w-[280px]">
116119
{isMobileDevice
117-
? 'Enter your gateway URL and password to connect.'
120+
? 'Enter your gateway address to start chatting.'
118121
: 'Make sure OpenClaw is running on this machine.'}
119122
</p>
120123

121-
<div className="space-y-2.5 w-full max-w-[320px]">
122-
{!isMobileDevice && (
123-
<button
124-
onClick={() => connect('ws://localhost:18789', '')}
125-
className="w-full flex items-center justify-center gap-2 py-2.5 rounded-lg text-[13px] font-medium transition-all cursor-pointer"
126-
style={{ backgroundColor: 'var(--brand)', color: 'var(--brand-contrast, #fff)' }}
127-
>
128-
<Icon icon="lucide:refresh-cw" width={14} height={14} />
129-
Retry connection
130-
</button>
131-
)}
124+
<div className="w-full max-w-[340px] space-y-3">
125+
{/* URL input */}
126+
<div className="relative">
127+
<Icon
128+
icon="lucide:globe"
129+
width={15}
130+
height={15}
131+
className="absolute left-3.5 top-1/2 -translate-y-1/2 text-[var(--text-disabled)]"
132+
/>
133+
<input
134+
type="text"
135+
value={url}
136+
onChange={(e) => setUrl(e.target.value)}
137+
onKeyDown={(e) => {
138+
if (e.key === 'Enter') handleConnect()
139+
}}
140+
placeholder={isMobileDevice ? 'wss://your-gateway.ts.net' : 'ws://localhost:18789'}
141+
className="w-full pl-10 pr-3 py-3.5 rounded-xl bg-[var(--bg)] border border-[var(--border)] text-[14px] font-mono text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] outline-none focus:border-[var(--brand)] focus:ring-1 focus:ring-[color-mix(in_srgb,var(--brand)_30%,transparent)] transition-all"
142+
autoCapitalize="off"
143+
autoCorrect="off"
144+
spellCheck={false}
145+
/>
146+
</div>
132147

148+
{/* Password input */}
149+
<div className="relative">
150+
<Icon
151+
icon="lucide:lock"
152+
width={15}
153+
height={15}
154+
className="absolute left-3.5 top-1/2 -translate-y-1/2 text-[var(--text-disabled)]"
155+
/>
156+
<input
157+
type="password"
158+
value={password}
159+
onChange={(e) => setPassword(e.target.value)}
160+
onKeyDown={(e) => {
161+
if (e.key === 'Enter') handleConnect()
162+
}}
163+
placeholder="Password (optional)"
164+
className="w-full pl-10 pr-3 py-3.5 rounded-xl bg-[var(--bg)] border border-[var(--border)] text-[14px] font-mono text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] outline-none focus:border-[var(--brand)] focus:ring-1 focus:ring-[color-mix(in_srgb,var(--brand)_30%,transparent)] transition-all"
165+
/>
166+
</div>
167+
168+
{/* Connect button */}
133169
<button
134-
onClick={() => setShowManual((v) => !v)}
135-
className="w-full flex items-center justify-center gap-1.5 py-2 text-[12px] text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] transition-colors cursor-pointer"
170+
onClick={handleConnect}
171+
disabled={!url.trim()}
172+
className="w-full py-3.5 rounded-xl text-[14px] font-semibold transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
173+
style={{
174+
backgroundColor: url.trim() ? 'var(--brand)' : 'var(--bg-subtle)',
175+
color: url.trim() ? 'var(--brand-contrast, #fff)' : 'var(--text-disabled)',
176+
}}
136177
>
137-
<Icon icon="lucide:settings-2" width={12} height={12} />
138-
{showManual ? 'Hide' : 'Manual connection'}
178+
Connect
139179
</button>
140180

141-
{showManual && (
142-
<div className="space-y-2 pt-1">
143-
<input
144-
type="text"
145-
value={url}
146-
onChange={(e) => setUrl(e.target.value)}
147-
onKeyDown={(e) => {
148-
if (e.key === 'Enter') handleConnect()
149-
}}
150-
placeholder="wss://your-gateway.ts.net"
151-
className="w-full px-3 py-3 sm:py-2 rounded-lg bg-[var(--bg)] border border-[var(--border)] text-[14px] sm:text-[12px] font-mono text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] outline-none focus:border-[var(--border-focus)]"
152-
autoCapitalize="off"
153-
autoCorrect="off"
154-
spellCheck={false}
155-
/>
156-
<input
157-
type="password"
158-
value={password}
159-
onChange={(e) => setPassword(e.target.value)}
160-
onKeyDown={(e) => {
161-
if (e.key === 'Enter') handleConnect()
162-
}}
163-
placeholder="Password (if set)"
164-
className="w-full px-3 py-3 sm:py-2 rounded-lg bg-[var(--bg)] border border-[var(--border)] text-[14px] sm:text-[12px] font-mono text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] outline-none focus:border-[var(--border-focus)]"
165-
/>
166-
<button
167-
onClick={handleConnect}
168-
disabled={!url.trim()}
169-
className="w-full py-3 sm:py-2 rounded-lg text-[14px] sm:text-[12px] font-medium bg-[var(--bg-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-tertiary)] transition-colors cursor-pointer disabled:opacity-50"
170-
>
171-
Connect
172-
</button>
173-
</div>
181+
{/* Desktop retry */}
182+
{!isMobileDevice && (
183+
<button
184+
onClick={() => connect('ws://localhost:18789', '')}
185+
className="w-full flex items-center justify-center gap-2 py-2 text-[12px] text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] transition-colors cursor-pointer"
186+
>
187+
<Icon icon="lucide:refresh-cw" width={12} height={12} />
188+
Retry localhost
189+
</button>
174190
)}
175191
</div>
176192

193+
{/* Error message */}
177194
{error && (
178-
<div className="flex items-start gap-2 mt-3 text-[11px] text-[var(--color-deletions)] max-w-[280px] text-left">
179-
<Icon icon="lucide:alert-circle" width={12} height={12} className="shrink-0 mt-0.5" />
195+
<div className="flex items-start gap-2 mt-4 py-2.5 px-3.5 rounded-lg bg-[color-mix(in_srgb,var(--color-deletions)_8%,transparent)] border border-[color-mix(in_srgb,var(--color-deletions)_15%,transparent)] text-[12px] text-[var(--color-deletions)] max-w-[340px] text-left">
196+
<Icon icon="lucide:alert-circle" width={14} height={14} className="shrink-0 mt-0.5" />
180197
<span>{error}</span>
181198
</div>
182199
)}
183200

184-
{isMobileDevice ? (
185-
<div className="mt-5 text-[11px] text-[var(--text-disabled)] text-left max-w-[320px] space-y-1">
186-
<p className="font-medium text-[var(--text-tertiary)]">Connection tips:</p>
187-
<p>• Use your Tailscale Funnel or local network URL</p>
201+
{/* Tips */}
202+
{isMobileDevice && (
203+
<div className="mt-5 text-[11px] text-[var(--text-disabled)] max-w-[340px] space-y-0.5">
188204
<p>
189-
• Format:{' '}
190-
<code className="px-1 py-0.5 bg-[var(--bg-secondary)] rounded text-[var(--brand)]">
191-
wss://host:port
192-
</code>{' '}
193-
or{' '}
205+
Use your Tailscale Funnel URL or{' '}
194206
<code className="px-1 py-0.5 bg-[var(--bg-secondary)] rounded text-[var(--brand)]">
195207
ws://ip:18789
196208
</code>
197209
</p>
198210
</div>
199-
) : (
200-
<p className="mt-5 text-[11px] text-[var(--text-disabled)]">
211+
)}
212+
{!isMobileDevice && (
213+
<p className="mt-4 text-[11px] text-[var(--text-disabled)]">
201214
Run{' '}
202215
<code className="px-1 py-0.5 bg-[var(--bg-secondary)] rounded text-[var(--brand)]">
203216
openclaw gateway start
@@ -2146,8 +2159,8 @@ export function AgentPanel() {
21462159
/>
21472160
)}
21482161

2149-
{/* Input section — hidden when ChatHome is showing */}
2150-
{(messages.length > 0 || !isConnected) && (
2162+
{/* Input section — hidden when disconnected on mobile, hidden when ChatHome is showing on desktop */}
2163+
{(messages.length > 0 || (!isConnected && !(typeof window !== 'undefined' && window.innerWidth <= 768))) && (
21512164
<ChatInputBar
21522165
input={input}
21532166
setInput={setInput}
@@ -2181,13 +2194,13 @@ export function AgentPanel() {
21812194
/>
21822195
)}
21832196

2184-
{/* Branded footer */}
2185-
<div className="shrink-0 flex items-center justify-center gap-1.5 px-3 py-0.5 border-t border-[var(--border)] bg-[var(--bg-elevated)]">
2197+
{/* Branded footer — hidden on mobile to save space */}
2198+
<div className="shrink-0 hidden sm:flex items-center justify-center gap-1.5 px-3 py-0.5 border-t border-[var(--border)] bg-[var(--bg-elevated)]">
21862199
<KnotLogo size={9} className="opacity-40" />
21872200
<span className="text-[8px] text-[var(--text-disabled)] font-medium tracking-wide">
21882201
KnotCode
21892202
</span>
2190-
<span className="text-[7px] text-[var(--text-disabled)] opacity-50">v1.0.0</span>
2203+
<span className="text-[7px] text-[var(--text-disabled)] opacity-50">v1.4.0</span>
21912204
</div>
21922205
</div>
21932206
)

0 commit comments

Comments
 (0)