From 42eb783395991bf0f03c4ed5d7b4d12491d61a8e Mon Sep 17 00:00:00 2001 From: moxi Date: Sat, 3 Jan 2026 22:43:58 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=85=8D?= =?UTF-8?q?=E9=A2=9D=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2=20UI=20=E4=B8=8E?= =?UTF-8?q?=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 卡片布局改为 CSS Grid 自适应,最小宽度 380px,支持 1080p 下显示 4 列 - 分页控件重构:移除数字输入框,改为 [按页显示] / [显示全部] 切换按钮 - 动态计算每页数量:按页模式固定显示 3 行(行数 * 动态列数) - Header 布局优化:凭证计数移至标题旁(淡蓝色气泡),刷新按钮合并为图标 - 安全限制:凭证数超过 30 个时禁用显示全部功能并弹窗提示 --- src/components/quota/QuotaSection.tsx | 132 +++++++++++++++---------- src/components/quota/useGridColumns.ts | 40 ++++++++ src/i18n/locales/zh-CN.json | 5 +- src/pages/QuotaPage.module.scss | 89 +++++++++++++---- 4 files changed, 192 insertions(+), 74 deletions(-) create mode 100644 src/components/quota/useGridColumns.ts diff --git a/src/components/quota/QuotaSection.tsx b/src/components/quota/QuotaSection.tsx index 98c7d4e..9852b4f 100644 --- a/src/components/quota/QuotaSection.tsx +++ b/src/components/quota/QuotaSection.tsx @@ -13,17 +13,17 @@ import { QuotaCard } from './QuotaCard'; import type { QuotaStatusState } from './QuotaCard'; import { useQuotaLoader } from './useQuotaLoader'; import type { QuotaConfig } from './quotaConfigs'; +import { useGridColumns } from './useGridColumns'; +import { IconRefreshCw } from '@/components/ui/icons'; import styles from '@/pages/QuotaPage.module.scss'; type QuotaUpdater = T | ((prev: T) => T); type QuotaSetter = (updater: QuotaUpdater) => void; -const MIN_CARD_PAGE_SIZE = 3; -const MAX_CARD_PAGE_SIZE = 30; +type ViewMode = 'paged' | 'all'; -const clampCardPageSize = (value: number) => - Math.min(MAX_CARD_PAGE_SIZE, Math.max(MIN_CARD_PAGE_SIZE, Math.round(value))); +const MAX_SHOW_ALL_THRESHOLD = 30; interface QuotaPaginationState { pageSize: number; @@ -40,7 +40,7 @@ interface QuotaPaginationState { const useQuotaPagination = (items: T[], defaultPageSize = 6): QuotaPaginationState => { const [page, setPage] = useState(1); - const [pageSize, setPageSizeState] = useState(() => clampCardPageSize(defaultPageSize)); + const [pageSize, setPageSizeState] = useState(defaultPageSize); const [loading, setLoadingState] = useState(false); const [loadingScope, setLoadingScope] = useState<'page' | 'all' | null>(null); @@ -57,7 +57,7 @@ const useQuotaPagination = (items: T[], defaultPageSize = 6): QuotaPaginatio }, [items, currentPage, pageSize]); const setPageSize = useCallback((size: number) => { - setPageSizeState(clampCardPageSize(size)); + setPageSizeState(size); setPage(1); }, []); @@ -107,6 +107,11 @@ export function QuotaSection({ Record >; + /* Removed useRef */ + const [columns, gridRef] = useGridColumns(380); // Min card width 380px matches SCSS + const [viewMode, setViewMode] = useState('paged'); + const [showTooManyWarning, setShowTooManyWarning] = useState(false); + const filteredFiles = useMemo(() => files.filter((file) => config.filterFn(file)), [ files, config.filterFn @@ -125,15 +130,25 @@ export function QuotaSection({ setLoading } = useQuotaPagination(filteredFiles); + // Update page size based on view mode and columns + useEffect(() => { + if (viewMode === 'all') { + setPageSize(Math.max(1, filteredFiles.length)); + } else { + // Paged mode: 3 rows * columns + setPageSize(columns * 3); + } + }, [viewMode, columns, filteredFiles.length, setPageSize]); + const { quota, loadQuota } = useQuotaLoader(config); - const handleRefreshPage = useCallback(() => { - loadQuota(pageItems, 'page', setLoading); - }, [loadQuota, pageItems, setLoading]); - - const handleRefreshAll = useCallback(() => { - loadQuota(filteredFiles, 'all', setLoading); - }, [loadQuota, filteredFiles, setLoading]); + const handleRefresh = useCallback(() => { + if (viewMode === 'all') { + loadQuota(filteredFiles, 'all', setLoading); + } else { + loadQuota(pageItems, 'page', setLoading); + } + }, [loadQuota, filteredFiles, pageItems, viewMode, setLoading]); useEffect(() => { if (loading) return; @@ -153,28 +168,53 @@ export function QuotaSection({ }); }, [filteredFiles, loading, setQuota]); + const titleNode = ( +
+ {t(`${config.i18nPrefix}.title`)} + {filteredFiles.length > 0 && ( + + {filteredFiles.length} + + )} +
+ ); + return ( +
+ + +
- } @@ -186,31 +226,7 @@ export function QuotaSection({ /> ) : ( <> -
-
- - { - const value = e.currentTarget.valueAsNumber; - if (!Number.isFinite(value)) return; - setPageSize(value); - }} - /> -
-
- -
- {filteredFiles.length} {t('auth_files.files_count')} -
-
-
-
+
{pageItems.map((item) => ( ({ /> ))}
- {filteredFiles.length > pageSize && ( + {filteredFiles.length > pageSize && viewMode === 'paged' && (
+
+
+ )}
); } diff --git a/src/components/quota/useGridColumns.ts b/src/components/quota/useGridColumns.ts new file mode 100644 index 0000000..206ccd8 --- /dev/null +++ b/src/components/quota/useGridColumns.ts @@ -0,0 +1,40 @@ +import { useState, useEffect, useCallback } from 'react'; + +/** + * Hook to calculate the number of grid columns based on container width and item min-width. + * Returns [columns, refCallback]. + */ +export function useGridColumns( + itemMinWidth: number, + gap: number = 16 +): [number, (node: HTMLDivElement | null) => void] { + const [columns, setColumns] = useState(1); + const [element, setElement] = useState(null); + + const refCallback = useCallback((node: HTMLDivElement | null) => { + setElement(node); + }, []); + + useEffect(() => { + if (!element) return; + + const updateColumns = () => { + const containerWidth = element.clientWidth; + const effectiveItemWidth = itemMinWidth + gap; + const count = Math.floor((containerWidth + gap) / effectiveItemWidth); + setColumns(Math.max(1, count)); + }; + + updateColumns(); + + const observer = new ResizeObserver(() => { + updateColumns(); + }); + + observer.observe(element); + + return () => observer.disconnect(); + }, [element, itemMinWidth, gap]); + + return [columns, refCallback]; +} diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 34fbb2c..743a595 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -327,6 +327,9 @@ "search_placeholder": "输入名称、类型或提供方关键字", "page_size_label": "单页数量", "page_size_unit": "个/页", + "view_mode_paged": "按页显示", + "view_mode_all": "显示全部", + "too_many_files_warning": "您的凭证总数过多,全部加载会导致页面卡顿,请保持单页浏览。", "filter_all": "全部", "filter_qwen": "Qwen", "filter_gemini": "Gemini", @@ -835,4 +838,4 @@ "version": "管理中心版本", "author": "作者" } -} +} \ No newline at end of file diff --git a/src/pages/QuotaPage.module.scss b/src/pages/QuotaPage.module.scss index 46857b3..c8715ae 100644 --- a/src/pages/QuotaPage.module.scss +++ b/src/pages/QuotaPage.module.scss @@ -30,6 +30,28 @@ display: flex; gap: $spacing-sm; flex-wrap: wrap; + align-items: center; +} + +.titleWrapper { + display: flex; + align-items: center; + gap: $spacing-sm; +} + +.countBadge { + display: inline-flex; + align-items: center; + justify-content: center; + height: 24px; + min-width: 24px; + padding: 0 8px; + border-radius: 999px; + font-size: 13px; + font-weight: 600; + color: #0284c7; // sky-600 + background-color: #e0f2fe; // sky-100 + box-sizing: border-box; } .errorBox { @@ -76,11 +98,7 @@ .geminiCliGrid { display: grid; gap: $spacing-md; - grid-template-columns: repeat(3, minmax(0, 1fr)); - - @include tablet { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } + grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); @include mobile { grid-template-columns: 1fr; @@ -112,28 +130,30 @@ } } +.viewModeToggle { + display: flex; + gap: 8px; + background-color: var(--bg-secondary); + padding: 4px; + border-radius: $radius-md; +} + .antigravityCard { - background-image: linear-gradient( - 180deg, - rgba(224, 247, 250, 0.12), - rgba(224, 247, 250, 0) - ); + background-image: linear-gradient(180deg, + rgba(224, 247, 250, 0.12), + rgba(224, 247, 250, 0)); } .codexCard { - background-image: linear-gradient( - 180deg, - rgba(255, 243, 224, 0.18), - rgba(255, 243, 224, 0) - ); + background-image: linear-gradient(180deg, + rgba(255, 243, 224, 0.18), + rgba(255, 243, 224, 0)); } .geminiCliCard { - background-image: linear-gradient( - 180deg, - rgba(231, 239, 255, 0.2), - rgba(231, 239, 255, 0) - ); + background-image: linear-gradient(180deg, + rgba(231, 239, 255, 0.2), + rgba(231, 239, 255, 0)); } .quotaSection { @@ -331,3 +351,32 @@ background-color: var(--bg-secondary); border-radius: $radius-md; } + +.warningOverlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.warningModal { + background-color: var(--bg-primary); + border-radius: $radius-lg; + padding: $spacing-lg; + max-width: 400px; + text-align: center; + box-shadow: $shadow-lg; + + p { + margin: 0 0 $spacing-md 0; + color: var(--text-primary); + font-size: 14px; + line-height: 1.6; + } +} \ No newline at end of file From 334d75f2dde1715580439760c4a63cf0773cbb92 Mon Sep 17 00:00:00 2001 From: moxi Date: Sun, 4 Jan 2026 00:04:36 +0800 Subject: [PATCH 2/6] fix: lint error --- package.json | 2 +- src/components/common/PageTransition.tsx | 30 ++++++++++++++++-------- src/components/quota/QuotaSection.tsx | 3 +-- src/components/ui/Modal.tsx | 21 ++++++++++++----- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 2ce978f..52b65e3 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives", "format": "prettier --write \"src/**/*.{ts,tsx,css,scss}\"", "type-check": "tsc --noEmit" }, diff --git a/src/components/common/PageTransition.tsx b/src/components/common/PageTransition.tsx index e237fc2..a09c908 100644 --- a/src/components/common/PageTransition.tsx +++ b/src/components/common/PageTransition.tsx @@ -69,17 +69,27 @@ export function PageTransition({ : toIndex > fromIndex ? 'forward' : 'backward'; - setTransitionDirection(nextDirection); - setLayers((prev) => { - const prevCurrent = prev[prev.length - 1]; - return [ - prevCurrent - ? { ...prevCurrent, status: 'exiting' } - : { key: location.key, location, status: 'exiting' }, - { key: location.key, location, status: 'current' }, - ]; + + let cancelled = false; + + queueMicrotask(() => { + if (cancelled) return; + setTransitionDirection(nextDirection); + setLayers((prev) => { + const prevCurrent = prev[prev.length - 1]; + return [ + prevCurrent + ? { ...prevCurrent, status: 'exiting' } + : { key: location.key, location, status: 'exiting' }, + { key: location.key, location, status: 'current' }, + ]; + }); + setIsAnimating(true); }); - setIsAnimating(true); + + return () => { + cancelled = true; + }; }, [ isAnimating, location, diff --git a/src/components/quota/QuotaSection.tsx b/src/components/quota/QuotaSection.tsx index 9852b4f..dc3d476 100644 --- a/src/components/quota/QuotaSection.tsx +++ b/src/components/quota/QuotaSection.tsx @@ -114,7 +114,7 @@ export function QuotaSection({ const filteredFiles = useMemo(() => files.filter((file) => config.filterFn(file)), [ files, - config.filterFn + config ]); const { @@ -126,7 +126,6 @@ export function QuotaSection({ goToPrev, goToNext, loading: sectionLoading, - loadingScope, setLoading } = useQuotaPagination(filteredFiles); diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx index 97d7a75..8282918 100644 --- a/src/components/ui/Modal.tsx +++ b/src/components/ui/Modal.tsx @@ -54,19 +54,28 @@ export function Modal({ open, title, onClose, footer, width = 520, children }: P ); useEffect(() => { + let cancelled = false; + if (open) { if (closeTimerRef.current !== null) { window.clearTimeout(closeTimerRef.current); closeTimerRef.current = null; } - setIsVisible(true); - setIsClosing(false); - return; + queueMicrotask(() => { + if (cancelled) return; + setIsVisible(true); + setIsClosing(false); + }); + } else if (isVisible) { + queueMicrotask(() => { + if (cancelled) return; + startClose(false); + }); } - if (isVisible) { - startClose(false); - } + return () => { + cancelled = true; + }; }, [open, isVisible, startClose]); const handleClose = useCallback(() => { From 38a3e204279233d241c31d352da19d8a396174a3 Mon Sep 17 00:00:00 2001 From: moxi Date: Sun, 4 Jan 2026 00:45:34 +0800 Subject: [PATCH 3/6] feat(quota): enhance QuotaSection with improved view mode handling and refresh functionality - Introduced effective view mode logic to manage 'paged' and 'all' views based on file count. - Added a warning for too many files when in 'all' view, prompting users to switch to 'paged'. - Updated refresh button to handle loading states more effectively and provide clearer user feedback. - Enhanced UI with new translations for view modes and refresh actions. - Adjusted styles for better alignment and spacing in the view mode toggle and refresh button. --- src/components/quota/QuotaSection.tsx | 70 ++++++++++++++++++++------- src/i18n/locales/en.json | 6 ++- src/i18n/locales/zh-CN.json | 5 +- src/pages/QuotaPage.module.scss | 15 ++++-- 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/components/quota/QuotaSection.tsx b/src/components/quota/QuotaSection.tsx index dc3d476..23b6983 100644 --- a/src/components/quota/QuotaSection.tsx +++ b/src/components/quota/QuotaSection.tsx @@ -2,11 +2,12 @@ * Generic quota section component. */ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { EmptyState } from '@/components/ui/EmptyState'; +import { triggerHeaderRefresh } from '@/hooks/useHeaderRefresh'; import { useQuotaStore, useThemeStore } from '@/stores'; import type { AuthFileItem, ResolvedTheme } from '@/types'; import { QuotaCard } from './QuotaCard'; @@ -116,6 +117,8 @@ export function QuotaSection({ files, config ]); + const showAllAllowed = filteredFiles.length <= MAX_SHOW_ALL_THRESHOLD; + const effectiveViewMode: ViewMode = viewMode === 'all' && !showAllAllowed ? 'paged' : viewMode; const { pageSize, @@ -129,25 +132,55 @@ export function QuotaSection({ setLoading } = useQuotaPagination(filteredFiles); + useEffect(() => { + if (showAllAllowed) return; + if (viewMode !== 'all') return; + + let cancelled = false; + queueMicrotask(() => { + if (cancelled) return; + setViewMode('paged'); + setShowTooManyWarning(true); + }); + + return () => { + cancelled = true; + }; + }, [showAllAllowed, viewMode]); + // Update page size based on view mode and columns useEffect(() => { - if (viewMode === 'all') { + if (effectiveViewMode === 'all') { setPageSize(Math.max(1, filteredFiles.length)); } else { // Paged mode: 3 rows * columns setPageSize(columns * 3); } - }, [viewMode, columns, filteredFiles.length, setPageSize]); + }, [effectiveViewMode, columns, filteredFiles.length, setPageSize]); const { quota, loadQuota } = useQuotaLoader(config); + const pendingQuotaRefreshRef = useRef(false); + const prevFilesLoadingRef = useRef(loading); + const handleRefresh = useCallback(() => { - if (viewMode === 'all') { - loadQuota(filteredFiles, 'all', setLoading); - } else { - loadQuota(pageItems, 'page', setLoading); - } - }, [loadQuota, filteredFiles, pageItems, viewMode, setLoading]); + pendingQuotaRefreshRef.current = true; + void triggerHeaderRefresh(); + }, []); + + useEffect(() => { + const wasLoading = prevFilesLoadingRef.current; + prevFilesLoadingRef.current = loading; + + if (!pendingQuotaRefreshRef.current) return; + if (loading) return; + if (!wasLoading) return; + + pendingQuotaRefreshRef.current = false; + const scope = effectiveViewMode === 'all' ? 'all' : 'page'; + const targets = effectiveViewMode === 'all' ? filteredFiles : pageItems; + loadQuota(targets, scope, setLoading); + }, [loading, effectiveViewMode, filteredFiles, pageItems, loadQuota, setLoading]); useEffect(() => { if (loading) return; @@ -185,14 +218,14 @@ export function QuotaSection({
} @@ -239,7 +275,7 @@ export function QuotaSection({ /> ))} - {filteredFiles.length > pageSize && viewMode === 'paged' && ( + {filteredFiles.length > pageSize && effectiveViewMode === 'paged' && (
); + const isRefreshing = sectionLoading || loading; + return ( ({ variant="secondary" size="sm" onClick={handleRefresh} - disabled={disabled || sectionLoading || loading || filteredFiles.length === 0} - loading={sectionLoading || loading} + disabled={disabled || isRefreshing || filteredFiles.length === 0} + loading={isRefreshing} title={t('quota_management.refresh_files_and_quota')} + aria-label={t('quota_management.refresh_files_and_quota')} > - - {!sectionLoading && !loading && } - {t('quota_management.refresh_files_and_quota')} - + {!isRefreshing && } } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 8d369b7..d79fdb6 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -713,7 +713,7 @@ "title": "Quota Management", "description": "Monitor OAuth quota status for Antigravity, Codex, and Gemini CLI credentials.", "refresh_files": "Refresh auth files", - "refresh_files_and_quota": "Refresh auth files & page quota" + "refresh_files_and_quota": "Refresh files & quota" }, "system_info": { "title": "Management Center Info", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 256848d..e836991 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -713,7 +713,7 @@ "title": "配额管理", "description": "集中查看 OAuth 额度与剩余情况", "refresh_files": "刷新认证文件", - "refresh_files_and_quota": "刷新认证文件列表&当前页额度" + "refresh_files_and_quota": "刷新认证文件&额度" }, "system_info": { "title": "管理中心信息", diff --git a/src/pages/QuotaPage.module.scss b/src/pages/QuotaPage.module.scss index 4474fe6..a515471 100644 --- a/src/pages/QuotaPage.module.scss +++ b/src/pages/QuotaPage.module.scss @@ -31,6 +31,14 @@ gap: $spacing-sm; flex-wrap: wrap; align-items: center; + + :global(.btn-sm) { + line-height: 16px; + } + + :global(svg) { + display: block; + } } .titleWrapper { @@ -136,13 +144,6 @@ align-items: center; } -.refreshButtonContent { - display: inline-flex; - align-items: center; - gap: $spacing-xs; - white-space: nowrap; -} - .antigravityCard { background-image: linear-gradient(180deg, rgba(224, 247, 250, 0.12), From e914337e57a2f135a694cfea959878c79e9a9e38 Mon Sep 17 00:00:00 2001 From: moxi Date: Sun, 4 Jan 2026 01:12:48 +0800 Subject: [PATCH 5/6] feat(button): enhance button component to conditionally render children - Added a check to determine if children are present before rendering them in the button. - Improved button rendering logic for better handling of empty or false children values. --- src/components/ui/Button.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index 00d71a9..d4f3dc0 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -20,6 +20,7 @@ export function Button({ disabled, ...rest }: PropsWithChildren) { + const hasChildren = children !== null && children !== undefined && children !== false; const classes = [ 'btn', `btn-${variant}`, @@ -33,7 +34,7 @@ export function Button({ return ( ); } From 692f7f3cdee9e5c9a9b887bc1697079e6e0a6d5e Mon Sep 17 00:00:00 2001 From: moxi Date: Sun, 4 Jan 2026 18:48:27 +0800 Subject: [PATCH 6/6] fix(quota): allow refresh without creds --- src/components/quota/QuotaSection.tsx | 3 ++- src/pages/QuotaPage.module.scss | 4 ++-- src/styles/themes.scss | 6 ++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/quota/QuotaSection.tsx b/src/components/quota/QuotaSection.tsx index ab7fcf9..377285c 100644 --- a/src/components/quota/QuotaSection.tsx +++ b/src/components/quota/QuotaSection.tsx @@ -179,6 +179,7 @@ export function QuotaSection({ pendingQuotaRefreshRef.current = false; const scope = effectiveViewMode === 'all' ? 'all' : 'page'; const targets = effectiveViewMode === 'all' ? filteredFiles : pageItems; + if (targets.length === 0) return; loadQuota(targets, scope, setLoading); }, [loading, effectiveViewMode, filteredFiles, pageItems, loadQuota, setLoading]); @@ -244,7 +245,7 @@ export function QuotaSection({ variant="secondary" size="sm" onClick={handleRefresh} - disabled={disabled || isRefreshing || filteredFiles.length === 0} + disabled={disabled || isRefreshing} loading={isRefreshing} title={t('quota_management.refresh_files_and_quota')} aria-label={t('quota_management.refresh_files_and_quota')} diff --git a/src/pages/QuotaPage.module.scss b/src/pages/QuotaPage.module.scss index a515471..5d52e9a 100644 --- a/src/pages/QuotaPage.module.scss +++ b/src/pages/QuotaPage.module.scss @@ -57,8 +57,8 @@ border-radius: 999px; font-size: 13px; font-weight: 600; - color: #0284c7; // sky-600 - background-color: #e0f2fe; // sky-100 + color: var(--count-badge-text); + background-color: var(--count-badge-bg); box-sizing: border-box; } diff --git a/src/styles/themes.scss b/src/styles/themes.scss index 65dde1f..1892763 100644 --- a/src/styles/themes.scss +++ b/src/styles/themes.scss @@ -32,6 +32,9 @@ --failure-badge-text: #991b1b; --failure-badge-border: #fca5a5; + --count-badge-bg: rgba(59, 130, 246, 0.14); + --count-badge-text: var(--primary-active); + --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); } @@ -66,6 +69,9 @@ --failure-badge-text: #fca5a5; --failure-badge-border: #dc2626; + --count-badge-bg: rgba(59, 130, 246, 0.25); + --count-badge-text: var(--primary-active); + --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.3); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.3); }