diff --git a/stash-web/package-lock.json b/stash-web/package-lock.json index 7a7d8d7..a526936 100644 --- a/stash-web/package-lock.json +++ b/stash-web/package-lock.json @@ -8,6 +8,7 @@ "name": "stash-web", "version": "0.0.0", "dependencies": { + "@stash/ui": "file:../../stash-ui", "react": "^19.3.1", "react-dom": "^19.3.1" }, @@ -21,6 +22,25 @@ "vite": "^8.3.4" } }, + "../../stash-ui": { + "name": "@stash/ui", + "version": "0.1.0", + "devDependencies": { + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "^6.1.2", + "oxlint": "^1.88.0", + "playwright-core": "^1.64.1", + "react": "^19.3.1", + "react-dom": "^19.3.1", + "typescript": "~6.0.3", + "vite": "^8.3.4" + }, + "peerDependencies": { + "react": ">=19", + "react-dom": ">=19" + } + }, "node_modules/@oxc-project/types": { "version": "0.154.0", "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.154.0.tgz", @@ -616,6 +636,10 @@ "dev": true, "license": "MIT" }, + "node_modules/@stash/ui": { + "resolved": "../../stash-ui", + "link": true + }, "node_modules/@types/node": { "version": "24.19.2", "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.19.2.tgz", diff --git a/stash-web/package.json b/stash-web/package.json index 37c7667..df086d3 100644 --- a/stash-web/package.json +++ b/stash-web/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@stash/ui": "file:../../stash-ui", "react": "^19.3.1", "react-dom": "^19.3.1" }, diff --git a/stash-web/src/App.css b/stash-web/src/App.css index ad0c908..7734ff0 100644 --- a/stash-web/src/App.css +++ b/stash-web/src/App.css @@ -1,1652 +1,408 @@ -/* ============================================================ - 页面背景:复刻 Alright 的 .site-background - ============================================================ */ - -.site-background { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 65vh; - background-image: url('./assets/site-bg.jpg'); - background-size: cover; - background-position: top center; - background-repeat: no-repeat; - pointer-events: none; - z-index: 0; -} - -/* 底部 80% 高度做一层「底色 → 透明」的渐变,让图片自然融进页面背景 */ -.site-background::after { - content: ''; - position: absolute; - right: 0; - bottom: 0; - left: 0; - height: 80%; - background-image: linear-gradient(0deg, var(--bg-color) 0%, rgba(0, 0, 0, 0) 100%); -} - -/* ============================================================ - 全局容器 - ============================================================ */ +/* 页面级样式:只保留「组件库没有」的部分(背景、外壳、封面比例、正文版式)。 + 颜色 / 间距 / 字号全部取自 @stash/ui 的令牌,组件内部样式不要再在这里覆盖。 */ .app { - height: 100%; - display: flex; - flex-direction: column; + background-color: var(--su-bg-page); } -/* 版心:顶栏与内容区共用,保证左右边界一?*/ -.layout-container { - width: 100%; - max-width: var(--layout-max-width); - margin: 0 auto; - padding: 0 20px; -} - -.card-box { - background-color: var(--card-bg); - border: 1px solid var(--border-color); - border-radius: var(--radius-main); - box-shadow: var(--shadow-card); -} - -/* 仅屏幕阅读器可见 */ -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -/* ============================================================ - 顶部栏:品牌 + 运行信息 + 操作图标 - ============================================================ */ - -.site-header { +/* 顶部背景图:固定 65vh,底部渐隐到页面底色 */ +.site-background { position: fixed; - top: 0; - left: 0; - width: 100%; - height: var(--top-nav-height); - background-color: var(--card-bg); - border-bottom: 1px solid var(--border-color); - z-index: 200; + inset: 0 0 auto; + z-index: -1; + height: 65vh; + background: url('./assets/site-bg.jpg') center top / cover no-repeat; + opacity: 0.16; + mask-image: linear-gradient(to bottom, #000 35%, transparent 100%); } .site-header-inner { - height: 100%; display: flex; align-items: center; - justify-content: space-between; - gap: 16px; + gap: var(--su-space-6); + height: 64px; } .site-brand { display: flex; align-items: center; - gap: 10px; - flex-shrink: 0; + gap: var(--su-space-2); } .site-brand-mark { + display: grid; + place-items: center; width: 32px; height: 32px; - border-radius: 50%; - background-color: var(--text-main); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - font-weight: 700; + border-radius: var(--su-radius-sm); + background-color: var(--su-color-primary); + color: var(--su-white); + font-size: var(--su-font-size-md); + font-weight: var(--su-font-weight-strong); } .site-brand-name { - font-size: 18px; - font-weight: 800; + font-size: var(--su-font-size-lg); + line-height: var(--su-line-height-lg); letter-spacing: 0.04em; - color: var(--text-main); } -/* 中间运行信息 */ - .header-info { - display: flex; - align-items: center; - gap: 20px; - min-width: 0; - font-size: 14px; - color: var(--text-sub); + flex: 1; + justify-content: center; } -.header-info-item { - display: flex; - align-items: center; - gap: 6px; - white-space: nowrap; -} - -.header-info-tag { - padding: 2px 8px; - border-radius: 4px; - background-color: var(--primary-soft); - color: var(--primary-color); - font-size: 12px; -} - -.header-info-tag.is-green { - background-color: rgba(74, 189, 134, 0.12); - color: var(--green); -} - -.header-info-text { - overflow: hidden; - text-overflow: ellipsis; -} - -/* 右侧图标 */ - .header-actions { + margin-left: auto; +} + +.content { display: flex; - align-items: center; - gap: 16px; - flex-shrink: 0; - color: var(--text-sub); + flex-direction: column; + gap: var(--su-space-4); + padding-top: var(--su-space-2); } -.header-icon-button { - width: 34px; - height: 34px; - display: flex; - align-items: center; - justify-content: center; - padding: 6px; - border-radius: 50%; - cursor: pointer; - transition: - background-color 0.2s, - color 0.2s; -} - -.header-icon-button:hover { - background-color: var(--border-strong); - color: var(--text-main); -} - -.header-icon-button .iconfont { - font-size: 19px; -} - -/* ============================================================ - 搜索框:固定悬浮在顶栏下方居? ============================================================ */ - -/* 固定悬浮、相对整页居中,与设计稿一?*/ +/* 搜索框吸在页头下方,滚动时内容从它下面穿过 */ .search-row { - position: fixed; - top: 76px; - left: 50%; - transform: translateX(-50%); - width: min(480px, calc(100vw - 48px)); - height: 44px; - z-index: 100; -} - -.search-bar { - display: flex; - align-items: center; - gap: 10px; - height: 100%; - padding: 0 12px; -} - -.search-icon { - flex-shrink: 0; - font-size: 16px; - color: var(--text-sub); -} - -.search-input { - flex: 1; - min-width: 0; - height: 36px; - border: none; - outline: none; - background: transparent; - font-size: 14px; - color: var(--text-main); -} - -.search-input::placeholder { - color: var(--text-muted); -} - -.search-clear { - flex-shrink: 0; - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - color: var(--text-muted); - cursor: pointer; - transition: - background-color 0.2s, - color 0.2s; -} - -.search-clear:hover { - background-color: var(--border-strong); - color: var(--text-main); -} - -.search-clear .iconfont { - font-size: 14px; -} - -/* ============================================================ - 双列布局:左侧栏 + 右侧内容 - ============================================================ */ - -.layout { - position: relative; - z-index: 1; - width: 100%; - max-width: var(--layout-max-width); - margin: var(--top-nav-height) auto 0; - height: calc(100vh - var(--top-nav-height)); - padding: 36px 20px 0; - display: grid; - grid-template-columns: var(--sidebar-width) minmax(0, 1fr); - gap: 20px; - align-items: stretch; + position: sticky; + top: 64px; + z-index: var(--su-z-header); + padding: var(--su-space-2) 0; + background-color: color-mix(in srgb, var(--su-bg-page) 88%, transparent); + backdrop-filter: blur(8px); } .sidebar { + position: sticky; + top: 84px; display: flex; flex-direction: column; - gap: 16px; - min-height: 0; - /* 与右侧首个分区卡片对?*/ - margin-top: var(--content-top-offset); + gap: var(--su-space-3); + padding: var(--su-space-4) var(--su-space-4) var(--su-space-4) 0; } -/* ---------- 用户资料?---------- */ +.profile-card, +.service-panel, +.nav-card { + flex-shrink: 0; +} .profile-card { display: flex; flex-direction: column; align-items: center; - gap: 6px; - padding: 18px 14px 14px; + gap: var(--su-space-2); text-align: center; - flex-shrink: 0; -} - -.profile-avatar { - position: relative; - width: 64px; - height: 64px; - margin-bottom: 4px; - border-radius: 50%; - display: grid; - place-items: center; - background: linear-gradient(135deg, #dfe4ff, #c9d0ff); - color: var(--primary-color); -} - -.profile-avatar .iconfont { - font-size: 34px; -} - -.profile-badge { - position: absolute; - right: -2px; - bottom: -2px; - width: 20px; - height: 20px; - border-radius: 50%; - border: 2px solid var(--card-bg); - background-color: var(--orange); - color: #fff; - display: grid; - place-items: center; - font-size: 10px; - font-weight: 700; -} - -.profile-name { - font-size: 14px; - font-weight: 600; - color: var(--text-main); - line-height: 1.2; -} - -.profile-role { - padding: 1px 8px; - border: 1px solid var(--border-color); - border-radius: 999px; - font-size: 12px; - color: var(--text-sub); -} - -.profile-bio { - margin: 2px 0 0; - font-size: 12px; - line-height: 1.55; - color: var(--text-sub); -} - -.profile-location { - margin-top: 2px; - font-size: 12px; - color: var(--text-muted); -} - -/* ---------- 本地服务面板 ---------- */ - -.service-panel { - padding: 12px 14px; - flex-shrink: 0; -} - -.service-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; -} - -.service-title { - font-size: 14px; - font-weight: 600; - color: var(--text-main); -} - -.service-pill { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 2px 8px; - border-radius: 999px; - background-color: var(--border-strong); - font-size: 12px; - color: var(--text-sub); -} - -.service-pill.is-on { - background-color: rgba(34, 197, 94, 0.12); - color: var(--green); } .service-stats { - display: grid; - gap: 6px; - margin-top: 10px; -} - -.service-stats > div { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - font-size: 12px; -} - -.service-stats dt { - flex-shrink: 0; - color: var(--text-sub); -} - -.service-stats dd { - margin: 0; - overflow: hidden; - font-family: var(--font-mono); - font-size: 12px; - font-weight: 600; - color: var(--text-main); - text-overflow: ellipsis; - white-space: nowrap; -} - -.service-actions { - display: flex; - gap: 8px; - margin-top: 12px; -} - -.service-action { - flex: 1; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 5px; - padding: 6px 8px; - border: 1px solid var(--border-color); - border-radius: 8px; - background-color: var(--card-bg); - color: var(--text-sub); - font-size: 12px; - white-space: nowrap; - cursor: pointer; - transition: - border-color 0.2s, - color 0.2s, - background-color 0.2s; -} - -.service-action:not(:disabled):hover { - border-color: var(--primary-color); - background-color: var(--primary-soft); - color: var(--primary-color); -} - -.service-action:disabled { - opacity: 0.6; - cursor: default; -} - -.service-action .iconfont { - font-size: 14px; -} - -.status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background-color: var(--border-strong); - flex-shrink: 0; - transition: - background-color 0.2s, - box-shadow 0.2s; -} - -.status-dot.is-on { - background-color: var(--green); - box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.16); -} - -/* ---------- 分类导航 ---------- */ - -.nav-card { - padding: 8px; - flex-shrink: 0; -} - -.nav-group { - list-style: none; -} - -.nav-item { - margin-bottom: 4px; -} - -.nav-item:last-child { - margin-bottom: 0; -} - -.nav-link { - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 9px 12px; - border-radius: 8px; - font-size: 14px; - font-weight: 500; - color: var(--text-sub); - cursor: pointer; - text-align: left; - transition: - background-color 0.2s, - color 0.2s; -} - -.nav-link:hover, -.nav-link.active { - background-color: var(--primary-soft); - color: var(--primary-color); -} - -/* 折叠箭头:复刻主题的 6×6 边框 V 形,靠右对齐 */ -.nav-link.has-children::after { - content: ''; - flex-shrink: 0; - width: 6px; - height: 6px; - border-left: 1px solid currentColor; - border-bottom: 1px solid currentColor; - opacity: 0.4; - transform: rotate(315deg); - transition: - transform 0.3s, - opacity 0.3s; -} - -.nav-link.has-children:hover::after { - opacity: 0.7; -} - -.nav-item.open > .nav-link.has-children::after { - opacity: 0.7; - transform: rotate(135deg); -} - -/* 子菜单展开:用 grid-template-rows 0fr → 1fr 做高度动画, - 高度精确贴合内容,全程 overflow: hidden,不会出现滚动条 */ -.submenu-wrap { - display: grid; - grid-template-rows: 0fr; - transition: grid-template-rows 0.28s ease; -} - -.submenu-wrap.is-open { - grid-template-rows: 1fr; -} - -.submenu { - list-style: none; - min-height: 0; - padding-left: 12px; - overflow: hidden; -} - -.submenu .nav-link { - padding: 7px 12px; - font-size: 12px; -} - -.submenu .nav-link > span:first-child { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* ============================================================ - 右侧内容? ============================================================ */ - -.content { - position: relative; display: flex; flex-direction: column; - min-height: 0; - /* 让出悬浮的搜索框;侧栏与遮罩层都按同一个变量对?*/ - padding-top: var(--content-top-offset); + gap: var(--su-space-2); + margin: var(--su-space-3) 0; } -/* 滚动后才淡入的顶部渐隐层? 卡片滚到容器顶边时被硬切会很难看,用一层背景色渐变把它柔化?*/ -.content-veil { - position: absolute; - top: var(--content-top-offset); - right: 0; - left: 0; - height: 32px; - pointer-events: none; - opacity: 0; - background: linear-gradient(to bottom, var(--bg-color) 0%, transparent 100%); - transition: opacity 0.2s ease; - z-index: 5; +.service-host { + max-width: 140px; } -.content-veil.is-visible { - opacity: 1; -} - -.content-scroll { - flex: 1; - min-height: 0; - overflow-y: auto; - padding-right: 4px; - padding-bottom: 40px; - scroll-behavior: smooth; - -ms-overflow-style: none; - scrollbar-width: none; -} - -.content-scroll::-webkit-scrollbar { - display: none; - width: 0; -} - -/* ---------- 首页:分区卡?---------- */ - .home-view { display: flex; flex-direction: column; - gap: 20px; -} - -.section-card { - background-color: var(--card-bg); - border: 1px solid var(--border-color); - border-radius: 12px; - padding: 20px; - box-shadow: var(--shadow-card); + gap: var(--su-space-4); } .section-header { display: flex; align-items: center; justify-content: space-between; - margin-bottom: 16px; -} - -.section-title-wrapper { - display: flex; - align-items: center; + margin-bottom: var(--su-space-4); } .section-bar { width: 4px; height: 16px; - margin-right: 8px; - border-radius: 2px; - background-color: var(--primary-color); + border-radius: var(--su-radius-pill); } -/* 标题容器设为 relative,角标才能贴在标题右上角 */ -.section-title-box { - position: relative; - display: inline-flex; - align-items: center; -} - -.section-title { - font-size: 16px; - font-weight: 600; - line-height: 1.2; - color: var(--text-main); -} - -.count-badge { - position: absolute; - top: -8px; - right: -18px; - display: flex; - align-items: center; - justify-content: center; - height: 16px; - min-width: 16px; - padding: 0 4px; - border-radius: 8px; - background-color: var(--danger); - box-shadow: 0 1px 3px rgba(255, 77, 79, 0.3); - color: #fff; - font-size: 12px; - font-weight: 700; - line-height: 1; -} - -.section-more { - display: inline-flex; - align-items: center; - padding: 4px 6px; - border-radius: 4px; - color: var(--text-muted); - font-size: 16px; - line-height: 1; - cursor: pointer; - transition: - background-color 0.2s, - color 0.2s; -} - -.section-more:hover { - background-color: var(--border-color); - color: var(--text-sub); -} - -.section-empty { - padding: 8px 0; - font-size: 14px; - color: var(--text-sub); -} - -/* ---------- 应用网格 ---------- */ - -.app-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 12px; -} - -.app-grid--detail { - gap: 16px; -} +/* ---------- 应用条目 ---------- */ .app-card { display: flex; align-items: center; - gap: 15px; - padding: 6px 0; - /* 和主题的作者条目一样:没有底色、描边、阴影,就是一行内容 */ - background: none; - border: 0; - color: inherit; + gap: var(--su-space-3); + padding: var(--su-space-2); + border-radius: var(--su-radius-sm); + transition: background-color var(--su-duration-fast) var(--su-ease-in-out); } -.app-card:hover .app-card-name { - color: var(--primary-color); -} - -/* 48px 圆形头像 + 右下角认证角标,对应主题 .flex-avatar / .avatar-badge.badge_5 */ -.app-card-avatar { - position: relative; - width: 48px; - height: 48px; - flex-shrink: 0; - border-radius: 100%; - display: flex; - align-items: center; - justify-content: center; - background-color: var(--primary-soft); - color: var(--primary-color); - font-size: 18px; - font-weight: 700; -} - -/* 有封面图时直接铺满圆形头像 */ -.app-card-avatar img { - width: 100%; - height: 100%; - border-radius: inherit; - object-fit: cover; - display: block; -} - -.app-card-verify { - position: absolute; - right: 0; - bottom: 0; - width: 10px; - height: 10px; - background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNiIgaGVpZ2h0PSIzNiIgZmlsbD0ibm9uZSI+PGNpcmNsZSBjeD0iMTgiIGN5PSIxOCIgcj0iMTgiIGZpbGw9InVybCgjYSkiLz48cGF0aCBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yOC4yNSAxMS44NDRhMi4yMzIgMi4yMzIgMCAwIDEgMCAzLjE1NkwxNy41ODggMjUuNjYyYTIuMzggMi4zOCAwIDAgMS0zLjM2NiAwbC01LjY0NC01LjY0NGEyLjIzMiAyLjIzMiAwIDAgMSAzLjE1Ni0zLjE1NWw0LjE3IDQuMTcgOS4xOS05LjE4OWEyLjIzMiAyLjIzMiAwIDAgMSAzLjE1NiAwWiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMzEuNzE0IiB4Mj0iLTMuNzMxIiB5MT0iLjg1NyIgeTI9IjExLjgzOSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiNGRkI3NEEiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRDkwMEYiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48L3N2Zz4="); - background-position: center; - background-repeat: no-repeat; - background-size: contain; +.app-card:hover { + background-color: var(--su-bg-subtle); } .app-card-info { display: flex; flex-direction: column; - justify-content: center; - min-width: 0; - flex: 1 1 auto; - overflow: hidden; -} - -.app-card-title { - display: flex; - align-items: center; - gap: 6px; + gap: 2px; min-width: 0; } -.app-card-name { - font-size: 16px; - font-weight: 500; - line-height: 24px; - color: var(--text-main); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} +/* ---------- 封面卡(海报 / 图文) ---------- */ -/* 名字后的 HOT 徽标:主题里是 24×16 的图形,不是文字 */ -.app-card-badge { - position: relative; - top: 2px; - display: inline-block; - flex-shrink: 0; - width: 24px; - height: 16px; - background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MCIgaGVpZ2h0PSIyMSIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0idXJsKCNhKSIgZD0iTTAgNGE0IDQgMCAwIDEgNC00aDQyYTQgNCAwIDAgMSA0IDR2MTNhNCA0IDAgMCAxLTQgNEg0YTQgNCAwIDAgMS00LTRWNFoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNi4yODYgOS4zNDVoOC4wM1Y0LjE3NmgyLjI4NnYxMi42NDhoLTIuMjg3di01LjE2OUg2LjI4NnY1LjE2OUg0VjQuMTc2aDIuMjg2djUuMTY5Wk0zMi41MTkgMTEuNzA4YzAgLjc3LS4xMzIgMS40NzktLjM5NiAyLjEyNmE1LjAzOSA1LjAzOSAwIDAgMS0xLjEgMS42NzUgNS4wMiA1LjAyIDAgMCAxLTEuNjcgMS4xMDNBNS41MjYgNS41MjYgMCAwIDEgMjcuMjYgMTdoLTMuMDk1Yy0uNzU3IDAtMS40NTctLjEzLTIuMTAyLS4zODhhNS4wMiA1LjAyIDAgMCAxLTEuNjcxLTEuMTAyIDUuMTUyIDUuMTUyIDAgMCAxLTEuMTA4LTEuNjc2IDUuNTc2IDUuNTc2IDAgMCAxLS4zOTYtMi4xMjZWOS4yOTJjMC0uNzY1LjEzMi0xLjQ3LjM5Ni0yLjExN2E1LjAzIDUuMDMgMCAwIDEgMS4xMDgtMS42NzYgNS4wMjIgNS4wMjIgMCAwIDEgMS42Ny0xLjEwMkE1LjQ4NCA1LjQ4NCAwIDAgMSAyNC4xNjYgNGgzLjA5NWMuNzU2IDAgMS40NTQuMTMyIDIuMDkzLjM5N2E1LjAyMiA1LjAyMiAwIDAgMSAxLjY3IDEuMTAyIDQuOTIyIDQuOTIyIDAgMCAxIDEuMSAxLjY3NmMuMjY0LjY0Ny4zOTYgMS4zNTIuMzk2IDIuMTE3djIuNDE2Wm0tMi4yODctMi40MTZjMC0uNDUzLS4wNzMtLjg2Mi0uMjItMS4yMjZhMi41ODkgMi41ODkgMCAwIDAtLjYwNi0uOTQ0IDIuNTggMi41OCAwIDAgMC0uOTQxLS42MDggMy4xODYgMy4xODYgMCAwIDAtMS4yMDUtLjIyaC0zLjA5NWMtLjQ0NiAwLS44NTMuMDczLTEuMjIzLjIyYTIuNjgxIDIuNjgxIDAgMCAwLS45NC42MDhjLS4yNjUuMjU5LS40Ny41NzMtLjYxNi45NDQtLjE0MS4zNjQtLjIxMS43NzMtLjIxMSAxLjIyNnYyLjQxNmMwIC40NTMuMDcuODY1LjIxIDEuMjM1LjE0Ny4zNjUuMzUyLjY4LjYxNi45NDQuMjY0LjI1OC41NzguNDYxLjk0MS42MDhhMy40IDMuNCAwIDAgMCAxLjIyMy4yMTJoMy4wNzhjLjQ0NSAwIC44NS0uMDcgMS4yMTMtLjIxMi4zNy0uMTQ3LjY4Ni0uMzUuOTUtLjYwOC4yNjQtLjI2NS40NjYtLjU4LjYwNy0uOTQ0LjE0Ni0uMzcuMjItLjc4Mi4yMi0xLjIzNVY5LjI5MlpNNDYgNi40N2gtNS4wMzl2MTAuMzU0aC0yLjI4NlY2LjQ2OWgtNS4wNDhWNC4xNzZINDZWNi40N1oiLz48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSI0NC4wNDgiIHgyPSI5LjEwNyIgeTE9Ii41IiB5Mj0iMjYuMjc2IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iI0ZGN0I1MSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0ZGMEUwRSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjwvc3ZnPg=="); - background-position: center; - background-repeat: no-repeat; - background-size: contain; -} - -.app-card-desc { - font-size: 12px; - line-height: 16px; - color: var(--text-muted); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -/* ---------- 海报网格 ---------- */ - -.poster-grid { - display: grid; - grid-template-columns: repeat(6, minmax(0, 1fr)); - gap: 12px; -} - -.poster-grid--detail { - gap: 14px; -} - -.poster-card { +.cover-card { position: relative; display: block; width: 100%; - margin: 0; - padding: 0; - border: none; + overflow: hidden; + border-radius: var(--su-radius-sm); + background-color: var(--su-bg-light); + text-align: left; +} + +.cover { + display: block; + width: 100%; + object-fit: cover; + transition: transform var(--su-duration-base) var(--su-ease-in-out); +} + +.cover--poster { aspect-ratio: 2 / 3; - border-radius: 8px; - overflow: hidden; - background-color: var(--border-strong); - text-align: left; - font: inherit; - cursor: pointer; - transition: transform 0.2s ease; } -.poster-card:hover { - transform: translateY(-2px); -} - -.poster-cover { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} - -.poster-caption { - position: absolute; - right: 0; - bottom: 0; - left: 0; - padding: 20px 8px 8px; - background: linear-gradient( - to top, - rgba(0, 0, 0, 0.85) 0%, - rgba(0, 0, 0, 0.4) 60%, - transparent 100% - ); - color: #fff; -} - -.poster-title { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - overflow: hidden; - font-size: 12px; - font-weight: 500; - line-height: 1.3; -} - -/* ---------- 首页图文网格 ---------- */ - -.article-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 12px; -} - -.article-card { - position: relative; - display: block; - width: 100%; - margin: 0; - padding: 0; - border: none; +.cover--article { aspect-ratio: 4 / 3; - border-radius: 6px; - overflow: hidden; - background-color: var(--border-strong); - text-align: left; - font: inherit; - cursor: pointer; - transition: transform 0.2s ease; } -.article-card:hover { - transform: translateY(-2px); +.cover-card:hover .cover { + transform: scale(1.04); } -.article-cover { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} - -.article-caption { +.cover-caption { position: absolute; - right: 0; - bottom: 0; - left: 0; - padding: 16px 6px 6px; - background: linear-gradient( - to top, - rgba(0, 0, 0, 0.8) 0%, - rgba(0, 0, 0, 0.3) 70%, - transparent 100% - ); - color: #fff; -} - -.article-title { + inset: auto 0 0; display: -webkit-box; + overflow: hidden; + padding: 24px var(--su-space-2) var(--su-space-2); + background: linear-gradient(transparent, rgb(0 0 0 / 62%)); + color: var(--su-white); + font-size: var(--su-font-size-sm); + line-height: var(--su-line-height-sm); -webkit-box-orient: vertical; -webkit-line-clamp: 2; - overflow: hidden; - font-size: 12px; - line-height: 1.3; -} - -/* ---------- 明细?---------- */ - -.detail-view { - display: flex; - flex-direction: column; - gap: 20px; -} - -.detail-nav { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 20px; -} - -.detail-back { - display: inline-flex; - align-items: center; - gap: 4px; - color: var(--primary-color); - font-size: 14px; - font-weight: 500; - cursor: pointer; -} - -.detail-back:hover { - text-decoration: underline; -} - -.detail-breadcrumb { - font-size: 12px; - color: var(--text-muted); -} - -.detail-hero { - display: flex; - flex-direction: column; - align-items: center; - padding: 12px 0 4px; - text-align: center; -} - -.detail-hero-icon { - width: 44px; - height: 44px; - margin-bottom: 8px; - border-radius: 50%; - background-color: var(--primary-soft); - color: var(--primary-color); - display: flex; - align-items: center; - justify-content: center; - font-size: 24px; -} - -.detail-hero-title { - font-size: 24px; - font-weight: 700; - color: var(--text-main); -} - -.detail-hero-count { - margin-top: 4px; - font-size: 14px; - color: var(--text-muted); -} - -/* 分组块:小标?+ 该组网格 */ -.group-block { - margin-bottom: 12px; -} - -.group-title { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 12px; - font-size: 16px; - font-weight: 600; - color: var(--text-main); -} - -.group-title::before { - content: ''; - width: 3px; - height: 14px; - border-radius: 2px; - background-color: var(--primary-color); -} - -/* 明细页图文白?*/ -.ces-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 16px; } .ces-card { - display: flex; - flex-direction: column; - width: 100%; - padding: 0; overflow: hidden; - border: 1px solid var(--border-color); - border-radius: 10px; - background-color: var(--card-bg); - box-shadow: var(--shadow-card); - text-align: left; - font: inherit; - cursor: pointer; - transition: - transform 0.2s, - box-shadow 0.2s; -} - -.ces-card:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-hover); -} - -.ces-cover { - width: 100%; - aspect-ratio: 16 / 10; - object-fit: cover; - display: block; - background-color: var(--border-color); } .ces-body { display: flex; flex-direction: column; - gap: 4px; - padding: 12px; + padding: var(--su-space-3); + text-align: left; } -.ces-title { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - overflow: hidden; - font-size: 14px; - font-weight: 500; - line-height: 1.4; - color: var(--text-main); -} - -.ces-desc { - display: block; - margin-top: 2px; - font-size: 12px; - color: var(--text-muted); -} - -/* ============================================================ - 文章详情页(点击图文卡片进入? ============================================================ */ - -.article-head { - display: flex; - flex-direction: column; - align-items: center; - gap: 12px; - padding: 4px 0; - text-align: center; -} - -.article-heading { - max-width: 760px; - font-size: 24px; - font-weight: 700; - line-height: 1.4; - color: var(--text-main); -} - -.article-meta { - display: flex; - align-items: center; - font-size: 12px; - color: var(--text-muted); -} - -.article-meta-item { - display: inline-flex; - align-items: center; -} - -.article-meta-dot { - width: 3px; - height: 3px; - margin: 0 8px; - border-radius: 50%; - background-color: var(--border-strong); -} - -.article-body { - padding: 32px 40px 40px; -} - -.article-body-text { - margin-bottom: 16px; - font-size: 14px; - line-height: 1.9; - color: var(--text-sub); -} - -.article-body-heading { - margin: 28px 0 16px; - font-size: 16px; - font-weight: 700; - color: var(--text-main); -} - -.article-body-image { - display: block; +.ces-body--button { width: 100%; - margin: 20px 0; - border-radius: 4px; - background-color: var(--border-color); + cursor: pointer; } -/* ============================================================ - 条目详情页(点击应用卡片进入? ============================================================ */ +.ces-body--button:hover { + background-color: var(--su-bg-subtle); +} + +/* ---------- 详情 ---------- */ -/* 明细 / 条目详情共用同一套打开动画,和点击 ••?的表现保持一?*/ .detail-view { - animation: view-in 0.22s ease both; -} - -@keyframes view-in { - from { - opacity: 0; - transform: translateY(6px); - } - to { - opacity: 1; - transform: none; - } -} - -.item-card { display: flex; flex-direction: column; - align-items: center; - padding: 24px 28px; + gap: var(--su-space-4); } -.item-title { +.detail-hero { display: flex; align-items: center; - justify-content: center; - gap: 6px; + gap: var(--su-space-3); +} + +.detail-hero-icon { font-size: 24px; - font-weight: 600; - color: var(--text-main); + line-height: 1; } .item-edit { - font-size: 14px; - font-weight: 400; - color: var(--text-muted); - cursor: default; + margin-left: var(--su-space-2); + vertical-align: middle; } -/* 主图:应用是方形头像,电影是竖版海报 */ .item-cover { - display: flex; - align-items: center; - justify-content: center; - margin: 18px 0 10px; + display: grid; + place-items: center; + width: 120px; + margin: var(--su-space-4) 0; overflow: hidden; - background-color: var(--primary-soft); - color: var(--primary-color); + border-radius: var(--su-radius-md); + background-color: var(--su-bg-light); } .item-cover--square { - width: 96px; - height: 96px; - border-radius: 12px; + aspect-ratio: 1; } .item-cover--portrait { - width: 179px; + width: 160px; aspect-ratio: 2 / 3; - border-radius: 8px; } .item-cover img { width: 100%; height: 100%; object-fit: cover; - display: block; } .item-cover-letter { - font-size: 38px; - font-weight: 700; -} - -.item-tags { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 8px; -} - -.item-tag { - padding: 2px 10px; - border-radius: 999px; - background-color: var(--primary-soft); - color: var(--primary-color); - font-size: 12px; + font-size: 32px; + color: var(--su-text-sub); } .item-quote { - display: flex; - gap: 6px; - width: 100%; - margin-top: 18px; - text-align: left; -} - -.item-quote-mark { - flex-shrink: 0; - font-size: 32px; - font-weight: 700; - line-height: 1; - color: rgba(255, 62, 62, 0.35); -} - -.item-quote-text { - font-size: 14px; - line-height: 1.7; - color: var(--text-sub); -} - -.item-desc { - width: 100%; - margin-top: 10px; - font-size: 14px; - line-height: 1.7; - color: var(--text-sub); - text-align: left; + padding: var(--su-space-3) var(--su-space-4); + border-left: 2px solid var(--su-color-primary); + border-radius: 0 var(--su-radius-sm) var(--su-radius-sm) 0; + background-color: var(--su-bg-subtle); } .item-actions { - display: flex; - align-items: center; - gap: 8px; - width: 100%; - margin-top: 18px; -} - -.item-stat { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - min-width: 56px; - height: 36px; - padding: 0 12px; - border-radius: 8px; - background-color: var(--bg-color); - color: var(--text-sub); - font-size: 12px; -} - -.item-stat .iconfont { - font-size: 16px; -} - -.item-stat em { - font-style: normal; - font-weight: 600; - color: var(--text-main); -} - -.item-stat--icon { - cursor: pointer; -} - -.item-stat--icon:hover { - background-color: var(--border-color); -} - -.item-jump { - flex: 1; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - height: 36px; - border-radius: 8px; - background-color: var(--primary-color); - color: #fff; - font-size: 14px; - transition: background-color 0.2s; -} - -.item-jump:hover { - background-color: #2c47d8; -} - -.item-jump:disabled { - opacity: 0.55; - cursor: default; -} - -.item-jump .iconfont { - font-size: 14px; + margin-top: var(--su-space-4); } .item-banner { width: 100%; - margin-top: 18px; - aspect-ratio: 3 / 1; - border-radius: 10px; - object-fit: cover; - background-color: var(--border-color); + margin-top: var(--su-space-4); + border-radius: var(--su-radius-md); } -/* 详情页「推荐阅读?*/ +/* ---------- 文章 ---------- */ -.related-card { - padding: 20px; -} - -.related-title { +.article-head { display: flex; - align-items: center; - gap: 8px; - margin-bottom: 16px; - font-size: 16px; - font-weight: 600; - color: var(--text-main); + flex-direction: column; + gap: var(--su-space-2); } -.related-title-icon { - font-size: 16px; +.article-body { + display: flex; + flex-direction: column; + gap: var(--su-space-3); } -.related-grid { - list-style: none; - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 14px 24px; +.article-meta-dot { + color: var(--su-text-muted); } +.article-lead { + margin-bottom: var(--su-space-2); +} + +.article-body-image { + width: 100%; + margin: var(--su-space-2) 0; + border-radius: var(--su-radius-md); +} + +/* ---------- 推荐阅读 ---------- */ + .related-item { display: flex; align-items: center; - gap: 10px; - width: 100%; - padding: 4px; - border-radius: 8px; + gap: var(--su-space-2); + padding: var(--su-space-2); + border-radius: var(--su-radius-sm); text-align: left; - cursor: pointer; - transition: background-color 0.2s; + transition: background-color var(--su-duration-fast) var(--su-ease-in-out); } .related-item:hover { - background-color: var(--bg-color); -} - -.related-avatar { - width: 32px; - height: 32px; - border-radius: 50%; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - background-color: var(--primary-soft); - color: var(--primary-color); - font-size: 14px; - font-weight: 700; - flex-shrink: 0; -} - -.related-avatar img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; + background-color: var(--su-bg-subtle); } .related-info { - display: flex; - flex-direction: column; min-width: 0; } -.related-name { - font-size: 14px; - font-weight: 600; - color: var(--text-main); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} +/* ---------- 通用 ---------- */ -.related-desc { - margin-top: 2px; - font-size: 12px; - color: var(--text-muted); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -@media (max-width: 900px) { - .related-grid { - grid-template-columns: minmax(0, 1fr); - } -} - -/* ============================================================ - 提示?/ 回到顶部 - ============================================================ */ - -.notice { - display: flex; - flex-direction: column; - gap: 6px; - margin-bottom: 20px; - padding: 24px; - border: 1px dashed var(--border-color); - border-radius: 12px; - background-color: rgba(255, 255, 255, 0.7); - color: var(--text-sub); - font-size: 14px; -} - -.notice strong { - font-size: 14px; - font-weight: 600; - color: var(--text-main); -} - -.notice-error { - border-style: solid; - border-color: rgba(255, 62, 62, 0.25); - background-color: #fff5f5; -} - -.notice-error strong { - color: var(--danger); -} - -.notice-action { - align-self: flex-start; - margin-top: 8px; - padding: 6px 14px; - border: 1px solid var(--border-color); - border-radius: 8px; - background-color: var(--card-bg); - font-size: 14px; - cursor: pointer; -} - -.notice-action:hover { - border-color: var(--primary-color); - color: var(--primary-color); -} - -/* 返回顶部:复刻 Alright 的 .totop-button(42px 圆形浅灰底), - 默认隐藏,内容区滚动后才淡入 */ -.back-to-top { - position: fixed; - top: 50%; - right: 30px; - width: 42px; - height: 42px; - display: flex; +.link-button { + display: inline-flex; align-items: center; - justify-content: center; - border-radius: 50%; - border: 1px solid var(--border-color); - background-color: var(--border-color); - color: var(--text-sub); - cursor: pointer; - z-index: 99; - opacity: 0; - visibility: hidden; - transform: translateY(-50%) translateY(15px); - transition: - opacity 0.3s ease, - visibility 0.3s ease, - transform 0.3s ease, - background-color 0.2s, - color 0.2s; + gap: var(--su-space-1); + height: 32px; + padding: 0 var(--su-space-3); + border-radius: var(--su-radius-sm); + font-size: var(--su-font-size-sm); + color: var(--su-text-sub); + transition: background-color var(--su-duration-fast) var(--su-ease-in-out); } -.back-to-top.is-visible { - opacity: 1; - visibility: visible; - transform: translateY(-50%); +.link-button:hover { + background-color: var(--su-bg-subtle); + color: var(--su-text-main); } -.back-to-top:hover { - background-color: var(--border-strong); - border-color: var(--border-strong); - color: var(--text-main); +.sr-only { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; } -.back-to-top .iconfont { - font-size: 20px; -} - -/* 刷新按钮转圈 */ -.service-action[data-spinning='true'] .iconfont { - animation: spin 0.9s linear infinite; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -/* ============================================================ - 响应? ============================================================ */ +/* ---------- 响应式 ---------- */ @media (max-width: 1100px) { - :root { - --sidebar-width: 200px; - } - - .app-grid, - .article-grid, - .ces-grid { + .sidebar { + position: static; + display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--su-space-3); + padding: var(--su-space-4); } - .poster-grid { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } -} - -@media (max-width: 900px) { - .app { - height: auto; - min-height: 100vh; + .profile-card { + grid-row: span 2; } - .layout-container { - padding: 0 16px; - } - - .site-header { - position: sticky; + .service-panel { + display: none; } .header-info { display: none; } +} - .search-row { - position: static; - left: auto; - top: auto; - transform: none; - width: auto; - height: 44px; - margin: 16px 16px 0; - } - - .layout { - margin-top: 0; - height: auto; - padding: 16px; - grid-template-columns: minmax(0, 1fr); - } - +@media (max-width: 820px) { .sidebar { display: none; } - .content { - padding-top: 0; + .search-row { + top: 60px; } - .content-veil { - display: none; + .site-header-inner { + height: 60px; } +} - .content-scroll { - overflow: visible; - -webkit-mask-image: none; - mask-image: none; - } - - /* 窄屏沿用主题的右下侧定位,隐藏/显示的位移单独处理 */ - .back-to-top { - top: auto; - right: 15px; - bottom: 30%; - transform: translateY(15px); - } - - .back-to-top.is-visible { - transform: translateY(0); +@media (prefers-reduced-motion: reduce) { + .cover, + .app-card, + .related-item, + .link-button { + transition: none; } } diff --git a/stash-web/src/App.tsx b/stash-web/src/App.tsx index 7b557c5..b9edf9b 100644 --- a/stash-web/src/App.tsx +++ b/stash-web/src/App.tsx @@ -1,3 +1,4 @@ +import { Empty, Layout, Notice } from '@stash/ui' import { useEffect, useMemo, useRef, useState } from 'react' import { BackToTop } from './components/common/BackToTop' import { ArticleDetailView } from './components/content/ArticleDetailView' @@ -39,7 +40,6 @@ export default function App() { const { view, go, back } = useViewRoute() const [scrolled, setScrolled] = useState(false) const searchRef = useRef(null) - const scrollRef = useRef(null) const categories = useMemo( () => withLocalCategory(projects, catalog.categories), @@ -103,20 +103,18 @@ export default function App() { return [] }, [itemView, itemCategory]) - // 切换视图时把内容区滚回顶部 + // 切换视图时回到顶部(页面整体滚动) useEffect(() => { - scrollRef.current?.scrollTo({ top: 0 }) + window.scrollTo({ top: 0 }) }, [view]) - // 桌面端滚动作在内容容器里,窄屏滚的是窗口: - // 两处都要驱动「已滚动」状态(顶部渐隐层 + 返回顶部按钮共用) useEffect(() => { const sync = () => { - const inner = scrollRef.current?.scrollTop ?? 0 - const next = Math.max(inner, window.scrollY) > 4 + const next = window.scrollY > 4 setScrolled((current) => (current === next ? current : next)) } + sync() window.addEventListener('scroll', sync, { passive: true }) return () => window.removeEventListener('scroll', sync) }, []) @@ -125,7 +123,6 @@ export default function App() { * 「打开这个分区」应该落到哪个视图。 * * 应用类分区配了首页展示块时,直接落到那个分组;其余情况打开整个分区。 - * 这样卡片上的「···」和分区标题都通向卡片里实际展示的那块内容。 */ const detailViewFor = (categoryId: string): View => { const category = categories.find((item) => item.id === categoryId) @@ -203,21 +200,13 @@ export default function App() { function renderItemView() { if (itemView === undefined) return null if (itemCategory === undefined) { - return ( -

- {dataPending ? '正在读取内容库…' : '分类不存在'} -

- ) + return } if (itemCategory.kind === 'articles') { const article = findArticleItem(itemCategory, itemView.itemId) if (article === undefined) { - return ( -

- {dataPending ? '正在读取内容库…' : '条目不存在'} -

- ) + return } return ( @@ -230,11 +219,7 @@ export default function App() { } if (detailModel === undefined) { - return ( -

- {dataPending ? '正在读取内容库…' : '条目不存在'} -

- ) + return } return ( @@ -249,111 +234,98 @@ export default function App() { } return ( -
+ <> {/* 主题的顶部背景图:固定 65vh,底部渐隐到页面底色 */} + ) } diff --git a/stash-web/src/assets/iconfont.woff2 b/stash-web/src/assets/iconfont.woff2 deleted file mode 100644 index 4ca9e6f..0000000 Binary files a/stash-web/src/assets/iconfont.woff2 and /dev/null differ diff --git a/stash-web/src/components/common/BackToTop.tsx b/stash-web/src/components/common/BackToTop.tsx index 49d7f8f..aef9b79 100644 --- a/stash-web/src/components/common/BackToTop.tsx +++ b/stash-web/src/components/common/BackToTop.tsx @@ -1,33 +1,14 @@ -import { ArrowUpIcon } from './Icon' +import { ScrollTop } from '@stash/ui' /** - * 回到顶部:复刻 Alright 主题的 .scroll-toolbar .totop-button。 - * - * - 42px 圆形、浅灰底,固定在右侧(bottom: 50%) - * - 默认隐藏,只有内容区滚动后才淡入,回到顶部后再淡出 + * 回到顶部:直接用组件库的 ScrollTop。 + * 页面整体滚动(没有内部滚动容器),所以不传 targetId。 */ type BackToTopProps = { - /** 内容区是否已滚动(由 App 传入) */ + /** 页面是否已滚动(由 App 传入) */ visible: boolean } export function BackToTop({ visible }: BackToTopProps) { - const handleClick = () => { - // 桌面端滚动的是内容容器,窄屏滚动的是窗口,两处都滚一遍 - document.getElementById('content-scroll')?.scrollTo({ top: 0, behavior: 'smooth' }) - window.scrollTo({ top: 0, behavior: 'smooth' }) - } - - return ( - - ) + return } diff --git a/stash-web/src/components/common/CountBadge.tsx b/stash-web/src/components/common/CountBadge.tsx index 8248eaf..4c8212e 100644 --- a/stash-web/src/components/common/CountBadge.tsx +++ b/stash-web/src/components/common/CountBadge.tsx @@ -1,11 +1,13 @@ +import { Badge } from '@stash/ui' + /** - * 数量角标:贴在分区标题右上角的小红标。 - * 纯展示组件,只接收数字,宽度由内容撑开(两位数不会变形)。 + * 数量角标:贴在分区标题旁边的小红标。 + * 组件库的 Badge 已经处理了两位数和 99+ 的宽度,这里只做语义封装。 */ type CountBadgeProps = { count: number } export function CountBadge({ count }: CountBadgeProps) { - return {count} + return } diff --git a/stash-web/src/components/common/Icon.tsx b/stash-web/src/components/common/Icon.tsx index 38a32c9..6151b46 100644 --- a/stash-web/src/components/common/Icon.tsx +++ b/stash-web/src/components/common/Icon.tsx @@ -1,63 +1,57 @@ -/** - * 通用图标组件。 - * - * 全部使用 Alright 主题的 iconfont 线性图标(见 src/iconfont.css), - * 尺寸与颜色由使用处的 CSS 控制(font-size / color), - * 因此同一套图标可以复用在按钮、统计条、导航等不同位置。 - */ +import { Icon } from '@stash/ui' +/** + * 语义图标:stash 内部一直按用途引用(SearchIcon / RefreshIcon…), + * 这里统一映射到 @stash/ui 的 iconfont 字形,避免各页面记字形名。 + */ type IconProps = { - /** 附加类名,便于局部覆盖字号或颜色 */ className?: string } -const base = (name: string, className?: string) => - `iconfont icon-${name}${className !== undefined ? ` ${className}` : ''}` - export function SearchIcon({ className }: IconProps) { - return