refactor(web): unify max-width constraints with tailwind content class

Adds maxWidth.content: '720px' to tailwind.config.ts for centralized width management across the web app. Replaces all hardcoded max-w-[720px] references with the new max-w-content class. Adds missing max-width constraints to file.tsx, files.tsx, and SessionList.tsx. Moves border-b dividers from outer full-width divs to inner max-w-content divs for consistent visual hierarchy.
This commit is contained in:
weishu
2025-12-23 18:40:51 +08:00
parent 2bff252e6a
commit a2bdc73236
7 changed files with 157 additions and 141 deletions
@@ -437,7 +437,7 @@ export function HappyComposer(props: {
return (
<div className="px-3 pb-3 pt-2 bg-[var(--app-bg)]">
<div className="mx-auto w-full max-w-[720px]">
<div className="mx-auto w-full max-w-content">
<ComposerPrimitive.Root className="relative">
{overlays}
@@ -135,7 +135,7 @@ export function HappyThread(props: {
<ThreadPrimitive.Root className="flex min-h-0 flex-1 flex-col">
<ThreadPrimitive.Viewport asChild autoScroll>
<div ref={viewportRef} className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden">
<div className="mx-auto w-full max-w-[720px] min-w-0 p-3">
<div className="mx-auto w-full max-w-content min-w-0 p-3">
<div ref={topSentinelRef} className="h-px w-full" aria-hidden="true" />
{props.isLoadingMessages ? (
<div className="text-sm text-[var(--app-hint)]">
+1 -1
View File
@@ -50,7 +50,7 @@ export function SessionHeader(props: {
return (
<div className="bg-[var(--app-bg)] pt-[env(safe-area-inset-top)]">
<div className="mx-auto w-full max-w-[720px] flex items-center gap-2 p-3">
<div className="mx-auto w-full max-w-content flex items-center gap-2 p-3">
{/* Back button */}
<button
type="button"
+1 -1
View File
@@ -100,7 +100,7 @@ export function SessionList(props: {
isLoading: boolean
}) {
return (
<div className="flex flex-col">
<div className="mx-auto w-full max-w-content flex flex-col">
<div className="flex items-center justify-between px-3 py-1">
<div className="text-xs text-[var(--app-hint)]">
{props.sessions.length} sessions
+56 -50
View File
@@ -175,8 +175,8 @@ export default function FilePage() {
return (
<div className="flex h-full flex-col">
<div className="bg-[var(--app-bg)] border-b border-[var(--app-border)] pt-[env(safe-area-inset-top)]">
<div className="mx-auto w-full max-w-[720px] flex items-center gap-2 p-3">
<div className="bg-[var(--app-bg)] pt-[env(safe-area-inset-top)]">
<div className="mx-auto w-full max-w-content flex items-center gap-2 p-3 border-b border-[var(--app-border)]">
<button
type="button"
onClick={goBack}
@@ -191,61 +191,67 @@ export default function FilePage() {
</div>
</div>
<div className="border-b border-[var(--app-divider)] bg-[var(--app-bg)] px-3 py-2 flex items-center gap-2">
<FileIcon fileName={fileName} size={20} />
<span className="text-xs text-[var(--app-hint)]">{filePath}</span>
<div className="bg-[var(--app-bg)]">
<div className="mx-auto w-full max-w-content px-3 py-2 flex items-center gap-2 border-b border-[var(--app-divider)]">
<FileIcon fileName={fileName} size={20} />
<span className="text-xs text-[var(--app-hint)]">{filePath}</span>
</div>
</div>
{diffContent ? (
<div className="border-b border-[var(--app-divider)] bg-[var(--app-bg)] px-3 py-2 flex items-center gap-2">
<button
type="button"
onClick={() => setDisplayMode('diff')}
className={`rounded px-3 py-1 text-xs font-semibold ${displayMode === 'diff' ? 'bg-[var(--app-link)] text-white' : 'bg-[var(--app-subtle-bg)] text-[var(--app-hint)]'}`}
>
Diff
</button>
<button
type="button"
onClick={() => setDisplayMode('file')}
className={`rounded px-3 py-1 text-xs font-semibold ${displayMode === 'file' ? 'bg-[var(--app-link)] text-white' : 'bg-[var(--app-subtle-bg)] text-[var(--app-hint)]'}`}
>
File
</button>
<div className="bg-[var(--app-bg)]">
<div className="mx-auto w-full max-w-content px-3 py-2 flex items-center gap-2 border-b border-[var(--app-divider)]">
<button
type="button"
onClick={() => setDisplayMode('diff')}
className={`rounded px-3 py-1 text-xs font-semibold ${displayMode === 'diff' ? 'bg-[var(--app-link)] text-white' : 'bg-[var(--app-subtle-bg)] text-[var(--app-hint)]'}`}
>
Diff
</button>
<button
type="button"
onClick={() => setDisplayMode('file')}
className={`rounded px-3 py-1 text-xs font-semibold ${displayMode === 'file' ? 'bg-[var(--app-link)] text-white' : 'bg-[var(--app-subtle-bg)] text-[var(--app-hint)]'}`}
>
File
</button>
</div>
</div>
) : null}
<div className="flex-1 overflow-y-auto p-4">
{diffErrorMessage ? (
<div className="mb-3 rounded-md bg-amber-500/10 p-2 text-xs text-[var(--app-hint)]">
{diffErrorMessage}
</div>
) : null}
{missingPath ? (
<div className="text-sm text-[var(--app-hint)]">No file path provided.</div>
) : loading ? (
<div className="text-sm text-[var(--app-hint)]">Loading file...</div>
) : fileError ? (
<div className="text-sm text-[var(--app-hint)]">{fileError}</div>
) : binaryFile ? (
<div className="text-sm text-[var(--app-hint)]">
This looks like a binary file. It cannot be displayed.
</div>
) : displayMode === 'diff' && diffContent ? (
<DiffDisplay diffContent={diffContent} />
) : displayMode === 'diff' && diffError ? (
<div className="text-sm text-[var(--app-hint)]">{diffError}</div>
) : displayMode === 'file' ? (
decodedContent ? (
<pre className="shiki overflow-auto rounded-md bg-[var(--app-code-bg)] p-3 text-xs font-mono">
<code>{highlighted ?? decodedContent}</code>
</pre>
<div className="flex-1 overflow-y-auto">
<div className="mx-auto w-full max-w-content p-4">
{diffErrorMessage ? (
<div className="mb-3 rounded-md bg-amber-500/10 p-2 text-xs text-[var(--app-hint)]">
{diffErrorMessage}
</div>
) : null}
{missingPath ? (
<div className="text-sm text-[var(--app-hint)]">No file path provided.</div>
) : loading ? (
<div className="text-sm text-[var(--app-hint)]">Loading file...</div>
) : fileError ? (
<div className="text-sm text-[var(--app-hint)]">{fileError}</div>
) : binaryFile ? (
<div className="text-sm text-[var(--app-hint)]">
This looks like a binary file. It cannot be displayed.
</div>
) : displayMode === 'diff' && diffContent ? (
<DiffDisplay diffContent={diffContent} />
) : displayMode === 'diff' && diffError ? (
<div className="text-sm text-[var(--app-hint)]">{diffError}</div>
) : displayMode === 'file' ? (
decodedContent ? (
<pre className="shiki overflow-auto rounded-md bg-[var(--app-code-bg)] p-3 text-xs font-mono">
<code>{highlighted ?? decodedContent}</code>
</pre>
) : (
<div className="text-sm text-[var(--app-hint)]">File is empty.</div>
)
) : (
<div className="text-sm text-[var(--app-hint)]">File is empty.</div>
)
) : (
<div className="text-sm text-[var(--app-hint)]">No changes to display.</div>
)}
<div className="text-sm text-[var(--app-hint)]">No changes to display.</div>
)}
</div>
</div>
</div>
)
+92 -86
View File
@@ -249,8 +249,8 @@ export default function FilesPage() {
return (
<div className="flex h-full flex-col">
<div className="bg-[var(--app-bg)] border-b border-[var(--app-border)] pt-[env(safe-area-inset-top)]">
<div className="mx-auto w-full max-w-[720px] flex items-center gap-2 p-3">
<div className="bg-[var(--app-bg)] pt-[env(safe-area-inset-top)]">
<div className="mx-auto w-full max-w-content flex items-center gap-2 p-3 border-b border-[var(--app-border)]">
<button
type="button"
onClick={goBack}
@@ -273,102 +273,108 @@ export default function FilesPage() {
</div>
</div>
<div className="border-b border-[var(--app-border)] bg-[var(--app-bg)] p-3">
<div className="flex items-center gap-2 rounded-md bg-[var(--app-subtle-bg)] px-3 py-2">
<SearchIcon className="text-[var(--app-hint)]" />
<input
value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)}
placeholder="Search files"
className="w-full bg-transparent text-sm text-[var(--app-fg)] placeholder:text-[var(--app-hint)] focus:outline-none"
autoCapitalize="none"
autoCorrect="off"
/>
<div className="bg-[var(--app-bg)]">
<div className="mx-auto w-full max-w-content p-3 border-b border-[var(--app-border)]">
<div className="flex items-center gap-2 rounded-md bg-[var(--app-subtle-bg)] px-3 py-2">
<SearchIcon className="text-[var(--app-hint)]" />
<input
value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)}
placeholder="Search files"
className="w-full bg-transparent text-sm text-[var(--app-fg)] placeholder:text-[var(--app-hint)] focus:outline-none"
autoCapitalize="none"
autoCorrect="off"
/>
</div>
</div>
</div>
{!gitLoading && gitStatus ? (
<div className="border-b border-[var(--app-divider)] bg-[var(--app-bg)] px-3 py-2">
<div className="flex items-center gap-2 text-sm">
<GitBranchIcon className="text-[var(--app-hint)]" />
<span className="font-semibold">{branchLabel}</span>
</div>
<div className="text-xs text-[var(--app-hint)]">
{gitStatus.totalStaged} staged, {gitStatus.totalUnstaged} unstaged
<div className="bg-[var(--app-bg)]">
<div className="mx-auto w-full max-w-content px-3 py-2 border-b border-[var(--app-divider)]">
<div className="flex items-center gap-2 text-sm">
<GitBranchIcon className="text-[var(--app-hint)]" />
<span className="font-semibold">{branchLabel}</span>
</div>
<div className="text-xs text-[var(--app-hint)]">
{gitStatus.totalStaged} staged, {gitStatus.totalUnstaged} unstaged
</div>
</div>
</div>
) : null}
<div className="flex-1 overflow-y-auto">
{showGitErrorBanner ? (
<div className="border-b border-[var(--app-divider)] bg-amber-500/10 px-3 py-2 text-xs text-[var(--app-hint)]">
{gitError}
</div>
) : null}
{gitLoading ? (
<div className="p-6 text-sm text-[var(--app-hint)]">Loading Git status...</div>
) : shouldSearch ? (
searchResults.isLoading ? (
<div className="p-6 text-sm text-[var(--app-hint)]">Loading files...</div>
) : searchResults.error ? (
<div className="p-6 text-sm text-[var(--app-hint)]">{searchResults.error}</div>
) : searchResults.files.length === 0 ? (
<div className="p-6 text-sm text-[var(--app-hint)]">
{searchQuery ? 'No files match your search.' : 'No files found in this project.'}
<div className="mx-auto w-full max-w-content">
{showGitErrorBanner ? (
<div className="border-b border-[var(--app-divider)] bg-amber-500/10 px-3 py-2 text-xs text-[var(--app-hint)]">
{gitError}
</div>
) : (
<div className="border-t border-[var(--app-divider)]">
{searchResults.files.map((file, index) => (
<SearchResultRow
key={`${file.fullPath}-${index}`}
file={file}
onOpen={() => handleOpenFile(file.fullPath)}
showDivider={index < searchResults.files.length - 1}
/>
))}
</div>
)
) : (
<div>
{gitStatus?.stagedFiles.length ? (
<div>
<div className="border-b border-[var(--app-divider)] bg-[var(--app-bg)] px-3 py-2 text-xs font-semibold text-[var(--app-git-staged-color)]">
Staged Changes ({gitStatus.stagedFiles.length})
</div>
{gitStatus.stagedFiles.map((file, index) => (
<GitFileRow
key={`staged-${file.fullPath}-${index}`}
file={file}
onOpen={() => handleOpenFile(file.fullPath, file.isStaged)}
showDivider={index < gitStatus.stagedFiles.length - 1 || gitStatus.unstagedFiles.length > 0}
/>
))}
</div>
) : null}
{gitStatus?.unstagedFiles.length ? (
<div>
<div className="border-b border-[var(--app-divider)] bg-[var(--app-bg)] px-3 py-2 text-xs font-semibold text-[var(--app-git-unstaged-color)]">
Unstaged Changes ({gitStatus.unstagedFiles.length})
</div>
{gitStatus.unstagedFiles.map((file, index) => (
<GitFileRow
key={`unstaged-${file.fullPath}-${index}`}
file={file}
onOpen={() => handleOpenFile(file.fullPath, file.isStaged)}
showDivider={index < gitStatus.unstagedFiles.length - 1}
/>
))}
</div>
) : null}
{gitStatus && gitStatus.stagedFiles.length === 0 && gitStatus.unstagedFiles.length === 0 ? (
) : null}
{gitLoading ? (
<div className="p-6 text-sm text-[var(--app-hint)]">Loading Git status...</div>
) : shouldSearch ? (
searchResults.isLoading ? (
<div className="p-6 text-sm text-[var(--app-hint)]">Loading files...</div>
) : searchResults.error ? (
<div className="p-6 text-sm text-[var(--app-hint)]">{searchResults.error}</div>
) : searchResults.files.length === 0 ? (
<div className="p-6 text-sm text-[var(--app-hint)]">
No changes detected. Use search to browse files.
{searchQuery ? 'No files match your search.' : 'No files found in this project.'}
</div>
) : null}
</div>
)}
) : (
<div className="border-t border-[var(--app-divider)]">
{searchResults.files.map((file, index) => (
<SearchResultRow
key={`${file.fullPath}-${index}`}
file={file}
onOpen={() => handleOpenFile(file.fullPath)}
showDivider={index < searchResults.files.length - 1}
/>
))}
</div>
)
) : (
<div>
{gitStatus?.stagedFiles.length ? (
<div>
<div className="border-b border-[var(--app-divider)] bg-[var(--app-bg)] px-3 py-2 text-xs font-semibold text-[var(--app-git-staged-color)]">
Staged Changes ({gitStatus.stagedFiles.length})
</div>
{gitStatus.stagedFiles.map((file, index) => (
<GitFileRow
key={`staged-${file.fullPath}-${index}`}
file={file}
onOpen={() => handleOpenFile(file.fullPath, file.isStaged)}
showDivider={index < gitStatus.stagedFiles.length - 1 || gitStatus.unstagedFiles.length > 0}
/>
))}
</div>
) : null}
{gitStatus?.unstagedFiles.length ? (
<div>
<div className="border-b border-[var(--app-divider)] bg-[var(--app-bg)] px-3 py-2 text-xs font-semibold text-[var(--app-git-unstaged-color)]">
Unstaged Changes ({gitStatus.unstagedFiles.length})
</div>
{gitStatus.unstagedFiles.map((file, index) => (
<GitFileRow
key={`unstaged-${file.fullPath}-${index}`}
file={file}
onOpen={() => handleOpenFile(file.fullPath, file.isStaged)}
showDivider={index < gitStatus.unstagedFiles.length - 1}
/>
))}
</div>
) : null}
{gitStatus && gitStatus.stagedFiles.length === 0 && gitStatus.unstagedFiles.length === 0 ? (
<div className="p-6 text-sm text-[var(--app-hint)]">
No changes detected. Use search to browse files.
</div>
) : null}
</div>
)}
</div>
</div>
</div>
)