diff options
Diffstat (limited to 'src/components/shared/PageHeader.module.scss')
| -rw-r--r-- | src/components/shared/PageHeader.module.scss | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/src/components/shared/PageHeader.module.scss b/src/components/shared/PageHeader.module.scss new file mode 100644 index 0000000..bf2fe3e --- /dev/null +++ b/src/components/shared/PageHeader.module.scss @@ -0,0 +1,400 @@ +/** + * 页面顶栏的统一样式,代理 / 连接 / 规则 / 日志四个页面共用(见 PageHeader.tsx)。 + * + * 断点约定: + * >1024 一行排下:标题 · 分段标签 · 搜索 · 操作钮 + * ≤1024 搜索换到第二行,操作钮靠 margin 顶到第一行右端 + * ≤768 顶栏拉平到页面底色(窄屏顶部已经有一条不透明的导航条, + * 顶栏再铺一层外壳色会夹出第三条色带),按钮退化成正方形图标钮, + * 凹陷的控件(分段标签轨道、输入框)翻成浮起 + */ + +.header { + position: sticky; + top: 0; + z-index: 10; + display: flex; + align-items: center; + gap: 12px; + padding: 20px 32px 15px; + // 半透明外壳色 + 毛玻璃 + 下边框:内容从底下滚过去时能透出一点, + // 同时比纯白的卡片灰一档。弹层都 portal 到 body, + // 不受 backdrop-filter 生成包含块的影响 + background: var(--color-chrome); + backdrop-filter: saturate(180%) blur(20px); + border-bottom: 1px solid var(--color-chrome-border); + + @media (max-width: 1024px) { + flex-wrap: wrap; + padding: 16px 16px 12px; + } + + @media (max-width: 768px) { + gap: 8px; + padding: 10px 12px; + background: var(--color-background); + backdrop-filter: none; + } +} + +.title { + margin: 0; + font-size: 1.6rem; + font-weight: 700; + line-height: 1.2; + letter-spacing: -0.01em; + color: var(--color-text-highlight); + white-space: nowrap; + + @media (max-width: 1024px) { + font-size: 1.35rem; + } + + @media (max-width: 768px) { + font-size: 1.1rem; + } +} + +/** 窄屏下强制换行的占位,让它后面的东西独占下一行 */ +.rowBreak { + display: none; + + @media (max-width: 768px) { + display: block; + flex-basis: 100%; + height: 0; + } +} + +/* ---------- 分段标签 ---------- */ + +.tabs { + display: flex; + align-items: center; + gap: 2px; + padding: 4px; + border-radius: 12px; + background: var(--color-track); + flex-shrink: 0; + + // 顶栏在窄屏拉平到页面底色后,--color-track 就是底色本身,凹槽会整个消失。 + // 这里去掉凹槽,改成「选中项浮起来」的样式,和导航胶囊、卡片同一套语言 + @media (max-width: 768px) { + padding: 0; + gap: 4px; + background: none; + } +} + +.tab { + display: inline-flex; + align-items: center; + gap: 7px; + appearance: none; + border: none; + background: transparent; + font-family: inherit; + font-size: 0.88rem; + font-weight: 500; + color: var(--color-text-secondary); + padding: 7px 14px; + border-radius: 9px; + cursor: pointer; + white-space: nowrap; + transition: + background-color 0.15s ease, + color 0.15s ease, + box-shadow 0.15s ease; + + &:hover:not(.tabActive) { + color: var(--color-text); + background: var(--color-hover-soft); + } + + &:focus-visible { + outline: 2px solid var(--color-focus-blue); + outline-offset: -1px; + } + + @media (max-width: 768px) { + gap: 5px; + padding: 5px 10px; + font-size: 0.8rem; + } +} + +.tabActive { + background: var(--color-card); + color: var(--color-text-highlight); + box-shadow: var(--shadow-segment); + + @media (max-width: 768px) { + border: 1px solid var(--color-card-border); + // 补掉边框占掉的 1px,选中和未选中的高度才对得齐 + padding: 4px 9px; + } +} + +.tabCount { + font-family: var(--font-mono); + font-size: 0.72rem; + font-weight: 500; + color: var(--color-text-secondary); + + // 小屏上标题 + 标签 + 几个操作钮刚好挤满一行,计数是这里最先能舍的东西 + @media (max-width: 400px) { + display: none; + } +} + +/* ---------- 搜索 ---------- */ + +.search { + position: relative; + display: flex; + align-items: center; + flex: 1 1 240px; + min-width: 160px; + max-width: 340px; + margin-left: auto; + + // 同时覆盖裸 <input> 和 TextFilter 自带的样式,两种用法长得一样 + input { + width: 100%; + height: 38px; + padding: 0 12px 0 38px; + border-radius: 10px; + border: 1px solid var(--color-card-border); + background: var(--color-track); + box-shadow: none; + color: var(--color-text); + font: inherit; + font-size: 0.85rem; + outline: none; + appearance: none; + + &::placeholder { + color: var(--color-text-secondary); + opacity: 0.75; + } + + &:focus { + border-color: var(--color-focus-blue); + } + } + + @media (max-width: 1024px) { + flex-basis: 100%; + min-width: 0; + max-width: none; + margin-left: 0; + } + + @media (max-width: 768px) { + input { + height: 34px; + padding-left: 34px; + font-size: 0.8rem; + // 同分段标签:底色拉平后凹陷的输入框会看不见,改成和卡片同层浮起 + background: var(--color-card); + } + } +} + +/** + * 搜索旁边还要并排放别的控件(下拉筛选)时叠这个类: + * 窄屏下别独占整行,把剩下的宽度让出来 + */ +.searchInline { + @media (max-width: 768px) { + flex: 1 1 auto; + flex-basis: auto; + min-width: 0; + } +} + +.searchIcon { + position: absolute; + left: 13px; + color: var(--color-text-secondary); + pointer-events: none; + + @media (max-width: 768px) { + left: 11px; + } +} + +/* ---------- 下拉筛选 ---------- */ + +// 叠在 Select 组件自带样式之上,把它拉齐到顶栏这套尺寸。 +// 类名写两遍是为了翻倍特异性——Select 自己的 .select 也是单类, +// 靠打包顺序决定胜负太脆 +.select.select { + height: 38px; + width: auto; + flex-shrink: 0; + border-radius: 10px; + border-color: var(--color-card-border); + background-color: var(--color-track); + box-shadow: none; + font-size: 0.85rem; + + @media (max-width: 768px) { + height: 34px; + font-size: 0.8rem; + background-color: var(--color-card); + } +} + +/* ---------- 操作按钮 ---------- */ + +.actions { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; + + // 搜索换到第二行后,靠 margin 把操作钮顶到第一行右端 + @media (max-width: 1024px) { + margin-left: auto; + } + + @media (max-width: 768px) { + gap: 6px; + } +} + +.btn { + display: inline-flex; + align-items: center; + gap: 6px; + appearance: none; + height: 38px; + padding: 0 15px; + border-radius: 10px; + border: 1px solid transparent; + font-family: inherit; + font-size: 0.85rem; + font-weight: 500; + cursor: pointer; + white-space: nowrap; + transition: + background-color 0.15s ease, + border-color 0.15s ease, + color 0.15s ease, + opacity 0.15s ease; + + &:focus-visible { + outline: 2px solid var(--color-focus-blue); + outline-offset: 2px; + } + + // 窄屏下文字全部收起,按钮退化成正方形图标钮 + @media (max-width: 768px) { + gap: 0; + width: 34px; + height: 34px; + padding: 0; + justify-content: center; + } +} + +.btnGhost { + background: var(--color-card); + border-color: var(--color-card-border); + color: var(--color-text); + + &:hover { + border-color: var(--color-focus-blue); + color: var(--color-focus-blue); + } +} + +.btnPrimary { + background: var(--color-focus-blue); + color: #fff; + + &:hover:not(:disabled) { + filter: brightness(1.08); + } + + &:disabled { + cursor: default; + } +} + +.btnDanger { + background: var(--color-danger); + color: #fff; + + &:hover { + filter: brightness(1.08); + } +} + +.btnPaused { + background: var(--color-warn-soft-bg); + color: var(--color-warn); + border-color: transparent; +} + +.btnBusy { + opacity: 0.75; +} + +/** 中等宽度就收起的文字,让给更重要的按钮 */ +.btnText { + @media (max-width: 1200px) { + display: none; + } +} + +/** 比 .btnText 保留得久一些:只有窄屏才收起 */ +.btnTextSm { + @media (max-width: 768px) { + display: none; + } +} + +.iconBtn { + display: inline-flex; + align-items: center; + justify-content: center; + appearance: none; + width: 38px; + height: 38px; + border-radius: 10px; + border: 1px solid var(--color-card-border); + background: var(--color-card); + color: var(--color-text-secondary); + cursor: pointer; + transition: + background-color 0.15s ease, + border-color 0.15s ease, + color 0.15s ease; + + &:hover { + border-color: var(--color-focus-blue); + color: var(--color-focus-blue); + } + + &:focus-visible { + outline: 2px solid var(--color-focus-blue); + outline-offset: 2px; + } + + @media (max-width: 768px) { + width: 34px; + height: 34px; + } +} + +.iconBtnActive { + background: var(--color-focus-blue); + border-color: var(--color-focus-blue); + color: #fff; + + &:hover { + color: #fff; + } +} |
