重命名CubeListPager和CubeListToolbarSearch
Yann authored at 2025-07-28 22:53:20 Yann committed at 2025-07-28 23:04:14
4.13 KiB
cube-front
<script setup lang="ts" generic="T extends { id: string; label: string; icon: string }">
import { ref } from 'vue';

/**
 * SwitcherDropdown — 通用图标+下拉切换组件
 *
 * 用途:主题切换、布局切换,或任何需要"小图标 → 下拉选单"形式的场景。
 *
 * Props:
 *   options     — 选项列表,每项需要 { id, label, icon }
 *   modelValue  — 当前选中项的 id(支持 v-model)
 *   title       — 按钮 tooltip 文字(可选)
 *
 * Slots:
 *   #icon          — 自定义触发按钮内的图标(不覆盖则使用默认 SVG)
 *   #option        — 自定义下拉选项内容(slot props: { option, active })
 *
 * Emits:
 *   update:modelValue — 切换时触发,携带新 id
 */

const props = withDefaults(
  defineProps<{
    options: T[];
    modelValue: string;
    title?: string;
  }>(),
  { title: '' },
);

const emit = defineEmits<{
  'update:modelValue': [id: string];
}>();

const open = ref(false);

function select(id: string) {
  emit('update:modelValue', id);
  open.value = false;
}

function toggle() {
  open.value = !open.value;
}

function close() {
  open.value = false;
}

// 暴露给父组件
defineExpose({ close });
</script>

<template>
  <!-- @click.stop 防止冒泡到外层关闭逻辑 -->
  <div class="sw-drop" @click.stop>
    <!-- 触发按钮 -->
    <button class="tn-btn sw-trigger" :title="title" @click="toggle">
      <slot name="icon">
        <!-- 默认:调色板图标 -->
        <svg
          width="15"
          height="15"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
        >
          <circle cx="12" cy="12" r="10" />
          <circle cx="12" cy="12" r="4" />
        </svg>
      </slot>
    </button>

    <!-- 下拉菜单 -->
    <Transition name="sw-fade">
      <div v-if="open" class="sw-menu">
        <div
          v-for="opt in options"
          :key="opt.id"
          class="sw-item"
          :class="{ active: modelValue === opt.id }"
          @click="select(opt.id)"
        >
          <!-- 支持通过 #option 插槽自定义每行内容 -->
          <slot name="option" :option="opt" :active="modelValue === opt.id">
            <span class="sw-i-icon">{{ opt.icon }}</span>
            <span class="sw-i-label">{{ opt.label }}</span>
            <svg
              v-if="modelValue === opt.id"
              class="sw-i-check"
              width="13"
              height="13"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2.5"
            >
              <polyline points="20 6 9 17 4 12" />
            </svg>
          </slot>
        </div>
      </div>
    </Transition>
  </div>
</template>

<style lang="scss" scoped>
.sw-drop {
  position: relative;
}

/* 触发按钮——复用 .tn-btn 样式,附加颜色覆盖 */
.sw-trigger {
  color: var(--tn-t, #74b898) !important;

  &:hover {
    background: rgba(255, 255, 255, 0.12) !important;
    color: var(--tn-ac, #4ec685) !important;
  }
}

/* 下拉菜单面板 */
.sw-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  background: var(--card, #ffffff);
  border: 1px solid var(--bd, #e0e6da);
  border-radius: 10px;
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(25, 36, 24, 0.12));
  min-width: 148px;
  overflow: hidden;
  z-index: 300;
  padding: 4px;
}

/* 选项行 */
.sw-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-radius: 7px;
  cursor: pointer;
  color: var(--t2, #4a604a);
  font-size: 13px;
  font-weight: 500;
  transition:
    background 0.12s,
    color 0.12s;

  &:hover {
    background: var(--ac-l, #e8f5ee);
    color: var(--ac, #1d7040);
  }

  &.active {
    color: var(--ac, #1d7040);
    font-weight: 600;
  }
}

.sw-i-icon {
  font-size: 14px;
  flex-shrink: 0;
}

.sw-i-label {
  flex: 1;
}

.sw-i-check {
  color: var(--ac, #1d7040);
  flex-shrink: 0;
}

/* 下拉动画 */
.sw-fade-enter-active,
.sw-fade-leave-active {
  transition:
    opacity 0.12s,
    transform 0.12s;
}

.sw-fade-enter-from,
.sw-fade-leave-to {
  opacity: 0;
  transform: translateY(-4px);
}
</style>