Files
Serendipity edbf78f236 feat: 初始化 Halo 暗色模式插件
- Halo Plugin 后端(Java/Gradle),含 DarkModePlugin 主类和测试
- Vue 3 + TypeScript 前端 UI,包含主题切换组件和设置页面
- 暗色模式 CSS 变量和覆盖样式(布局/编辑器/表单/滚动条等)
- 设计文档和调查文档
- Halo 插件/主题开发 Agent Skills

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 21:03:00 +08:00

11 KiB

UI Components

Halo provides two layers of UI primitives for plugin frontends:

  1. Base component library (@halo-dev/components) — install and import explicitly
  2. Business components & directives — globally registered, use directly without import

Full base component docs: https://halo-ui-components.halo-run.workers.dev

For forms, see ui-forms.md — Halo uses FormKit (globally registered) with many custom inputs.

Official Docs Routing

Component APIs change across Halo versions. Treat the lists below as common shortcuts, not an exhaustive API reference.

Need Official docs
Base components from @halo-dev/components https://halo-ui-components.halo-run.workers.dev
Business component index https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/index.md
AttachmentSelectorModal https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/attachment-selector-modal.md
AttachmentFileTypeIcon https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/attachment-file-type-icon.md
AnnotationsForm https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/annotations-form.md
FilterDropdown / FilterCleanButton https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/filter-dropdown.md
HasPermission https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/has-permission.md
PluginDetailModal https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/plugin-detail-modal.md
SearchInput https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/search-input.md
UppyUpload https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/uppy-upload.md
VCodemirror https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/v-codemirror.md
v-permission https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/v-permission.md
v-tooltip https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/ui/components/v-tooltip.md

Base Component Library

Install:

pnpm install @halo-dev/components

Import and use:

<script lang="ts" setup>
import { ref } from "vue";
import { VButton, VModal, VCard } from "@halo-dev/components";
const visible = ref(false);
</script>

<template>
  <VButton type="secondary" @click="visible = true">Open</VButton>
  <VModal v-if="visible" @close="visible = false" title="Title">
    <VCard>Content</VCard>
  </VModal>
</template>

Common exported components from @halo-dev/components:

Component Purpose
VAlert Alert banner
VAvatar / VAvatarGroup User avatar(s)
VButton Button with variants: default, primary, secondary, danger, ghost
VCard Card container
VDescription / VDescriptionItem Key-value description list
VDialog + Dialog (manager) Dialog with imperative API
VDropdownDivider / VDropdownItem Dropdown menu items
VEmpty Empty state placeholder
VEntity / VEntityContainer / VEntityField Entity list item layout
VLoading Loading spinner/overlay
VMenu / VMenuItem / VMenuLabel Menu navigation
VModal Modal with v-model:visible
VPagination Pagination control
VPageHeader Page header with back button/title/actions
VSpace Flex spacing layout
VStatusDot Status indicator dot
VSwitch Toggle switch
VTabbar / VTabs / VTabItem Tab navigation
VTag Colored tag/badge
Toast (manager) Toast notification imperative API
VTooltipComponent / vTooltip Tooltip component/directive

Business Components (Globally Registered)

These are available without import in any plugin Vue component.

VCodemirror

Code editor.

<VCodemirror v-model="value" height="300px" language="yaml" />
Prop Type Default Description
modelValue string "" Binding value
height string "auto" Editor height
language string "yaml" Language: yaml, html, js, css, json
extensions array [] Codemirror extensions

AttachmentSelectorModal

Attachment picker modal (Console only).

<script setup>
const visible = ref(false);
function onSelect(attachments) {
  console.log(attachments); // AttachmentLike[]
}
</script>

<template>
  <VButton @click="visible = true">Select</VButton>
  <AttachmentSelectorModal
    v-if="visible"
    @close="visible = false"
    :accepts="['image/*']"
    :min="1"
    :max="5"
    @select="onSelect"
  />
</template>
Prop Type Default Description
visible boolean false Controlled visibility
accepts string[] ["*/*"] Accepted MIME types
min / max number Min/max selection count

UppyUpload

File upload component.

<UppyUpload
  endpoint="/apis/api.console.halo.run/v1alpha1/attachments/upload"
  :meta="{ policyName, groupName }"
  @uploaded="onUploaded"
  @error="onError"
/>
Prop Type Default Description
endpoint string required Upload API endpoint
meta Record<string, unknown> Extra metadata sent with upload
autoProceed boolean false Auto-upload on select
method string "post" HTTP method

SearchInput

Search input that only triggers on Enter (not while typing).

<SearchInput v-model="keyword" placeholder="Search..." />

AttachmentFileTypeIcon

File-type icon for attachment/file lists.

<AttachmentFileTypeIcon fileName="example.png" :display-ext="true" />

AnnotationsForm

Renders the Annotations form for a given Extension group/kind.

<script setup>
const formRef = ref();

async function handleSubmit() {
  formRef.value?.handleSubmit();
  await nextTick();
  const { customAnnotations, annotations, customFormInvalid, specFormInvalid } =
    formRef.value || {};
  if (customFormInvalid || specFormInvalid) return;
  const merged = { ...annotations, ...customAnnotations };
  // ...submit merged
}
</script>

<template>
  <AnnotationsForm ref="formRef" :value="currentAnnotations" kind="Post" group="content.halo.run" />
  <VButton @click="handleSubmit">Save</VButton>
</template>

FilterDropdown / FilterCleanButton

Generic filter dropdown and clear button for list pages.

<FilterDropdown
  v-model="sortValue"
  label="Sort"
  :items="[
    { label: 'Newest', value: 'creationTimestamp,desc' },
    { label: 'Oldest', value: 'creationTimestamp,asc' },
  ]"
/>

<FilterCleanButton @click="resetFilters" />

PluginDetailModal

Open a plugin's detail/settings modal inline.

<PluginDetailModal v-if="visible" name="my-plugin" @close="visible = false" />
Prop Type Description
name string Plugin metadata.name

HasPermission

Render content only when the user has the required permissions.

<HasPermission :permissions="['system:posts:manage']">
  <VButton type="danger">Delete</VButton>
</HasPermission>

Directives (Globally Registered)

v-permission

Conditionally render based on permissions.

<VButton type="danger" v-permission="['system:posts:manage']">Delete</VButton>

Equivalent component: <HasPermission :permissions="['system:posts:manage']">...</HasPermission>

v-tooltip

Add tooltip to any element.

<IconDeleteBin v-tooltip="'Delete this item'" />