diff --git a/docs/frontend-analytics-manifest.json b/docs/frontend-analytics-manifest.json index 54da16de0..107800167 100644 --- a/docs/frontend-analytics-manifest.json +++ b/docs/frontend-analytics-manifest.json @@ -1,7 +1,7 @@ { "schema": 1, "description": "Source declarations, not runtime DOM counts, pageviews, successful outcomes or a closed runtime enum. Forwarding sites require explicit callers. See frontend-analytics.md for gaps and event policy.", - "counts": {"routes":54,"uniqueIds":804,"literalJsxSites":822,"literalJsxValues":976,"navigationDefinitions":29,"forwardingSites":36}, + "counts": {"routes":54,"uniqueIds":804,"literalJsxSites":823,"literalJsxValues":977,"navigationDefinitions":29,"forwardingSites":36}, "routes": [ {"name":"landingRoute","path":"/","declaration":{"file":"web/src/app/router.tsx","line":258},"implementation":{"file":"web/src/pages/landing.tsx","line":257,"export":"LandingPage"},"guarded":false,"roleProtected":false}, {"name":"skillsRoute","path":"skills","declaration":{"file":"web/src/app/router.tsx","line":264},"implementation":{"file":"web/src/pages/home.tsx","line":12,"export":"HomePage"},"guarded":false,"roleProtected":false}, @@ -19,7 +19,7 @@ {"name":"skillDetailRoute","path":"/space/$namespace/$slug","declaration":{"file":"web/src/app/router.tsx","line":364},"implementation":{"file":"web/src/pages/skill-detail.tsx","line":133,"export":"SkillDetailPage"},"guarded":false,"roleProtected":false}, {"name":"skillVersionCompareRoute","path":"/space/$namespace/$slug/compare","declaration":{"file":"web/src/app/router.tsx","line":374},"implementation":{"file":"web/src/pages/skill-version-compare.tsx","line":5,"export":"SkillVersionComparePage"},"guarded":false,"roleProtected":false}, {"name":"dashboardRoute","path":"dashboard","declaration":{"file":"web/src/app/router.tsx","line":384},"implementation":{"file":"web/src/pages/dashboard.tsx","line":98,"export":"DashboardPage"},"guarded":true,"roleProtected":false}, - {"name":"dashboardSkillsRoute","path":"dashboard/skills","declaration":{"file":"web/src/app/router.tsx","line":391},"implementation":{"file":"web/src/pages/dashboard/my-skills.tsx","line":53,"export":"MySkillsPage"},"guarded":true,"roleProtected":false}, + {"name":"dashboardSkillsRoute","path":"dashboard/skills","declaration":{"file":"web/src/app/router.tsx","line":391},"implementation":{"file":"web/src/pages/dashboard/my-skills.tsx","line":54,"export":"MySkillsPage"},"guarded":true,"roleProtected":false}, {"name":"dashboardPublishRoute","path":"dashboard/publish","declaration":{"file":"web/src/app/router.tsx","line":404},"implementation":{"file":"web/src/pages/dashboard/publish.tsx","line":41,"export":"PublishPage"},"guarded":true,"roleProtected":false}, {"name":"dashboardSuiteCreateRoute","path":"dashboard/suites/new","declaration":{"file":"web/src/app/router.tsx","line":430},"implementation":{"file":"web/src/pages/dashboard/suite-editor.tsx","line":716,"export":"SuiteCreatePage"},"guarded":true,"roleProtected":false}, {"name":"dashboardSuitesRoute","path":"dashboard/suites","declaration":{"file":"web/src/app/router.tsx","line":437},"implementation":{"file":"web/src/pages/dashboard/my-suites.tsx","line":21,"export":"MySuitesPage"},"guarded":true,"roleProtected":false}, @@ -547,33 +547,34 @@ {"file":"web/src/pages/dashboard/my-namespaces.tsx","line":138,"host":"Button","prop":"data-analytics-id","event":"click","ids":["github.namespace.open"]}, {"file":"web/src/pages/dashboard/my-namespaces.tsx","line":148,"host":"Pagination","prop":"analyticsId","event":"click","ids":["namespace.list.paginate"]}, {"file":"web/src/pages/dashboard/my-namespaces.tsx","line":157,"host":"Button","prop":"data-analytics-id","event":"click","ids":["namespace.create.open"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":358,"host":"form","prop":"data-analytics-id","event":"submit","ids":["skill.owned.search"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":383,"host":"SelectTrigger","prop":"data-analytics-id","event":"click+keydown-intent","ids":["skill.owned.namespace.interact"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":387,"host":"SelectItem","prop":"data-analytics-id","event":"radix-item-intent","ids":["skill.owned.namespace"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":389,"host":"SelectItem","prop":"data-analytics-id","event":"radix-item-intent","ids":["skill.owned.namespace"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":401,"host":"Button","prop":"data-analytics-id","event":"click","ids":["skill.owned.filter"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":419,"host":"Button","prop":"data-analytics-id","event":"click","ids":["skill.owned.clear"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":453,"host":"Card","prop":"data-analytics-id","event":"click","ids":["skill.owned.open"],"conditional":true}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":477,"host":"Button","prop":"data-analytics-id","event":"click","ids":["skill.update.open"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":503,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.related.open"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":506,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.visibility.open"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":510,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.review.withdraw"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":512,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.promotion.create.request","skill.promotion.update.request"],"conditional":true}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":515,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.unarchive.request"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":517,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.archive.request"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":520,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.restore.request"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":549,"host":"Pagination","prop":"analyticsId","event":"click","ids":["skill.owned.paginate"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":558,"host":"Button","prop":"data-analytics-id","event":"click","ids":["skill.owned.clear"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":580,"host":"ConfirmDialog","prop":"confirmAnalyticsId","event":"click","ids":["skill.restore.confirm"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":580,"host":"ConfirmDialog","prop":"cancelAnalyticsId","event":"click","ids":["skill.restore.cancel"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":593,"host":"ConfirmDialog","prop":"confirmAnalyticsId","event":"click","ids":["skill.promotion.create.confirm","skill.promotion.update.confirm"],"conditional":true}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":593,"host":"ConfirmDialog","prop":"cancelAnalyticsId","event":"click","ids":["skill.promotion.create.cancel","skill.promotion.update.cancel"],"conditional":true}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":609,"host":"ConfirmDialog","prop":"confirmAnalyticsId","event":"click","ids":["skill.archive.confirm"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":609,"host":"ConfirmDialog","prop":"cancelAnalyticsId","event":"click","ids":["skill.archive.cancel"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":622,"host":"ConfirmDialog","prop":"confirmAnalyticsId","event":"click","ids":["skill.unarchive.confirm"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":622,"host":"ConfirmDialog","prop":"cancelAnalyticsId","event":"click","ids":["skill.unarchive.cancel"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":635,"host":"ConfirmDialog","prop":"confirmAnalyticsId","event":"click","ids":["skill.review.withdraw.confirm"]}, - {"file":"web/src/pages/dashboard/my-skills.tsx","line":635,"host":"ConfirmDialog","prop":"cancelAnalyticsId","event":"click","ids":["skill.review.withdraw.cancel"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":362,"host":"form","prop":"data-analytics-id","event":"submit","ids":["skill.owned.search"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":387,"host":"SelectTrigger","prop":"data-analytics-id","event":"click+keydown-intent","ids":["skill.owned.namespace.interact"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":391,"host":"SelectItem","prop":"data-analytics-id","event":"radix-item-intent","ids":["skill.owned.namespace"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":393,"host":"SelectItem","prop":"data-analytics-id","event":"radix-item-intent","ids":["skill.owned.namespace"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":405,"host":"Button","prop":"data-analytics-id","event":"click","ids":["skill.owned.filter"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":423,"host":"Button","prop":"data-analytics-id","event":"click","ids":["skill.owned.clear"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":457,"host":"Card","prop":"data-analytics-id","event":"click","ids":["skill.owned.open"],"conditional":true}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":481,"host":"Button","prop":"data-analytics-id","event":"click","ids":["skill.update.open"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":507,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.update.open"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":511,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.related.open"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":514,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.visibility.open"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":518,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.review.withdraw"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":520,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.promotion.create.request","skill.promotion.update.request"],"conditional":true}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":523,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.unarchive.request"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":525,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.archive.request"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":528,"host":"DropdownMenuItem","prop":"data-analytics-id","event":"click","ids":["skill.restore.request"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":562,"host":"Pagination","prop":"analyticsId","event":"click","ids":["skill.owned.paginate"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":571,"host":"Button","prop":"data-analytics-id","event":"click","ids":["skill.owned.clear"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":593,"host":"ConfirmDialog","prop":"confirmAnalyticsId","event":"click","ids":["skill.restore.confirm"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":593,"host":"ConfirmDialog","prop":"cancelAnalyticsId","event":"click","ids":["skill.restore.cancel"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":606,"host":"ConfirmDialog","prop":"confirmAnalyticsId","event":"click","ids":["skill.promotion.create.confirm","skill.promotion.update.confirm"],"conditional":true}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":606,"host":"ConfirmDialog","prop":"cancelAnalyticsId","event":"click","ids":["skill.promotion.create.cancel","skill.promotion.update.cancel"],"conditional":true}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":622,"host":"ConfirmDialog","prop":"confirmAnalyticsId","event":"click","ids":["skill.archive.confirm"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":622,"host":"ConfirmDialog","prop":"cancelAnalyticsId","event":"click","ids":["skill.archive.cancel"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":635,"host":"ConfirmDialog","prop":"confirmAnalyticsId","event":"click","ids":["skill.unarchive.confirm"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":635,"host":"ConfirmDialog","prop":"cancelAnalyticsId","event":"click","ids":["skill.unarchive.cancel"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":648,"host":"ConfirmDialog","prop":"confirmAnalyticsId","event":"click","ids":["skill.review.withdraw.confirm"]}, + {"file":"web/src/pages/dashboard/my-skills.tsx","line":648,"host":"ConfirmDialog","prop":"cancelAnalyticsId","event":"click","ids":["skill.review.withdraw.cancel"]}, {"file":"web/src/pages/dashboard/my-suites.tsx","line":55,"host":"Button","prop":"data-analytics-id","event":"click","ids":["suite.create.open"]}, {"file":"web/src/pages/dashboard/my-suites.tsx","line":57,"host":"form","prop":"data-analytics-id","event":"submit","ids":["suite.owned.search"]}, {"file":"web/src/pages/dashboard/my-suites.tsx","line":63,"host":"button","prop":"data-analytics-id","event":"click","ids":["suite.query.clear"]}, diff --git a/openspec/changes/improve-my-skills-management/design.md b/openspec/changes/improve-my-skills-management/design.md index 7dbf8cd89..135afd105 100644 --- a/openspec/changes/improve-my-skills-management/design.md +++ b/openspec/changes/improve-my-skills-management/design.md @@ -37,3 +37,18 @@ ## 验证方式 先完成一个可执行筛选切片并创建 Draft PR,再完成卡片和操作。实现收敛后执行一次集中独立 Review,随后冻结代码,形成需求追踪矩阵并运行源检查、精确提交镜像及浏览器关键业务路径。截图只证明视觉结果,不替代接口与最终状态断言。 + + +## 原型视觉基线与页面隔离 + +以已确认原生 Figma 稿及最终执行生成脚本为视觉来源,原生 Medium 字重采用500;本地 HTML 仅作辅助对照;旧覆盖前的30px操作高度不作为基线。所有尺寸仅作用于“我的技能”,不改变共享组件或其他管理页。 + +| 区域 | 本页基线 | 生产适配 | +| --- | --- | --- | +| 标题/简介/元数据/状态 | 15px/24px、12px/20px、11px/18px、10px/20px | 窄屏不进一步缩字,标题最多两行,完整名称保留于title及详情 | +| 状态与图标 | 状态圆角5px、内边距0/7px、低饱和底色;元数据图标13px | 小字前景色加深到4.5:1对比;真实头像或首字回退32px,不推断技能类别 | +| 卡片与操作 | 圆角12px、卡片间距12px、桌面内边距18px/20px、移动14px | 更新72×32px、更多32×32px;768px以下更新进入菜单,保留相同权限及预填参数 | +| 页面框架 | 仅/dashboard/skills使用1440px外壳,桌面右栏左右40px、顶部36px | 手机左右16px、顶部18px;仅本页侧栏添加桌面20px水平/32px顶部及移动16px内边距,公共导航及侧栏行为和其他管理页保持原样 | +| 筛选与菜单 | 筛选控件40px、胶囊32px;菜单宽208px、圆角10px、条目36px、图标16px;发布按钮36px | 不伪造状态计数;浅色与深色分别适配 | + +原型中的彩色类别头像与固定技能数据是演示样例,不能据此生成生产语义;真实技能图片、生命周期、只读状态与审核目标仍来自现有接口。短可见性标签和图标旁保留完整范围说明,长坐标可截断但保留完整悬停文本及复制操作。公共导航高度及内容继续遵循应用外壳,不宣称整个应用逐像素复刻原型。 diff --git a/openspec/changes/improve-my-skills-management/specs/my-skills-management/spec.md b/openspec/changes/improve-my-skills-management/specs/my-skills-management/spec.md index 046854311..f509d72a4 100644 --- a/openspec/changes/improve-my-skills-management/specs/my-skills-management/spec.md +++ b/openspec/changes/improve-my-skills-management/specs/my-skills-management/spec.md @@ -51,3 +51,9 @@ - **WHEN** 菜单操作完成,查询结果移除了原技能行及其操作入口 - **THEN** 若焦点因入口消失落到页面主体,焦点返回列表搜索控件 - **AND** 原行保留时恢复原入口;用户已主动聚焦其他控件时不抢夺焦点 + + +#### Scenario: 原型视觉基线与其他页面隔离 +- **WHEN** 用户打开“我的技能”列表 +- **THEN** 卡片、筛选、标签和图标按设计文档的本页量化视觉基线展示,窄屏保留菜单内更新操作 +- **AND** 仅该路由使用宽版内容框架,其他管理页及公共导航不受影响 diff --git a/openspec/changes/improve-my-skills-management/tasks.md b/openspec/changes/improve-my-skills-management/tasks.md index faff71f22..0e117a43b 100644 --- a/openspec/changes/improve-my-skills-management/tasks.md +++ b/openspec/changes/improve-my-skills-management/tasks.md @@ -12,6 +12,7 @@ ## 3. 验证与交付 - [x] 筛选首片针对性测试与静态检查 - [x] 创建 Draft PR 并持续更新(#1062) -- [ ] 集中独立 Review 与修复 +- [x] 集中独立 Review 与修复(功能、子路径与焦点问题已修复并复核) +- [ ] 原型视觉专项:图标、字号、标签、路由框架及明暗/移动端浏览器对照 - [ ] 冻结代码后的需求场景矩阵、源检查与精确提交运行时验证 - [ ] 保留人工预览入口并交付验证结果 diff --git a/web/src/app/layout-main-content.test.ts b/web/src/app/layout-main-content.test.ts index 6ef027d48..e43d2fd3b 100644 --- a/web/src/app/layout-main-content.test.ts +++ b/web/src/app/layout-main-content.test.ts @@ -5,6 +5,10 @@ import { CENTERED_MAIN_CLASS_NAME, CENTERED_SEARCH_CONTENT_CLASS_NAME, DEFAULT_MAIN_CLASS_NAME, + MY_SKILLS_MAIN_CLASS_NAME, + MY_SKILLS_CONTENT_CLASS_NAME, + MY_SKILLS_SIDEBAR_ROW_CLASS_NAME, + MY_SKILLS_SIDEBAR_CONTENT_CLASS_NAME, SPLIT_AUTH_MAIN_CLASS_NAME, getAppMainContentLayout, resolveAppMainContentPathname, @@ -28,8 +32,8 @@ describe('getAppMainContentLayout', () => { expect(layout.contentClassName).toContain('max-w-[1200px]') }) - it('centers all dashboard sub-pages within a slightly narrower content frame', () => { - const layout = getAppMainContentLayout('/dashboard/skills') + it.each(['/dashboard/tokens', '/dashboard/stars', '/settings/profile', '/dashboard/skills/unrelated'])('keeps %s in the existing dashboard frame', (pathname) => { + const layout = getAppMainContentLayout(pathname) expect(layout).toEqual({ mainClassName: CENTERED_MAIN_CLASS_NAME, @@ -38,6 +42,16 @@ describe('getAppMainContentLayout', () => { expect(layout.contentClassName).toContain('max-w-[1100px]') }) + it('isolates the prototype-width frame and sidebar gutters to My Skills', () => { + expect(getAppMainContentLayout('/dashboard/skills')).toEqual({ + mainClassName: MY_SKILLS_MAIN_CLASS_NAME, + contentClassName: MY_SKILLS_CONTENT_CLASS_NAME, + sidebarRowClassName: MY_SKILLS_SIDEBAR_ROW_CLASS_NAME, + sidebarContentClassName: MY_SKILLS_SIDEBAR_CONTENT_CLASS_NAME, + }) + expect(MY_SKILLS_CONTENT_CLASS_NAME).toContain('max-w-[1440px]') + }) + it('uses the full-bleed split layout for login and registration', () => { for (const pathname of ['/login', '/register']) { expect(getAppMainContentLayout(pathname)).toEqual({ diff --git a/web/src/app/layout-main-content.ts b/web/src/app/layout-main-content.ts index 9728a4575..ef64f32fd 100644 --- a/web/src/app/layout-main-content.ts +++ b/web/src/app/layout-main-content.ts @@ -5,6 +5,10 @@ export const DEFAULT_MAIN_CLASS_NAME = 'flex-1 relative z-10 px-6 py-10 md:px-12 export const CENTERED_MAIN_CLASS_NAME = 'flex-1 relative z-10 px-4 py-8 sm:px-6 md:px-12 md:py-10' export const CENTERED_SEARCH_CONTENT_CLASS_NAME = 'mx-auto w-full max-w-[1200px]' export const CENTERED_DASHBOARD_CONTENT_CLASS_NAME = 'mx-auto min-h-[calc(100vh-11rem)] w-full max-w-[1100px]' +export const MY_SKILLS_MAIN_CLASS_NAME = 'flex-1 relative z-10 bg-background' +export const MY_SKILLS_CONTENT_CLASS_NAME = 'mx-auto min-h-[calc(100vh-11rem)] w-full max-w-[1440px]' +export const MY_SKILLS_SIDEBAR_ROW_CLASS_NAME = 'flex flex-col lg:flex-row gap-0 [&>aside]:p-4 lg:[&>aside]:px-5 lg:[&>aside]:pt-8 lg:[&>aside]:pb-6 lg:[&>aside]:border-r lg:[&>aside]:border-border/40' +export const MY_SKILLS_SIDEBAR_CONTENT_CLASS_NAME = 'flex-1 min-w-0 px-4 pb-7 pt-[18px] lg:px-10 lg:pt-9' export const REVIEW_DETAIL_CONTENT_CLASS_NAME = 'mx-auto min-h-[calc(100vh-11rem)] w-full max-w-[1500px]' export const DASHBOARD_PATH_PREFIXES = ['/dashboard', '/settings/'] as const @@ -14,6 +18,8 @@ export const SPLIT_AUTH_PATHS = ['/login', '/register'] as const interface AppMainContentLayout { mainClassName: string contentClassName: string + sidebarRowClassName?: string + sidebarContentClassName?: string } export function resolveAppMainContentPathname( @@ -59,6 +65,15 @@ export function getAppMainContentLayout(pathname: string): AppMainContentLayout } } + if (pathname === '/dashboard/skills') { + return { + mainClassName: MY_SKILLS_MAIN_CLASS_NAME, + contentClassName: MY_SKILLS_CONTENT_CLASS_NAME, + sidebarRowClassName: MY_SKILLS_SIDEBAR_ROW_CLASS_NAME, + sidebarContentClassName: MY_SKILLS_SIDEBAR_CONTENT_CLASS_NAME, + } + } + if (pathname === '/dashboard' || pathname.startsWith('/dashboard/') || pathname.startsWith('/settings/')) { return { mainClassName: CENTERED_MAIN_CLASS_NAME, diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index 287a78f27..a0c7c1b2c 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -219,9 +219,9 @@ export function Layout() { >
{showSidebar ? ( -
+
-
+
diff --git a/web/src/pages/dashboard/my-skills-interactions.test.tsx b/web/src/pages/dashboard/my-skills-interactions.test.tsx index c19004034..00219a540 100644 --- a/web/src/pages/dashboard/my-skills-interactions.test.tsx +++ b/web/src/pages/dashboard/my-skills-interactions.test.tsx @@ -282,4 +282,16 @@ describe('My Skills interactions with real menu and dialog', () => { expect(document.activeElement?.isConnected).toBe(true) }) + + it('opens the existing prefilled update form from the mobile menu action', async () => { + render() + await openMenu() + fireEvent.click(screen.getByRole('menuitem', { name: 'mySkills.update' })) + expect(navigate).toHaveBeenCalledOnce() + expect(navigate).toHaveBeenCalledWith({ + to: '/dashboard/publish', + search: { namespace: 'team', updateSkill: 'demo', updateName: 'Demo', visibility: 'PRIVATE' }, + }) + }) + }) diff --git a/web/src/pages/dashboard/my-skills.module.css b/web/src/pages/dashboard/my-skills.module.css new file mode 100644 index 000000000..a6eb213de --- /dev/null +++ b/web/src/pages/dashboard/my-skills.module.css @@ -0,0 +1,157 @@ +/* Page-local density follows the confirmed My Skills prototype, not shared UI defaults. */ +.page .filterPanel { + border-radius: 12px; + margin-top: 26px; + padding: 16px; + background: #fafafa; + border-color: #e9e9ed; +} + +.page .filterControl { + height: 40px; + border-radius: 8px; + font-size: 13px; + box-shadow: none; + border-color: #dedee5; +} + +.page .filterButton { + height: 32px; + min-width: 85px; + padding: 0 14px; + border-radius: 999px; + font-size: 12px; + box-shadow: none; +} + +.page .resultCount { + gap: 12px; + font-size: 11px; + line-height: 20px; +} + +.page .clearButton { + height: 24px; + padding: 0; + font-size: 11px; +} + +.page .skillCard { + padding: 18px 20px; + border-radius: 12px; + box-shadow: none; + border-color: #dedee5; +} + +.page .skillTitle { + font-size: 15px; + font-weight: 500; + line-height: 24px; + letter-spacing: .05px; +} + +.page .status { + display: inline-flex; + margin: 0; + padding: 0 7px; + border-radius: 5px; + font-size: 10px; + font-weight: 500; + line-height: 20px; + color: #666670; + background: #f1f2f4; +} + +.page .statusPublished { color: #357548; background: #eef7f0; } +.page .statusReview { color: #8b631f; background: #fff5e6; } +.page .statusRejected { color: #a64d46; background: #fff0ef; } + +:global(.dark) .page .status { color: #b9b9c2; background: #303039; } +:global(.dark) .page .statusPublished { color: #97d5ac; background: #193329; } +:global(.dark) .page .statusReview { color: #e7bf78; background: #3b2d19; } +:global(.dark) .page .statusRejected { color: #efa49b; background: #402625; } + +.page .description { + font-size: 12px; + line-height: 20px; + color: #666670; +} + +.page .metadata { + gap: 8px 17px; + font-size: 11px; + line-height: 18px; + color: #73737d; +} + +.page .metadataItem { + display: inline-flex; + align-items: center; + gap: 5px; + min-width: 0; +} + +.page .coordinate { max-width: 210px; color: #60738d; } +:global(.dark) .page .coordinate { color: #a1b5d0; } +.page .pendingVersion { color: #8b631f; } +:global(.dark) .page .pendingVersion { color: #d9b77e; } + +.page .updateButton, +.page .moreButton { + height: 32px; + border-radius: 8px; + box-shadow: none; + font-size: 12px; +} + +.page .updateButton { gap: 6px; padding: 0 8px; min-width: 72px; } +.page .moreButton { width: 32px; padding: 0; color: hsl(var(--muted-foreground)); } +.page .note { font-size: 12px; line-height: 20px; } + +@media (max-width: 639px) { + .page .filterPanel, + .page .skillCard { padding: 14px; } + .page .filterButton { min-width: 0; padding: 0 12px; } + .page .metadata { gap: 6px 12px; } + .page .coordinate { max-width: 150px; } +} + +:global(.dark) .page .filterPanel { background: hsl(var(--muted) / .3); border-color: hsl(var(--border) / .6); } +:global(.dark) .page .skillCard, +:global(.dark) .page .filterControl { border-color: hsl(var(--border) / .7); } +:global(.dark) .page .description, +:global(.dark) .page .metadata { color: hsl(var(--muted-foreground)); } + +.page h1 { font-size: 27px; line-height: 36px; font-weight: 500; letter-spacing: -.8px; } +.page .publishButton { height: 36px; padding: 0 12px; border-radius: 8px; font-size: 12px; } +.moreMenu.moreMenu { width: 208px; padding: 6px; border-radius: 10px; } +.moreMenu [role="menuitem"] { min-height: 36px; padding: 0 10px; font-size: 12px; } + +@media (max-width: 767px) { + .page .desktopUpdate { display: none; } + .page .skillTitle { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } + .page .skillTitle a { white-space: normal; overflow: visible; } +} +@media (min-width: 768px) { + .moreMenu .mobileUpdate { display: none; } +} + +.page .skillList { margin-top: 20px; } +.page h1 + p { font-size: 13px; line-height: 20px; } +.page .skillCard:hover { border-color: #c7c7d1; } +:global(.dark) .page .skillCard:hover { border-color: hsl(var(--border)); } +.page .updateButton:hover, +.page .moreButton:hover, +.page .publishButton { box-shadow: none; } +.page .updateButton svg, +.page .publishButton svg, +.moreMenu svg { stroke-width: 1.65; } + +.page .filterControl::placeholder { color: #73737d; opacity: 1; } +:global(.dark) .page .filterControl::placeholder { color: #aaaab5; } +.moreMenu .dangerAction, +.moreMenu .dangerAction:focus, +.moreMenu .dangerAction[data-highlighted] { color: #b42318; } +:global(.dark) .moreMenu .dangerAction, +:global(.dark) .moreMenu .dangerAction:focus, +:global(.dark) .moreMenu .dangerAction[data-highlighted] { color: #ffb4ab; } diff --git a/web/src/pages/dashboard/my-skills.tsx b/web/src/pages/dashboard/my-skills.tsx index 34ace90e3..20c8a74e1 100644 --- a/web/src/pages/dashboard/my-skills.tsx +++ b/web/src/pages/dashboard/my-skills.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import { Archive, ArrowUpRight, Copy, Download, Eye, GitCompareArrows, Globe, MoreHorizontal, RotateCcw, Search, Undo2, Upload } from 'lucide-react' +import { Archive, ArrowUpRight, Copy, Download, Eye, GitCompareArrows, Globe, LockKeyhole, MoreHorizontal, Package, Plus, RotateCcw, Search, Undo2, Upload, Users } from 'lucide-react' +import styles from './my-skills.module.css' import { useLocation, useNavigate, useSearch } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' import { useAuth } from '@/features/auth/use-auth' @@ -201,28 +202,28 @@ export function MySkillsPage() { const resolveStatusClassName = (status?: string) => { if (status === 'HIDDEN') { - return 'status-pill status-pill--archived' + return 'status-pill' } if (status === 'ARCHIVED') { - return 'status-pill status-pill--archived' + return 'status-pill' } if (status === 'PENDING_REVIEW') { - return 'status-pill status-pill--review' + return `status-pill ${styles.statusReview}` } if (status === 'PUBLISHED') { - return 'status-pill status-pill--published' + return `status-pill ${styles.statusPublished}` } if (status === 'REJECTED') { - return 'status-pill status-pill--rejected' + return `status-pill ${styles.statusRejected}` } if (status === 'SCANNING') { - return 'status-pill status-pill--review' + return `status-pill ${styles.statusReview}` } if (status === 'SCAN_FAILED') { - return 'status-pill status-pill--rejected' + return `status-pill ${styles.statusRejected}` } if (status === 'UPLOADED') { - return 'status-pill status-pill--review' + return `status-pill ${styles.statusReview}` } return 'status-pill' } @@ -347,13 +348,16 @@ export function MySkillsPage() { } return ( -
+
navigate({ to: '/dashboard/publish' })}> +