|
|
@@ -1,4 +1,5 @@
|
|
|
-import { booleanAttribute, Component, computed, input, model } from '@angular/core';
|
|
|
+import { booleanAttribute, Component, computed, inject, input, model } from '@angular/core';
|
|
|
+import { ThemeService } from '../../theme/theme.service';
|
|
|
|
|
|
export type SwitchSize = 'sm' | 'md';
|
|
|
|
|
|
@@ -12,6 +13,16 @@ export type SwitchSize = 'sm' | 'md';
|
|
|
styleUrl: './switch.scss',
|
|
|
})
|
|
|
export class UiSwitch {
|
|
|
+ /**
|
|
|
+ * 主题状态(2026-11-01)。
|
|
|
+ *
|
|
|
+ * 开/关两态的颜色需要**随主题反转**才能始终清晰:
|
|
|
+ * · 浅色模式 → 深色轨道 + 亮色圆点
|
|
|
+ * · 深色模式 → 亮色轨道 + 深色圆点
|
|
|
+ * 否则白底白轨 / 深底深轨,用户分不清开关状态(多次反馈)。
|
|
|
+ */
|
|
|
+ private readonly theme = inject(ThemeService, { optional: true });
|
|
|
+
|
|
|
/** 开关状态(双向绑定) */
|
|
|
readonly checked = model(false);
|
|
|
/** 尺寸 */
|
|
|
@@ -31,37 +42,70 @@ export class UiSwitch {
|
|
|
return [
|
|
|
'relative inline-flex shrink-0 items-center rounded-full transition-colors duration-200',
|
|
|
'disabled:pointer-events-none disabled:opacity-50',
|
|
|
- /*
|
|
|
- * 关闭态轨道用**深色**(2026-11-01 用户要求:白色背景下滑轨要明显)。
|
|
|
- *
|
|
|
- * 取 --surface-active 与 --edge-strong 之间偏深的一档?
|
|
|
- * 不 —— 直接在 switch.scss 里用固定深色,理由:
|
|
|
- * 两个主题下的目标都是「深色轨道 + 白色圆点」这组经典配色,
|
|
|
- * 而 --faint / --edge-strong / --surface-* 在深色主题下都偏浅,
|
|
|
- * 会退化成「白底白轨」(用户截图正是这个观感)。
|
|
|
- */
|
|
|
- this.checked() ? 'bg-primary' : 'ui-switch-track-off',
|
|
|
+ // 颜色一律走 trackStyle(内联),这里只留尺寸与通用态
|
|
|
sizes[this.size()],
|
|
|
].join(' ');
|
|
|
});
|
|
|
|
|
|
+ /**
|
|
|
+ * 轨道颜色 —— **随主题反转**(2026-11-01 用户要求)。
|
|
|
+ *
|
|
|
+ * | 主题 | 关闭 | 开启 |
|
|
|
+ * |---|---|---|
|
|
|
+ * | 浅色 | 深色轨道(#3a4049) | 品牌蓝 |
|
|
|
+ * | 深色 | 浅色轨道(#9aa3ad) | 品牌蓝 |
|
|
|
+ *
|
|
|
+ * 🔴 用**内联样式**而不是组件 SCSS 类:
|
|
|
+ * 模板 [class] 动态拼出的类名,Angular 样式封装配不上 ——
|
|
|
+ * 第一版写成 .ui-switch-track-off 后整条规则被丢弃,
|
|
|
+ * 界面上完全没有效果(用户截图:深色模式里开关仍是纯白)。
|
|
|
+ * 内联样式不受封装影响,优先级也足够。
|
|
|
+ */
|
|
|
+ private readonly isDark = computed(() => this.theme?.isDark() ?? false);
|
|
|
+
|
|
|
+ protected readonly trackStyle = computed<Record<string, string>>(() => {
|
|
|
+ const dark = this.isDark();
|
|
|
+ if (this.checked()) {
|
|
|
+ return { 'background-color': 'var(--primary)' };
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ 'background-color': dark ? '#9aa3ad' : '#3a4049',
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
protected readonly knobClasses = computed(() => {
|
|
|
const sm = this.size() === 'sm';
|
|
|
const knob = sm ? 'h-4 w-4' : 'h-5 w-5';
|
|
|
const x = this.checked() ? (sm ? 'translate-x-4' : 'translate-x-5') : 'translate-x-0.5';
|
|
|
return [
|
|
|
- 'pointer-events-none inline-flex items-center justify-center rounded-full bg-white shadow-sm',
|
|
|
- /*
|
|
|
- * 圆点加一圈极淡描边:深色主题下白色圆点与深轨道之间缺少过渡时会发虚,
|
|
|
- * 描边让它的边界更实(1px,颜色取页面边框色)。
|
|
|
- */
|
|
|
- 'ring-1 ring-black/5',
|
|
|
+ 'pointer-events-none inline-flex items-center justify-center rounded-full',
|
|
|
'transition-transform duration-200',
|
|
|
knob,
|
|
|
x,
|
|
|
].join(' ');
|
|
|
});
|
|
|
|
|
|
+ /**
|
|
|
+ * 圆点颜色 —— 与轨道**反向**(2026-11-01 用户要求)。
|
|
|
+ *
|
|
|
+ * | 主题 | 关闭(轨道深/浅) | 圆点 |
|
|
|
+ * |---|---|---|
|
|
|
+ * | 浅色 | 深色轨道 | 亮色圆点(#fff) |
|
|
|
+ * | 深色 | 浅色轨道 | 深色圆点(#1e2128) |
|
|
|
+ *
|
|
|
+ * 无论主题如何,圆点与轨道始终「一亮一暗」,保证状态一眼可辨。
|
|
|
+ * 开启态轨道是品牌蓝,圆点统一用白色/近白,与蓝色对比足够。
|
|
|
+ */
|
|
|
+ protected readonly knobStyle = computed<Record<string, string>>(() => {
|
|
|
+ const dark = this.isDark();
|
|
|
+ if (this.checked()) {
|
|
|
+ return { 'background-color': '#ffffff' };
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ 'background-color': dark ? '#1e2128' : '#ffffff',
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
protected toggle(): void {
|
|
|
if (this.disabled() || this.loading()) return;
|
|
|
this.checked.set(!this.checked());
|