feat(web): rework the repo toolbar tabs, refs and clone box
The tab strip drops its pill container for a flat bar: every tab gains an icon, the active one an orange underline, the merged-request tab a count badge (capped at 99+) and the AI tab a BETA chip. The clone menu leaves the toolbar's right slot for the end of the refs row, so the branch picker and the clone action read as one line; `showRefs` now defaults to false because the pages render the refs picker next to their own content. The clone button switches to GitHub's green and the ref chips become label plus count pairs behind a divider, with the toolbar losing its 60px min-height so the row sits flush with the content below it. The i18n plural keys behind those chips collapse into `branchLabel`/`tagLabel`/ `commitLabel`.
ZhenYi committed
Modified web/src/app/styles/workspace.css web/src/app/styles/workspace.css +2 −2 View web/src/app/styles/workspace.css
@@ -256,7 +256,7 @@
256256.board-toolbar { padding: 16px 24px; gap: 12px; border-bottom: 1px solid var(--shell-hairline); }
257257.board-canvas { min-height: 0; padding: 20px 24px; background: var(--shell-field); }
258258.board-track { gap: 18px; }
259.repo-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; min-height: 60px; padding: 12px 24px; border-bottom: 1px solid var(--shell-hairline); }
259.repo-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding: 12px 24px 0; border-bottom: 1px solid var(--shell-hairline); }
260260.repo-toolbar-refs { display: flex; min-width: 0; flex-shrink: 0; flex-wrap: wrap; gap: 8px; }
261261.repo-toolbar-clone { display: flex; flex-shrink: 0; align-items: center; gap: 8px; }
262262.repo-toolbar-clone.empty .repo-overview-toggle { margin-left: auto; }
@@ -321,7 +321,7 @@
321321 .settings-facts dt { width: 100%; }
322322 .board-toolbar { padding: 12px 16px; }
323323 .board-canvas { padding: 16px; }
324 .repo-toolbar { padding: 10px 16px; }
324 .repo-toolbar { padding: 10px 16px 0; }
325325 .repo-toolbar-refs { flex: 1 1 100%; min-width: 0; }
326326 .repo-toolbar-clone { max-width: 100%; flex-wrap: wrap; margin-left: 0; }
327327 .repo-content-layout { padding: 12px 16px 16px; }
Modified web/src/features/repo-browse/ui/code-tab.tsx web/src/features/repo-browse/ui/code-tab.tsx +26 −8 View web/src/features/repo-browse/ui/code-tab.tsx
@@ -1,5 +1,5 @@
11import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
2import { ArrowLeft, ArrowUp, ChevronRight, Eye, File as FileIcon, FileCode, Folder, FolderGit2, History, Users } from 'lucide-react'
2import { ArrowLeft, ArrowUp, ChevronRight, Eye, FileCode, FolderGit2, History, Users } from 'lucide-react'
33import { useT } from '@/shared/i18n'
44import { fmtRelative, fmtTime, formatBytes, repoKey, shortSha, useAsync } from '@/shared/lib'
55import { ErrorPanel, InitialAvatar, PanelNote } from '@/shared/ui'
@@ -18,6 +18,7 @@
1818import { isMarkdownPath, isPdfPath } from '../model/blob-kind'
1919import { prepareMarkdownHtml, prepareReadmeHtml, resolveRepoPath, statRepoPath } from '../model/readme'
2020import { prepareTreeHtml } from '../model/tree-html'
21import { treeIconUrl } from '../model/tree-icons'
2122import { useFragmentNavigation, type FragmentRoutes } from '../model/use-fragment-navigation'
2223import { BlameView } from './blame-view'
2324import { PdfViewer } from './pdf-viewer'
@@ -28,6 +29,8 @@
2829 rev: string
2930 repoDto: RepoDetail
3031 refs?: ReactNode
32 /** Clone menu pinned to the right end of the refs row. */
33 clone?: ReactNode
3134 mode: 'html' | 'json'
3235 /** Directory being listed ('' = repo root). Owned by the route. */
3336 path: string
@@ -54,6 +57,7 @@
5457 rev,
5558 repoDto,
5659 refs,
60 clone,
5761 mode,
5862 path,
5963 file,
@@ -86,6 +90,7 @@
8690 return (
8791 <FileView
8892 refs={refs}
93 clone={clone}
8994 space={space}
9095 repo={repo}
9196 rev={rev}
@@ -100,6 +105,7 @@
100105 return (
101106 <DirectoryView
102107 refs={refs}
108 clone={clone}
103109 space={space}
104110 repo={repo}
105111 rev={rev}
@@ -114,6 +120,7 @@
114120
115121function DirectoryView({
116122 refs,
123 clone,
117124 space,
118125 repo,
119126 rev,
@@ -128,11 +135,17 @@
128135 mode: 'html' | 'json'
129136 routes: FragmentRoutes
130137 refs?: ReactNode
138 clone?: ReactNode
131139}) {
132140 return (
133141 <div className="repo-code-view">
134142 <div className="repo-tree-card">
135 {refs ? <div className="repo-refs-row">{refs}</div> : null}
143 {refs ? (
144 <div className="repo-refs-row">
145 {refs}
146 {clone ? <div className="repo-refs-clone">{clone}</div> : null}
147 </div>
148 ) : null}
136149 <DirectoryCommitBar
137150 space={space}
138151 repo={repo}
@@ -374,11 +387,9 @@
374387 className="flex items-center gap-1.5 hover:underline"
375388 onClick={() => (isDir ? onNavigate(row.path) : onOpenFile({ path: row.path, oid: row.oid ?? undefined }))}
376389 >
377 {isDir ? (
378 <Folder className="h-4 w-4 shrink-0 text-shell-ink-muted" aria-hidden />
379 ) : (
380 <FileIcon className="h-4 w-4 shrink-0 text-shell-ink-muted" aria-hidden />
381 )}
390 <span className="repo-tree-icon" aria-hidden>
391 <img src={treeIconUrl(row.name, isDir)} alt="" width={16} height={16} decoding="async" />
392 </span>
382393 {row.name}
383394 {row.mode === 'Symlink' ? <span className="text-[11px] text-shell-ink-muted">→</span> : null}
384395 </button>
@@ -764,6 +775,7 @@
764775
765776function FileView({
766777 refs,
778 clone,
767779 space,
768780 repo,
769781 rev,
@@ -780,6 +792,7 @@
780792 file: FileRef
781793 line?: number
782794 refs?: ReactNode
795 clone?: ReactNode
783796 routes: FragmentRoutes
784797 onBack: () => void
785798}) {
@@ -799,7 +812,12 @@
799812 leaf={leaf}
800813 onNavigate={routes.onNavigate}
801814 />
802 {refs ? <div className="repo-refs-row">{refs}</div> : null}
815 {refs ? (
816 <div className="repo-refs-row">
817 {refs}
818 {clone ? <div className="repo-refs-clone">{clone}</div> : null}
819 </div>
820 ) : null}
803821 <div className="repo-file-toolbar flex flex-wrap items-center gap-2">
804822 <button
805823 type="button"
Modified web/src/pages/workspace/ui/layout/repo-layout.tsx web/src/pages/workspace/ui/layout/repo-layout.tsx +81 −30 View web/src/pages/workspace/ui/layout/repo-layout.tsx
@@ -1,5 +1,5 @@
11import { useEffect, useLayoutEffect, useMemo, useRef, useState, type ReactNode } from 'react'
2import { ChevronDown, File, Folder, FolderGit2, GitBranch, Info, MoreHorizontal, Tag } from 'lucide-react'
2import { Activity, ChevronDown, Code, File, Folder, FolderGit2, GitBranch, GitCommitHorizontal, GitPullRequest, Info, MoreHorizontal, Settings, Sparkles, Tag, type LucideIcon } from 'lucide-react'
33import { useT, type MessageKey } from '@/shared/i18n'
44import { AppShell, ShellTopBar, type AppShellProps } from '@/shared/ui'
55import type { RepoTabId } from '../../model/route'
@@ -28,9 +28,12 @@
2828 * settings endpoint is space-owner / repo-admin only, so the page passes
2929 * false when the viewer is neither. Defaults to the least privilege. */
3030 canAdmin?: boolean
31 /** 打开的合并请求数:合并请求页签的数字徽标;0 或缺省时不显示。 */
32 pullsCount?: number
3133 /** Interactive branch/tag picker replacing the static ref chips. */
3234 refsSlot?: ReactNode
33 /** Hide the toolbar refs row when the page owns it inside its content. */
35 /** Show the toolbar refs row; hidden by default because the pages render the
36 * refs picker with their own content instead. */
3437 showRefs?: boolean
3538 /** Right segment of the repo toolbar (clone box). */
3639 clone?: ReactNode
@@ -65,8 +68,9 @@
6568 activeTab = 'code',
6669 onTabChange,
6770 canAdmin = false,
71 pullsCount,
6872 refsSlot,
69 showRefs = true,
73 showRefs = false,
7074 clone,
7175 branch,
7276 ref,
@@ -103,7 +107,7 @@
103107 </>
104108 )}
105109 </div> : null}
106 <div className="repo-toolbar-tabs">{tabs ?? <DefaultTabs active={activeTab} onChange={onTabChange} canAdmin={canAdmin} tabOptions={tabOptions} />}</div>
110 <div className="repo-toolbar-tabs">{tabs ?? <DefaultTabs active={activeTab} onChange={onTabChange} canAdmin={canAdmin} tabOptions={tabOptions} pullsCount={pullsCount} />}</div>
107111 <div className={clone ? 'repo-toolbar-clone' : 'repo-toolbar-clone empty'}>
108112 {showAside ? (
109113 <button type="button" className="repo-overview-toggle workspace-icon-button" title={t('shell.repo.overview')} aria-label={t('shell.repo.overview')} aria-expanded={overviewOpen} onClick={() => setOverviewOpen(value => !value)}><Info size={18} /></button>
@@ -154,17 +158,54 @@
154158}
155159
156160/** One label's chrome; the probe reuses it so measured widths match the real
157 * buttons exactly (the active variant only swaps colours and a shadow). */
161 * buttons exactly (the active variant only swaps colours and adds the bar). */
158162const TAB_ITEM_CLASS =
159 'flex h-full shrink-0 items-center whitespace-nowrap rounded-lg px-3 text-[13.5px] font-medium transition-colors'
163 'relative flex h-full shrink-0 items-center gap-1.5 whitespace-nowrap px-3 text-[13.5px] font-medium transition-colors'
160164
161const tabItemClass = (isActive: boolean) =>
165const tabItemClass = (isActive: boolean, id: RepoTabId) =>
162166 `${TAB_ITEM_CLASS} ${
163 isActive
164 ? 'bg-shell-surface text-shell-ink shadow-[inset_0_0_0_1px_var(--shell-hairline)]'
165 : 'text-shell-ink-muted hover:text-shell-ink'
167 isActive ? 'text-[#ff8400]' : id === 'ai' ? 'text-[#7c3aed] hover:text-[#6d28d9]' : 'text-shell-ink-muted hover:text-shell-ink'
166168 }`
167169
170/** 页签图标;`pull` 是合并请求详情的路由 id,与 `pulls` 用同一图标。 */
171const TAB_ICONS: Record<RepoTabId, LucideIcon> = {
172 code: Code,
173 commits: GitCommitHorizontal,
174 pulls: GitPullRequest,
175 pull: GitPullRequest,
176 branches: GitBranch,
177 pipelines: Activity,
178 releases: Tag,
179 ai: Sparkles,
180 settings: Settings,
181}
182
183/** 激活页签的底部指示条。 */
184function TabIndicator() {
185 return <span aria-hidden className="absolute inset-x-2 -bottom-px h-0.5 rounded-full bg-[#ff8400]" />
186}
187
188/** 页签内容(图标 + 文案 + 徽标);探针复用同一份渲染,量出的宽度才和真实按钮一致。 */
189function TabContent({ id, labelKey, pullsCount }: { id: RepoTabId; labelKey: MessageKey; pullsCount?: number }) {
190 const { t } = useT()
191 const Icon = TAB_ICONS[id]
192 const count = id === 'pulls' ? (pullsCount ?? 0) : 0
193 return (
194 <>
195 <Icon className="h-3.5 w-3.5 shrink-0" aria-hidden />
196 <span>{t(labelKey)}</span>
197 {count > 0 ? (
198 <span className="min-w-[18px] rounded-full bg-shell-field px-1 text-center text-[11px] font-semibold leading-[18px] text-shell-ink-muted">
199 {count > 99 ? '99+' : count}
200 </span>
201 ) : null}
202 {id === 'ai' ? (
203 <span className="rounded bg-[#8b5cf6]/10 px-1 text-[10px] font-semibold leading-4 text-[#6d28d9]">BETA</span>
204 ) : null}
205 </>
206 )
207}
208
168209/** Width reserved for the "…" trigger when part of the strip is hidden. */
169210const MORE_WIDTH = 28
170211
@@ -180,11 +221,13 @@
180221 onChange,
181222 canAdmin,
182223 tabOptions,
224 pullsCount,
183225}: {
184226 active: NonNullable<RepoLayoutProps['activeTab']>
185227 onChange?: (tab: RepoTabId) => void
186228 canAdmin: boolean
187229 tabOptions?: RepoTabOptions
230 pullsCount?: number
188231}) {
189232 const { t, locale } = useT()
190233 const stripRef = useRef<HTMLElement>(null)
@@ -286,12 +329,16 @@
286329
287330 if (!onChange) {
288331 return (
289 <nav className="flex h-9 w-fit items-center gap-1 rounded-[11px] border border-shell-hairline bg-shell-field p-[3px]">
290 {labels.map(([id, key]) => (
291 <span key={id} className={tabItemClass(tabGroup(active) === id)}>
292 {t(key)}
293 </span>
294 ))}
332 <nav className="flex h-9 w-fit items-center gap-1">
333 {labels.map(([id, key]) => {
334 const isActive = tabGroup(active) === id
335 return (
336 <span key={id} className={tabItemClass(isActive, id)}>
337 <TabContent id={id} labelKey={key} pullsCount={pullsCount} />
338 {isActive ? <TabIndicator /> : null}
339 </span>
340 )
341 })}
295342 </nav>
296343 )
297344 }
@@ -299,32 +346,36 @@
299346 return (
300347 <nav
301348 ref={stripRef}
302 className="relative flex h-9 w-fit items-center gap-1 rounded-[11px] border border-shell-hairline bg-shell-field p-[3px]"
349 className="relative flex h-9 w-fit items-center gap-1"
303350 >
304351 <nav
305352 ref={probeRef}
306353 aria-hidden="true"
307 className="pointer-events-none fixed -left-[9999px] top-0 flex items-center gap-1 p-[3px] opacity-0"
354 className="pointer-events-none fixed -left-[9999px] top-0 flex items-center gap-1 opacity-0"
308355 >
309356 {labels.map(([id, key]) => (
310357 <span key={id} className={`${TAB_ITEM_CLASS} text-shell-ink-muted`}>
311 {t(key)}
358 <TabContent id={id} labelKey={key} pullsCount={pullsCount} />
312359 </span>
313360 ))}
314361 </nav>
315362 {labels
316363 .filter(([id]) => shown.includes(id))
317 .map(([id, key]) => (
318 <button
319 key={id}
320 type="button"
321 aria-current={tabGroup(active) === id ? 'page' : undefined}
322 onClick={() => onChange(id)}
323 className={tabItemClass(tabGroup(active) === id)}
324 >
325 {t(key)}
326 </button>
327 ))}
364 .map(([id, key]) => {
365 const isActive = tabGroup(active) === id
366 return (
367 <button
368 key={id}
369 type="button"
370 aria-current={isActive ? 'page' : undefined}
371 onClick={() => onChange(id)}
372 className={tabItemClass(isActive, id)}
373 >
374 <TabContent id={id} labelKey={key} pullsCount={pullsCount} />
375 {isActive ? <TabIndicator /> : null}
376 </button>
377 )
378 })}
328379 {hidden.length > 0 ? (
329380 <div ref={menuRef} className="relative ml-auto flex h-full items-center">
330381 <button
Modified web/src/pages/workspace/ui/repo/clone-menu.tsx web/src/pages/workspace/ui/repo/clone-menu.tsx +4 −4 View web/src/pages/workspace/ui/repo/clone-menu.tsx
@@ -1,5 +1,5 @@
11import { useEffect, useRef, useState } from 'react'
2import { Check, ChevronDown, Copy, GitFork, Loader2 } from 'lucide-react'
2import { Check, ChevronDown, Code, Copy, Loader2 } from 'lucide-react'
33import { useT } from '@/shared/i18n'
44import { createArchive, type ArchiveFormat, type CloneTarget } from '@/entities/repo'
55
@@ -91,11 +91,11 @@
9191 aria-expanded={open}
9292 aria-label={t('repo.clone.title')}
9393 onClick={() => setOpen(value => !value)}
94 className="flex h-9 shrink-0 items-center gap-1.5 rounded-[9px] border border-shell-hairline bg-shell-field px-2.5 text-[13px] font-medium transition-colors hover:brightness-95"
94 className="flex h-9 shrink-0 items-center gap-1.5 rounded-[9px] bg-[#1f883d] px-2.5 text-[13px] font-medium text-white transition-colors hover:brightness-110"
9595 >
96 <GitFork className="h-3.5 w-3.5 shrink-0 text-shell-ink-muted" aria-hidden />
96 <Code className="h-3.5 w-3.5 shrink-0" aria-hidden />
9797 <span className="whitespace-nowrap">{t('repo.clone.button')}</span>
98 <ChevronDown className="h-3 w-3 shrink-0 text-shell-ink-muted" aria-hidden />
98 <ChevronDown className="h-3 w-3 shrink-0" aria-hidden />
9999 </button>
100100 {open ? (
101101 <div
Modified web/src/pages/workspace/ui/repo/ref-picker.tsx web/src/pages/workspace/ui/repo/ref-picker.tsx +16 −12 View web/src/pages/workspace/ui/repo/ref-picker.tsx
@@ -29,7 +29,7 @@
2929 commitsCount,
3030 onShowCommits,
3131}: RefPickerProps) {
32 const { t, tc } = useT()
32 const { t } = useT()
3333 const branches = useAsync(() => listBranches(space, repo).catch(() => null), [space, repo], {
3434 cacheKey: repoKey(space, repo, 'refs:branches:100'),
3535 })
@@ -95,25 +95,26 @@
9595 aria-label={t('repo.refs.current', { rev: revLabel })}
9696 aria-expanded={open === 'branch'}
9797 onClick={event => toggle('branch', event.currentTarget)}
98 className="flex h-9 max-w-[220px] items-center gap-1.5 rounded-md bg-shell-field px-2.5 text-[13px] font-medium transition-colors hover:brightness-95"
98 className="repo-refs-trigger flex h-9 max-w-[220px] items-center gap-1.5 rounded-md bg-shell-field px-2.5 text-[13px] font-medium transition-colors hover:brightness-95"
9999 >
100100 <GitBranch className="h-3.5 w-3.5 shrink-0 text-shell-ink-muted" aria-hidden />
101101 <span className="truncate">{revLabel}</span>
102102 <ChevronDown className="h-3 w-3 shrink-0 text-shell-ink-muted" aria-hidden />
103103 </button>
104 <span className="repo-refs-divider" aria-hidden />
104105 <button
105106 type="button"
106107 aria-haspopup="dialog"
107108 aria-controls={open === 'branch' ? popupId : undefined}
108109 aria-expanded={open === 'branch'}
109110 onClick={event => toggle('branch', event.currentTarget)}
110 className="flex h-9 shrink-0 items-center gap-1.5 rounded-md bg-shell-field px-2.5 text-[13px] font-medium transition-colors hover:brightness-95"
111 className="repo-refs-count"
111112 >
112113 <GitBranch className="h-3.5 w-3.5 shrink-0 text-shell-ink-muted" aria-hidden />
113 <span className="whitespace-nowrap">
114 {tc('repo.refs.branches', branchTotal)}
114 <span className="whitespace-nowrap">{t('repo.refs.branchLabel')}</span>
115 <span className="rounded-full bg-shell-active px-1.5 text-[11px] font-semibold leading-[18px] text-shell-ink-muted">
116 {branchTotal}
115117 </span>
116 <ChevronDown className="h-3 w-3 shrink-0 text-shell-ink-muted" aria-hidden />
117118 </button>
118119 <button
119120 type="button"
@@ -121,21 +122,24 @@
121122 aria-controls={open === 'tag' ? popupId : undefined}
122123 aria-expanded={open === 'tag'}
123124 onClick={event => toggle('tag', event.currentTarget)}
124 className="flex h-9 shrink-0 items-center gap-1.5 rounded-md bg-shell-field px-2.5 text-[13px] font-medium transition-colors hover:brightness-95"
125 className="repo-refs-count"
125126 >
126127 <Tag className="h-3.5 w-3.5 shrink-0 text-shell-ink-muted" aria-hidden />
127 <span className="whitespace-nowrap">
128 {tc('repo.refs.tags', tagTotal)}
128 <span className="whitespace-nowrap">{t('repo.refs.tagLabel')}</span>
129 <span className="rounded-full bg-shell-active px-1.5 text-[11px] font-semibold leading-[18px] text-shell-ink-muted">
130 {tagTotal}
129131 </span>
130 <ChevronDown className="h-3 w-3 shrink-0 text-shell-ink-muted" aria-hidden />
131132 </button>
132133 <button
133134 type="button"
134135 onClick={onShowCommits}
135 className="flex h-9 shrink-0 items-center gap-1.5 rounded-md bg-shell-field px-2.5 text-[13px] font-medium transition-colors hover:brightness-95"
136 className="repo-refs-count"
136137 >
137138 <GitCommitHorizontal className="h-3.5 w-3.5 shrink-0 text-shell-ink-muted" aria-hidden />
138 <span className="whitespace-nowrap">{tc('repo.refs.commits', commitsCount)}</span>
139 <span className="whitespace-nowrap">{t('repo.refs.commitLabel')}</span>
140 <span className="rounded-full bg-shell-active px-1.5 text-[11px] font-semibold leading-[18px] text-shell-ink-muted">
141 {commitsCount}
142 </span>
139143 </button>
140144 {open ? (
141145 <div id={popupId} role="dialog" aria-label={open === 'branch' ? t('repo.branch.switch') : t('repo.tag.switch')} className="repo-ref-popover absolute left-0 top-full z-40 mt-2 w-72 rounded-lg border border-shell-hairline bg-shell-surface p-2 shadow-xl">
Modified web/src/pages/workspace/ui/repo/repo-polish.css web/src/pages/workspace/ui/repo/repo-polish.css +10 −9 View web/src/pages/workspace/ui/repo/repo-polish.css
@@ -1,22 +1,25 @@
11
22.repo-view-content { min-height: 0; padding-top: 4px; }
33.repo-refs-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 7px 12px; border-bottom: 1px solid var(--shell-hairline); background: color-mix(in srgb, var(--shell-field) 45%, var(--shell-surface)); }
4.repo-refs-row > .repo-refs { width: 100%; }
5.repo-refs-row > .repo-refs > button { min-height: 36px; border: 1px solid var(--shell-hairline); border-radius: 9px; background: var(--shell-field); }
4.repo-refs-row > .repo-refs { min-width: 0; flex: 1 1 auto; }
5.repo-refs-row > .repo-refs-clone { display: flex; flex: none; align-items: center; margin-left: auto; }
6.repo-refs-trigger { min-height: 36px; border: 1px solid var(--shell-hairline); border-radius: 9px; background: var(--shell-field); font-size: 13px; transition: color 160ms, border-color 160ms, background 160ms; }
7.repo-refs-trigger:hover, .repo-refs-trigger[aria-expanded='true'] { border-color: var(--shell-tile); color: var(--shell-ink-muted); background: var(--shell-field); }
8.repo-refs-divider { width: 1px; height: 20px; flex: none; margin-inline: 2px; background: var(--shell-hairline); }
9.repo-refs-count { display: flex; height: 36px; flex: none; align-items: center; gap: 6px; border-radius: 8px; padding-inline: 8px; font-size: 13px; font-weight: 500; color: var(--shell-ink); transition: background 160ms; }
10.repo-refs-count:hover { background: var(--shell-field); }
611.repo-toolbar { background: var(--shell-surface); }
712.repo-toolbar-tabs { order: 1; padding-left: 0; border-left: 0; }
813.repo-toolbar-clone { order: 2; }
914.repo-toolbar-refs { order: 3; flex: 1 1 100%; }
10.repo-toolbar-refs > span,
11.repo-toolbar-refs > .repo-refs > button { min-height: 36px; border: 1px solid var(--shell-hairline); border-radius: 9px; background: var(--shell-field); }
12.repo-toolbar-tabs nav { box-shadow: 0 1px 2px rgb(24 28 31 / 2%); }
15.repo-toolbar-refs > span { min-height: 36px; border: 1px solid var(--shell-hairline); border-radius: 9px; background: var(--shell-field); }
1316.repo-toolbar-tabs button, .repo-toolbar-tabs span { transition: color 160ms, background 160ms; }
1417
1518.repo-toolbar .repo-toolbar-tabs { flex: 1 1 20rem; min-width: 20rem; max-width: calc(100% - 3rem); }
1619.repo-toolbar-tabs > nav { flex: 0 0 auto; }
1720.repo-toolbar .repo-toolbar-tabs { overflow: visible; }
1821.repo-tabs-more { display: flex; height: 100%; width: 1.75rem; flex-shrink: 0; align-items: center; justify-content: center; border-radius: 8px; color: var(--shell-ink-muted); transition: color 160ms, background 160ms; }
19.repo-tabs-more:hover, .repo-tabs-more[aria-expanded='true'] { color: var(--shell-ink); background: var(--shell-surface); }
22.repo-tabs-more:hover, .repo-tabs-more[aria-expanded='true'] { color: var(--shell-ink); background: var(--shell-field); }
2023.repo-tabs-menu button { min-height: 32px; border-radius: 7px; }
2124.repo-tabs-menu button:hover { background: var(--shell-field); }
2225.repo-toolbar .repo-toolbar-clone.empty { display: flex; flex: 0 0 auto; margin-left: auto; }
@@ -67,8 +70,6 @@
6770.repo-pdf-page { display: flex; justify-content: center; }
6871.repo-pdf-page img { max-width: 100%; height: auto; border-radius: 3px; background: #fff; box-shadow: 0 1px 3px rgb(24 28 31 / 18%); }
6972.repo-pdf-note { padding: 6px 0; text-align: center; font-size: 12.5px; color: var(--shell-ink-muted); }
70.repo-refs > button { min-height: 36px; border: 1px solid var(--shell-hairline); border-radius: 9px; background: var(--shell-field); font-size: 13px; transition: color 160ms, border-color 160ms, background 160ms; }
71.repo-refs > button:hover, .repo-refs > button[aria-expanded='true'] { border-color: var(--shell-tile); color: var(--shell-ink-muted); background: var(--shell-field); }
7273.repo-ref-popover { border-radius: 12px; box-shadow: 0 16px 44px rgb(24 28 31 / 16%); }
7374.repo-ref-search { transition: border-color 160ms, background 160ms, box-shadow 160ms; }
7475.repo-ref-search:hover { border-color: var(--shell-tile); }
@@ -114,7 +115,7 @@
114115@media (max-width: 760px) {
115116 .repo-view-content { padding-top: 0; }
116117 .repo-commit-row, .repo-branch-row { flex-wrap: wrap; row-gap: 8px; }
117 .repo-toolbar-tabs button, .repo-toolbar-tabs span { padding-inline: 10px; }
118 .repo-toolbar-tabs nav > button, .repo-toolbar-tabs nav > span { padding-inline: 10px; }
118119 .repo-file-toolbar { padding: 8px 10px; }
119120 .repo-directory-header { padding: 8px 10px; }
120121 .repo-code-header { padding: 8px; }
Modified web/src/pages/workspace/ui/repo/repo-view.tsx web/src/pages/workspace/ui/repo/repo-view.tsx +7 −3 View web/src/pages/workspace/ui/repo/repo-view.tsx
@@ -192,17 +192,20 @@
192192 onShowCommits={() => setTab('commits')}
193193 />
194194 )
195 // 克隆菜单跟着 refs 行走:代码页签的 refs 在内容卡片里,克隆放行尾;其它页签留在工具栏。
196 const cloneMenu = (
197 <RepoCloneMenu targets={clones} space={spaceName} repo={repoName} rev={rev || repo.default_branch || ''} />
198 )
195199 return (
196200 <RepoLayout
197201 {...shell}
198202 title={`${spaceName} / ${repo.name}`}
199203 topic={repo.description ?? undefined}
200 refsSlot={refsPicker}
201 showRefs={tab !== 'code'}
202 clone={<RepoCloneMenu targets={clones} space={spaceName} repo={repoName} rev={rev || repo.default_branch || ''} />}
204 clone={tab === 'code' ? undefined : cloneMenu}
203205 activeTab={tab}
204206 onTabChange={setTab}
205207 canAdmin={canManageRepo}
208 pullsCount={repo.pulls_count}
206209 showAside={tab === 'code'}
207210 tabOptions={{
208211 allowPullRequests: repo.allow_pull_requests,
@@ -238,6 +241,7 @@
238241 rev={rev}
239242 repoDto={repo}
240243 refs={refsPicker}
244 clone={cloneMenu}
241245 mode={mode}
242246 path={path}
243247 file={file}
Modified web/src/shared/i18n/messages/en/repo.ts web/src/shared/i18n/messages/en/repo.ts +4 −10 View web/src/shared/i18n/messages/en/repo.ts
@@ -37,7 +37,7 @@
3737 'repo.clone.https': 'HTTPS',
3838 'repo.clone.ssh': 'SSH',
3939 'repo.clone.copied': 'Clone URL copied.',
40 'repo.clone.button': 'Clone',
40 'repo.clone.button': 'Clone / Code',
4141 'repo.clone.withSsh': 'Clone with SSH',
4242 'repo.clone.withHttps': 'Clone with HTTPS',
4343 'repo.clone.download': 'Download source code',
@@ -413,15 +413,9 @@
413413 'repo.refs.selectBranch': 'Select branch',
414414 'repo.refs.commit': '{sha} (commit)',
415415 'repo.refs.current': 'Current revision {rev}; select a branch',
416 'repo.refs.branches': '{count} branches',
417 'repo.refs.branches_one': '{count} branch',
418 'repo.refs.branches_other': '{count} branches',
419 'repo.refs.tags': '{count} tags',
420 'repo.refs.tags_one': '{count} tag',
421 'repo.refs.tags_other': '{count} tags',
422 'repo.refs.commits': '{count} commits',
423 'repo.refs.commits_one': '{count} commit',
424 'repo.refs.commits_other': '{count} commits',
416 'repo.refs.branchLabel': 'Branches',
417 'repo.refs.tagLabel': 'Tags',
418 'repo.refs.commitLabel': 'Commits',
425419 'repo.refs.searchBranches': 'Search branches',
426420 'repo.refs.searchTags': 'Search tags',
427421 'repo.refs.noMatch': 'No matches — try a different keyword.',
Modified web/src/shared/i18n/messages/zh-CN/repo.ts web/src/shared/i18n/messages/zh-CN/repo.ts +4 −10 View web/src/shared/i18n/messages/zh-CN/repo.ts
@@ -37,7 +37,7 @@
3737 'repo.clone.https': 'HTTPS',
3838 'repo.clone.ssh': 'SSH',
3939 'repo.clone.copied': '克隆地址已复制。',
40 'repo.clone.button': '克隆',
40 'repo.clone.button': '克隆/代码',
4141 'repo.clone.withSsh': '使用 SSH 克隆',
4242 'repo.clone.withHttps': '使用 HTTPS 克隆',
4343 'repo.clone.download': '下载源代码',
@@ -413,15 +413,9 @@
413413 'repo.refs.selectBranch': '选择分支',
414414 'repo.refs.commit': '{sha} (提交)',
415415 'repo.refs.current': '当前修订 {rev},选择分支',
416 'repo.refs.branches': '{count} 分支',
417 'repo.refs.branches_one': '{count} 分支',
418 'repo.refs.branches_other': '{count} 分支',
419 'repo.refs.tags': '{count} 标签',
420 'repo.refs.tags_one': '{count} 标签',
421 'repo.refs.tags_other': '{count} 标签',
422 'repo.refs.commits': '{count} 提交',
423 'repo.refs.commits_one': '{count} 提交',
424 'repo.refs.commits_other': '{count} 提交',
416 'repo.refs.branchLabel': '分支',
417 'repo.refs.tagLabel': '标签',
418 'repo.refs.commitLabel': '提交',
425419 'repo.refs.searchBranches': '搜索分支',
426420 'repo.refs.searchTags': '搜索标签',
427421 'repo.refs.noMatch': '没有匹配的结果,试试其他关键词。',