Skip to content

Commit f82fa72

Browse files
authored
Merge pull request #650 from GCWing/feat/skills-scene-ui-align-sparo
feat(web-ui): align Skills scene UI with Sparo-style layout
2 parents 2be7806 + 309c907 commit f82fa72

9 files changed

Lines changed: 999 additions & 942 deletions

File tree

src/web-ui/src/app/scenes/skills/SkillsScene.scss

Lines changed: 524 additions & 603 deletions
Large diffs are not rendered by default.

src/web-ui/src/app/scenes/skills/SkillsScene.tsx

Lines changed: 388 additions & 293 deletions
Large diffs are not rendered by default.

src/web-ui/src/app/scenes/skills/components/SkillCard.scss

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@use '../../../../component-library/styles/tokens' as *;
22

33
/* ───────────────────────────────────────────────────
4-
SkillCard — vertical card with glassmorphism effect
4+
SkillCard — compact card for discover tab grid
55
DOM:
66
.skill-card
77
::before (decorative circle blur)
@@ -18,9 +18,10 @@
1818
─────────────────────────────────────────────────── */
1919

2020
.skill-card {
21-
width: 360px;
22-
height: 200px;
23-
border-radius: 15px;
21+
width: 100%;
22+
height: 100%;
23+
min-height: 150px;
24+
border-radius: 12px;
2425
background: var(--element-bg-soft);
2526
display: flex;
2627
flex-direction: column;
@@ -33,31 +34,27 @@
3334
transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1),
3435
box-shadow 0.35s ease;
3536

36-
// Top gradient overlay - shows on hover, covers entire card except footer
37+
// Top gradient overlay
3738
&::before {
3839
content: "";
3940
position: absolute;
4041
top: 0;
4142
left: 0;
4243
right: 0;
43-
bottom: 40px;
44+
bottom: 0;
4445
background: var(--skill-card-gradient);
4546
opacity: 0;
4647
transition: opacity 0.35s ease;
4748
pointer-events: none;
4849
z-index: 0;
4950
}
5051

51-
&--no-actions::before {
52-
bottom: 0;
53-
}
54-
5552
&:hover {
56-
transform: translateY(-4px) scale(1.02);
57-
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2);
53+
transform: translateY(-3px) scale(1.01);
54+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15);
5855

5956
&::before {
60-
opacity: 0.4;
57+
opacity: 0.25;
6158
}
6259
}
6360

@@ -71,8 +68,7 @@
7168
display: flex;
7269
align-items: flex-start;
7370
justify-content: space-between;
74-
padding: $size-gap-3;
75-
padding-bottom: 0;
71+
padding: 12px 12px 8px;
7672
position: relative;
7773
z-index: 1;
7874
}
@@ -81,8 +77,8 @@
8177
display: flex;
8278
align-items: center;
8379
justify-content: center;
84-
width: 32px;
85-
height: 32px;
80+
width: 30px;
81+
height: 30px;
8682
border-radius: 8px;
8783
background: rgba(255, 255, 255, 0.12);
8884
backdrop-filter: blur(8px);
@@ -104,7 +100,7 @@
104100
// ── Body ──
105101
&__body {
106102
flex: 1;
107-
padding: $size-gap-2 $size-gap-3;
103+
padding: 0 12px;
108104
display: flex;
109105
flex-direction: column;
110106
gap: 4px;
@@ -116,12 +112,12 @@
116112
&__title-row {
117113
display: flex;
118114
align-items: center;
119-
gap: $size-gap-2;
115+
gap: 8px;
120116
min-width: 0;
121117
}
122118

123119
&__name {
124-
font-size: 0.92em;
120+
font-size: 0.9em;
125121
font-weight: $font-weight-semibold;
126122
color: var(--color-text-primary);
127123
line-height: $line-height-base;
@@ -165,10 +161,11 @@
165161
display: flex;
166162
align-items: center;
167163
width: 100%;
168-
border-radius: 0 0 15px 15px;
164+
border-radius: 0 0 12px 12px;
169165
overflow: hidden;
170166
position: relative;
171167
z-index: 1;
168+
margin-top: auto;
172169

173170
// Bottom gradient blur background matching card color
174171
&::after {
@@ -179,14 +176,14 @@
179176
right: 0;
180177
bottom: 0;
181178
background: var(--skill-card-gradient);
182-
opacity: 0.5;
179+
opacity: 0.35;
183180
transition: opacity 0.35s ease;
184181
pointer-events: none;
185182
}
186183
}
187184

188185
&:hover &__footer::after {
189-
opacity: 1;
186+
opacity: 0.6;
190187
}
191188

192189
&__actions {
@@ -202,7 +199,7 @@
202199
display: inline-flex;
203200
align-items: center;
204201
justify-content: center;
205-
height: 35px;
202+
height: 32px;
206203
padding: 0;
207204
border: none;
208205
background: rgba(255, 255, 255, 0.08);
@@ -244,14 +241,6 @@
244241
}
245242
}
246243

247-
// ── Responsive ──
248-
@media (max-width: 720px) {
249-
.skill-card {
250-
width: 100%;
251-
min-height: 180px;
252-
}
253-
}
254-
255244
// ── Animations ──
256245
@keyframes skill-card-in {
257246
from {

src/web-ui/src/app/scenes/skills/components/SkillCard.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,9 @@ const SkillCard: React.FC<SkillCardProps> = ({
4141
const Icon = iconKind === 'market' ? Package : Puzzle;
4242
const openDetails = () => onOpenDetails?.();
4343

44-
const hasActions = actions.length > 0;
45-
4644
return (
4745
<div
48-
className={['skill-card', !hasActions && 'skill-card--no-actions'].filter(Boolean).join(' ')}
46+
className="skill-card"
4947
style={{
5048
'--card-index': index,
5149
'--skill-card-gradient': getCardGradient(accentSeed ?? name),
@@ -91,7 +89,7 @@ const SkillCard: React.FC<SkillCardProps> = ({
9189
</div>
9290

9391
{/* Footer: action buttons */}
94-
{hasActions && (
92+
{actions.length > 0 && (
9593
<div className="skill-card__footer">
9694
<div className="skill-card__actions" onClick={(e) => e.stopPropagation()}>
9795
{actions.map((action) => (

src/web-ui/src/app/scenes/skills/hooks/useInstalledSkills.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,17 @@ export function useInstalledSkills({ searchQuery, activeFilter }: UseInstalledSk
168168

169169
const filteredSkills = useMemo(() => {
170170
return skills.filter((skill) => {
171-
const matchesFilter = activeFilter === 'all' || skill.level === activeFilter;
171+
let matchesFilter = true;
172+
if (activeFilter === 'user') {
173+
matchesFilter = skill.level === 'user' && !skill.isBuiltin;
174+
} else if (activeFilter === 'project') {
175+
matchesFilter = skill.level === 'project' && !skill.isBuiltin;
176+
} else if (activeFilter === 'builtin') {
177+
matchesFilter = skill.isBuiltin;
178+
} else if (activeFilter === 'suite') {
179+
matchesFilter = false;
180+
}
181+
172182
const matchesQuery = !normalizedQuery || [
173183
skill.name,
174184
skill.description,
@@ -180,8 +190,10 @@ export function useInstalledSkills({ searchQuery, activeFilter }: UseInstalledSk
180190

181191
const counts = useMemo(() => ({
182192
all: skills.length,
183-
user: skills.filter((skill) => skill.level === 'user').length,
184-
project: skills.filter((skill) => skill.level === 'project').length,
193+
builtin: skills.filter((skill) => skill.isBuiltin).length,
194+
user: skills.filter((skill) => skill.level === 'user' && !skill.isBuiltin).length,
195+
project: skills.filter((skill) => skill.level === 'project' && !skill.isBuiltin).length,
196+
suite: 0,
185197
}), [skills]);
186198

187199
return {

src/web-ui/src/app/scenes/skills/skillsSceneStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { create } from 'zustand';
22

3-
export type InstalledFilter = 'all' | 'user' | 'project';
3+
export type InstalledFilter = 'all' | 'builtin' | 'user' | 'project' | 'suite';
44

55
interface SkillsSceneState {
66
searchDraft: string;

src/web-ui/src/locales/en-US/scenes/skills.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,17 @@
4141
},
4242
"filters": {
4343
"all": "All",
44+
"builtin": "Built-in",
4445
"user": "User",
45-
"project": "Project"
46+
"project": "Project",
47+
"suite": "Suites"
48+
},
49+
"categories": {
50+
"all": "All installed skills, including built-in, user-level, and project-level.",
51+
"builtin": "Core skills shipped with the app. They cannot be deleted.",
52+
"user": "User-level skills installed globally for your account.",
53+
"project": "Project-level skills for the current workspace.",
54+
"suite": "Curated skill bundles that package related skills together (coming soon)."
4655
},
4756
"section": {
4857
"user": {
@@ -64,6 +73,7 @@
6473
"noMatch": "No matching marketplace skills found",
6574
"noSkills": "No marketplace skills available"
6675
},
76+
"resultsInfo": "{{count}} results for \"{{query}}\"",
6777
"item": {
6878
"sourceLabel": "Source: ",
6979
"installs": "Installs: {{count}}",
@@ -98,7 +108,8 @@
98108
"user": "User-level (Global)",
99109
"project": "Project-level (Current Workspace)",
100110
"projectDisabled": " - Need to open workspace first",
101-
"currentWorkspace": "Current workspace: {{path}}"
111+
"currentWorkspace": "Current workspace: {{path}}",
112+
"selectedProjectPath": "Selected project path: {{path}}"
102113
},
103114
"path": {
104115
"label": "Skill Folder Path",
@@ -123,6 +134,9 @@
123134
"item": {
124135
"user": "User",
125136
"project": "Project",
137+
"builtin": "Built-in",
138+
"userInstalled": "Installed",
139+
"detail": "Details",
126140
"deleteTooltip": "Delete",
127141
"pathLabel": "Path:",
128142
"openPathInExplorer": "Open this folder in file explorer",

src/web-ui/src/locales/zh-CN/scenes/skills.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,17 @@
4141
},
4242
"filters": {
4343
"all": "全部",
44+
"builtin": "内置",
4445
"user": "用户级",
45-
"project": "项目级"
46+
"project": "项目级",
47+
"suite": "套件"
48+
},
49+
"categories": {
50+
"all": "查看所有已安装的技能,包含内置、用户和项目级。",
51+
"builtin": "系统出厂自带的核心技能,不可删除。",
52+
"user": "全局安装到当前账户的用户级技能。",
53+
"project": "当前工作区下的项目级技能。",
54+
"suite": "精选技能套件将多个相关技能打包提供(敬请期待)。"
4655
},
4756
"section": {
4857
"user": {
@@ -64,6 +73,7 @@
6473
"noMatch": "没有找到匹配的市场技能",
6574
"noSkills": "暂时没有可展示的市场技能"
6675
},
76+
"resultsInfo": "关键词「{{query}}」共 {{count}} 条相关结果",
6777
"item": {
6878
"sourceLabel": "来源: ",
6979
"installs": "安装量: {{count}}",
@@ -98,7 +108,8 @@
98108
"user": "用户级(全局)",
99109
"project": "项目级(当前工作空间)",
100110
"projectDisabled": " - 需要先打开工作区",
101-
"currentWorkspace": "当前工作区: {{path}}"
111+
"currentWorkspace": "当前工作区: {{path}}",
112+
"selectedProjectPath": "所选项目路径: {{path}}"
102113
},
103114
"path": {
104115
"label": "技能文件夹路径",
@@ -123,6 +134,9 @@
123134
"item": {
124135
"user": "用户级",
125136
"project": "项目级",
137+
"builtin": "内置",
138+
"userInstalled": "已安装",
139+
"detail": "详情",
126140
"deleteTooltip": "删除",
127141
"pathLabel": "路径:",
128142
"openPathInExplorer": "在资源管理器中打开此文件夹",

src/web-ui/src/locales/zh-TW/scenes/skills.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,17 @@
4141
},
4242
"filters": {
4343
"all": "全部",
44+
"builtin": "內建",
4445
"user": "用戶級",
45-
"project": "項目級"
46+
"project": "項目級",
47+
"suite": "套件"
48+
},
49+
"categories": {
50+
"all": "查看所有已安裝的技能,包含內建、用戶與項目級。",
51+
"builtin": "系統出廠內建的核心技能,不可刪除。",
52+
"user": "為目前帳戶全域安裝的用戶級技能。",
53+
"project": "目前工作區底下的項目級技能。",
54+
"suite": "精選技能套件將多個相關技能打包提供(敬請期待)。"
4655
},
4756
"section": {
4857
"user": {
@@ -64,6 +73,7 @@
6473
"noMatch": "沒有找到匹配的市場技能",
6574
"noSkills": "暫時沒有可展示的市場技能"
6675
},
76+
"resultsInfo": "關鍵詞「{{query}}」共 {{count}} 筆相關結果",
6777
"item": {
6878
"sourceLabel": "來源: ",
6979
"installs": "安裝量: {{count}}",
@@ -98,7 +108,8 @@
98108
"user": "用戶級(全局)",
99109
"project": "項目級(當前工作空間)",
100110
"projectDisabled": " - 需要先打開工作區",
101-
"currentWorkspace": "當前工作區: {{path}}"
111+
"currentWorkspace": "當前工作區: {{path}}",
112+
"selectedProjectPath": "所選項目路徑: {{path}}"
102113
},
103114
"path": {
104115
"label": "技能文件夾路徑",
@@ -123,6 +134,9 @@
123134
"item": {
124135
"user": "用戶級",
125136
"project": "項目級",
137+
"builtin": "內建",
138+
"userInstalled": "已安裝",
139+
"detail": "詳情",
126140
"deleteTooltip": "刪除",
127141
"pathLabel": "路徑:",
128142
"openPathInExplorer": "在資源管理器中打開此文件夾",

0 commit comments

Comments
 (0)