mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
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:
@@ -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)]">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user