iter(g332): 主题工具下沉到组件库

This commit is contained in:
HIsheR
2026-10-27 02:05:27 +08:00
parent 3e6c32520b
commit 4b37399593
9 changed files with 97 additions and 14 deletions
+1
View File
@@ -90,3 +90,4 @@ export { noticeBar } from './noticeBar.js';
export { sortableList } from './sortableList.js';
export { masonry } from './masonry.js';
export { editable } from './editable.js';
export { theme, currentThemeDark } from './theme.js';
+54
View File
@@ -0,0 +1,54 @@
import { effect, signal } from '@ui-arch/frame';
import type { ThemeHandle, ThemeMode, ThemeOptions } from './types.js';
/*** 主题:把「URL 参数 > 本地记忆 > 跟随系统」的优先级收进一处,写 html[data-theme] 换整包令牌;接入方的二十行样板(读参数 / 读记忆 / 听系统 / 落盘)只剩一行调用 ***/
export function theme(options: ThemeOptions = {}): ThemeHandle {
// —— 状态集中声明 ——
const key = options.storageKey ?? 'ua-theme'; // 本地记忆键
const param = options.param ?? 'theme'; // URL 参数名,值 light / dark 时生效(iframe 联动入口)
const darkName = options.dark ?? 'dark'; // 写进 data-theme 的值
const media = typeof matchMedia === 'function' ? matchMedia('(prefers-color-scheme: dark)') : null;
const systemDark = signal(media?.matches ?? false); // 系统偏好落成信号,mode='system' 时它才是真相
const mode = signal<ThemeMode>(options.initial ?? readEnv(param, key)); // 初始选择:环境优先,兜底 system
const dark = signal(resolveDark(mode(), systemDark())); // 实际生效是否暗色(写给出品方直接绑控件)
/*** 系统偏好变化:只在跟随系统时影响视觉,显式选过的不被系统改回去 ***/
const onSystem = (e: MediaQueryListEvent): void => { systemDark(e.matches); };
media?.addEventListener('change', onSystem);
effect(() => {
const now = resolveDark(mode(), systemDark()); // 读两个依赖:选择变了或系统偏好变了都要重算
dark(now);
document.documentElement.dataset.theme = now ? darkName : ''; // 亮色靠 :root 缺省,清掉属性即可
if (mode() !== 'system') localStorage.setItem(key, mode()); // 只记显式选择:跟随系统不该被冻成某一档
});
/*** 切到指定档:'system' 交还给系统偏好,其余落盘后刷新时仍生效 ***/
const set = (m: ThemeMode): void => { mode(m); };
/*** 明暗互换:控件只有一个开关时用它,system 态下按当前生效值反着切 ***/
const toggle = (): void => { mode(dark() ? 'light' : 'dark'); };
/*** 摘监听:单页应用卸载前调用,避免重复实例各挂一份 media 监听 ***/
const dispose = (): void => { media?.removeEventListener('change', onSystem); };
return { mode, dark, set, toggle, dispose };
}
/*** 环境里的初值:URL 参数(联动入口)> 本地记忆 > 跟随系统 ***/
function readEnv(param: string, key: string): ThemeMode {
const fromUrl = new URLSearchParams(location.search).get(param);
if (fromUrl === 'dark' || fromUrl === 'light') return fromUrl; // 卫语句:参数是合法档位才认
const stored = localStorage.getItem(key);
return stored === 'dark' || stored === 'light' ? stored : 'system'; // 记住的选择优先于系统偏好
}
/*** 选择 + 系统偏好 → 是否暗色:显式档直接给答案,system 档才看系统 ***/
function resolveDark(mode: ThemeMode, systemDark: boolean): boolean {
return mode === 'system' ? systemDark : mode === 'dark';
}
/*** 读当前生效值(不建实例):只要一个「现在是不是暗色」的场景用得上,比如按主题切图表配色 ***/
export function currentThemeDark(darkName = 'dark'): boolean {
return document.documentElement.dataset.theme === darkName;
}
+20
View File
@@ -1170,3 +1170,23 @@ export interface EditableProps {
validate?: (v: string) => string | null; // 提交校验:返回文案即拒绝提交、保持编辑态并标红;null 表示通过
onCommit?: (v: string) => void; // 提交回调(值已写回信号之后触发)
}
/*** 主题档位:显式亮 / 暗,或交还给系统偏好 ***/
export type ThemeMode = 'light' | 'dark' | 'system';
/*** 主题工具入参 ***/
export interface ThemeOptions {
storageKey?: string; // 本地记忆键;缺省 ua-theme
param?: string; // URL 参数名,值 light / dark 时生效(iframe 联动入口);缺省 theme
dark?: string; // 写进 html[data-theme] 的暗色值;缺省 dark
initial?: ThemeMode; // 强制初值,忽略 URL 与本地记忆;缺省按「参数 > 记忆 > 系统」解析
}
/*** 主题工具句柄 ***/
export interface ThemeHandle {
mode: Signal<ThemeMode>; // 当前选择(light / dark / system),可读也可直接写
dark: Signal<boolean>; // 实际生效是否暗色:system 档下跟随系统偏好,可直接绑 switchButton 的 checked
set: (m: ThemeMode) => void; // 切到指定档;非 system 档会落盘
toggle: () => void; // 明暗互换(只有一个开关时用)
dispose: () => void; // 摘掉系统偏好监听
}
+2 -1
View File
@@ -104,7 +104,8 @@
"noticeBar(props):单行通知条,语义色 + 左侧符号 + 可选跑马灯与关闭,适合页顶常驻播报",
"sortableList(props):拖拽排序列表,整行可拖 + Alt+↑↓ 键盘搬位,返回 { el, move, order } 句柄",
"masonry(props):瀑布流多列布局,列宽自适应或固定列数,卡片不被拆到两列",
"editable(props):行内编辑,静止态是文字、点一下就地变输入框,Enter 提交 / Esc 取消 / 失焦提交"
"editable(props):行内编辑,静止态是文字、点一下就地变输入框,Enter 提交 / Esc 取消 / 失焦提交",
"theme(options):主题工具(非组件),URL 参数 > 本地记忆 > 跟随系统,写 html[data-theme] 换令牌,返回 { mode, dark, set, toggle, dispose }"
],
"keyTypes": [
"ButtonProps",