summaryrefslogtreecommitdiff
path: root/src/components/shared/PageHeader.module.scss
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/shared/PageHeader.module.scss')
-rw-r--r--src/components/shared/PageHeader.module.scss400
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;
+ }
+}