From 6e5cb2ca07310f41839133c0fee58274a8a12d4c Mon Sep 17 00:00:00 2001 From: Estrella Pan Date: Mon, 6 Jul 2026 11:15:16 +0200 Subject: [PATCH] feat(webui): Soft Ink universal component set Overlays: ab-modal (adaptive dialog/sheet on headlessui, focus trap, footer slot), promise-based useConfirm + host, ab-tooltip. Forms: ab-field (label/description/error/aria wiring), ab-input component (affixes, clearable, password reveal), ab-select/ab-switch wrappers, ab-segmented underline tabs; ab-setting rebuilt on them with new description/error/required props. Display: ab-tag v2 (semantic marks, ink text, no truncation), ab-status v2 (4 states + label/detail), ab-badge, ab-progress (scaleX fill), ab-container footer slot, accessible animated ab-fold-panel. Feedback: ab-alert (hairline, bold lead word), inverted-ink toast theme, ab-empty, ab-skeleton, ab-list (selection, loading, density), ab-toolbar. All with vitest coverage and Storybook stories; bottom sheet gains footer slot and drops hard-coded overlay/z-index. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01FAxVyRwrY7z5NotTtgnwVs --- webui/src/App.vue | 1 + webui/src/components/ab-container.vue | 22 +- webui/src/components/ab-fold-panel.vue | 90 ++++++- webui/src/components/ab-setting.vue | 71 +++--- webui/src/components/advanced-section.vue | 35 ++- .../basic/__tests__/ab-confirm.test.ts | 49 ++++ .../basic/__tests__/ab-field.test.ts | 58 +++++ .../basic/__tests__/ab-input.test.ts | 54 ++++ .../basic/__tests__/ab-list.test.ts | 59 +++++ .../basic/__tests__/ab-menu.test.ts | 12 +- .../basic/__tests__/ab-modal.test.ts | 69 +++++ .../basic/__tests__/ab-segmented.test.ts | 38 +++ .../components/basic/__tests__/ab-tag.test.ts | 32 +++ webui/src/components/basic/ab-alert.vue | 120 +++++++++ webui/src/components/basic/ab-badge.vue | 57 +++++ .../src/components/basic/ab-bottom-sheet.vue | 22 +- .../src/components/basic/ab-confirm-host.vue | 50 ++++ webui/src/components/basic/ab-empty.vue | 53 ++++ .../src/components/basic/ab-field.stories.ts | 32 +++ webui/src/components/basic/ab-field.vue | 155 +++++++++++ webui/src/components/basic/ab-icon-button.vue | 25 +- .../src/components/basic/ab-input.stories.ts | 32 +++ webui/src/components/basic/ab-input.vue | 240 ++++++++++++++++++ webui/src/components/basic/ab-list.vue | 210 +++++++++++++++ .../src/components/basic/ab-modal.stories.ts | 39 +++ webui/src/components/basic/ab-modal.vue | 197 ++++++++++++++ .../basic/ab-offset-mismatch-dialog.vue | 18 +- webui/src/components/basic/ab-progress.vue | 79 ++++++ .../components/basic/ab-segmented.stories.ts | 30 +++ webui/src/components/basic/ab-segmented.vue | 117 +++++++++ .../src/components/basic/ab-select.stories.ts | 29 +++ webui/src/components/basic/ab-select.vue | 72 ++++++ webui/src/components/basic/ab-skeleton.vue | 118 +++++++++ .../src/components/basic/ab-split-button.vue | 8 +- .../src/components/basic/ab-status.stories.ts | 21 ++ webui/src/components/basic/ab-status.vue | 121 ++++++--- .../src/components/basic/ab-switch.stories.ts | 30 +++ webui/src/components/basic/ab-switch.vue | 36 +++ webui/src/components/basic/ab-tag.stories.ts | 23 ++ webui/src/components/basic/ab-tag.vue | 133 +++++++--- webui/src/components/basic/ab-toolbar.vue | 64 +++++ .../components/basic/ab-tooltip.stories.ts | 27 ++ webui/src/components/basic/ab-tooltip.vue | 34 +++ webui/src/hooks/useConfirm.ts | 43 ++++ webui/src/hooks/useMessage.ts | 30 ++- webui/src/i18n/en.json | 6 +- webui/src/i18n/zh-CN.json | 6 +- webui/types/components.ts | 5 + webui/types/dts/auto-imports.d.ts | 2 + webui/types/dts/components.d.ts | 19 ++ 50 files changed, 2729 insertions(+), 164 deletions(-) create mode 100644 webui/src/components/basic/__tests__/ab-confirm.test.ts create mode 100644 webui/src/components/basic/__tests__/ab-field.test.ts create mode 100644 webui/src/components/basic/__tests__/ab-input.test.ts create mode 100644 webui/src/components/basic/__tests__/ab-list.test.ts create mode 100644 webui/src/components/basic/__tests__/ab-modal.test.ts create mode 100644 webui/src/components/basic/__tests__/ab-segmented.test.ts create mode 100644 webui/src/components/basic/__tests__/ab-tag.test.ts create mode 100644 webui/src/components/basic/ab-alert.vue create mode 100644 webui/src/components/basic/ab-badge.vue create mode 100644 webui/src/components/basic/ab-confirm-host.vue create mode 100644 webui/src/components/basic/ab-empty.vue create mode 100644 webui/src/components/basic/ab-field.stories.ts create mode 100644 webui/src/components/basic/ab-field.vue create mode 100644 webui/src/components/basic/ab-input.stories.ts create mode 100644 webui/src/components/basic/ab-input.vue create mode 100644 webui/src/components/basic/ab-list.vue create mode 100644 webui/src/components/basic/ab-modal.stories.ts create mode 100644 webui/src/components/basic/ab-modal.vue create mode 100644 webui/src/components/basic/ab-progress.vue create mode 100644 webui/src/components/basic/ab-segmented.stories.ts create mode 100644 webui/src/components/basic/ab-segmented.vue create mode 100644 webui/src/components/basic/ab-select.stories.ts create mode 100644 webui/src/components/basic/ab-select.vue create mode 100644 webui/src/components/basic/ab-skeleton.vue create mode 100644 webui/src/components/basic/ab-switch.stories.ts create mode 100644 webui/src/components/basic/ab-switch.vue create mode 100644 webui/src/components/basic/ab-toolbar.vue create mode 100644 webui/src/components/basic/ab-tooltip.stories.ts create mode 100644 webui/src/components/basic/ab-tooltip.vue create mode 100644 webui/src/hooks/useConfirm.ts diff --git a/webui/src/App.vue b/webui/src/App.vue index b2a7a5c4..4d3c1991 100644 --- a/webui/src/App.vue +++ b/webui/src/App.vue @@ -147,6 +147,7 @@ const naiveTheme = computed(() => (isDark.value ? darkTheme : null)); + diff --git a/webui/src/components/ab-container.vue b/webui/src/components/ab-container.vue index 0b369c3c..7e939614 100644 --- a/webui/src/components/ab-container.vue +++ b/webui/src/components/ab-container.vue @@ -2,9 +2,12 @@ withDefaults( defineProps<{ title: string; + /** 去掉 body 内边距(内容自带布局时,如列表/表格贴边) */ + flush?: boolean; }>(), { title: 'title', + flush: false, } ); @@ -16,9 +19,13 @@ withDefaults( -
+
+ +
@@ -57,5 +64,18 @@ withDefaults( font-size: 14px; transition: background-color var(--transition-normal), color var(--transition-normal); + + &--flush { + padding: 0; + } +} + +.container-footer { + display: flex; + justify-content: flex-end; + gap: 8px; + padding: 10px 14px; + border-top: 1px solid var(--color-border); + background: var(--color-surface); } diff --git a/webui/src/components/ab-fold-panel.vue b/webui/src/components/ab-fold-panel.vue index fb679675..999f75ad 100644 --- a/webui/src/components/ab-fold-panel.vue +++ b/webui/src/components/ab-fold-panel.vue @@ -1,7 +1,9 @@ + + + + diff --git a/webui/src/components/basic/ab-badge.vue b/webui/src/components/basic/ab-badge.vue new file mode 100644 index 00000000..49fb5f08 --- /dev/null +++ b/webui/src/components/basic/ab-badge.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/webui/src/components/basic/ab-bottom-sheet.vue b/webui/src/components/basic/ab-bottom-sheet.vue index c1b13dba..4f856251 100644 --- a/webui/src/components/basic/ab-bottom-sheet.vue +++ b/webui/src/components/basic/ab-bottom-sheet.vue @@ -146,6 +146,11 @@ function close() {
+ + + @@ -157,22 +162,22 @@ function close() { .ab-bottom-sheet { position: fixed; inset: 0; - z-index: 100; + z-index: var(--z-modal); display: flex; align-items: flex-end; &__backdrop { position: fixed; inset: 0; - z-index: 100; - background: rgba(0, 0, 0, 0.4); + z-index: var(--z-modal-backdrop); + background: var(--color-overlay); backdrop-filter: blur(4px); } &__container { position: fixed; inset: 0; - z-index: 101; + z-index: var(--z-modal); display: flex; align-items: flex-end; justify-content: center; @@ -181,7 +186,6 @@ function close() { &__panel { position: relative; - z-index: 102; width: 100%; max-width: 640px; max-height: 85dvh; // Use dynamic viewport height for iOS Safari keyboard support @@ -244,5 +248,13 @@ function close() { scroll-margin-bottom: 20px; } } + + &__footer { + display: flex; + justify-content: flex-end; + gap: 8px; + padding: 12px 20px; + border-top: 1px solid var(--color-border); + } } diff --git a/webui/src/components/basic/ab-confirm-host.vue b/webui/src/components/basic/ab-confirm-host.vue new file mode 100644 index 00000000..0eb9c271 --- /dev/null +++ b/webui/src/components/basic/ab-confirm-host.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/webui/src/components/basic/ab-empty.vue b/webui/src/components/basic/ab-empty.vue new file mode 100644 index 00000000..768feba7 --- /dev/null +++ b/webui/src/components/basic/ab-empty.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/webui/src/components/basic/ab-field.stories.ts b/webui/src/components/basic/ab-field.stories.ts new file mode 100644 index 00000000..b29c183e --- /dev/null +++ b/webui/src/components/basic/ab-field.stories.ts @@ -0,0 +1,32 @@ +import type { Meta, StoryObj } from '@storybook/vue3'; + +import AbField from './ab-field.vue'; +import AbInput from './ab-input.vue'; + +const meta: Meta = { + title: 'basic/ab-field', + component: AbField, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +export const Template: Story = { + render: () => ({ + components: { AbField, AbInput }, + template: ` +
+ + + + + + +
`, + }), +}; diff --git a/webui/src/components/basic/ab-field.vue b/webui/src/components/basic/ab-field.vue new file mode 100644 index 00000000..f3cca241 --- /dev/null +++ b/webui/src/components/basic/ab-field.vue @@ -0,0 +1,155 @@ + + + + + + + diff --git a/webui/src/components/basic/ab-icon-button.vue b/webui/src/components/basic/ab-icon-button.vue index 0dc4ed4c..1000e1c9 100644 --- a/webui/src/components/basic/ab-icon-button.vue +++ b/webui/src/components/basic/ab-icon-button.vue @@ -5,10 +5,15 @@ const props = withDefaults( /** 无文本按钮的可访问名称(必填) */ label: string; size?: 'sm' | 'md'; + /** solid:主色填充(用于卡片上的主要动作);round:圆形 */ + variant?: 'ghost' | 'solid'; + round?: boolean; disabled?: boolean; }>(), { size: 'md', + variant: 'ghost', + round: false, disabled: false, } ); @@ -24,7 +29,11 @@ function onClick(event: MouseEvent) { diff --git a/webui/src/components/basic/ab-status.stories.ts b/webui/src/components/basic/ab-status.stories.ts index 5c3feb5c..7f54cd32 100644 --- a/webui/src/components/basic/ab-status.stories.ts +++ b/webui/src/components/basic/ab-status.stories.ts @@ -6,12 +6,19 @@ const meta: Meta = { title: 'basic/ab-status', component: AbStatus, tags: ['autodocs'], + argTypes: { + state: { + control: 'select', + options: ['running', 'stopped', 'paused', 'degraded'], + }, + }, }; export default meta; type Story = StoryObj; export const Template: Story = { + args: { state: 'running', label: 'Running', detail: 'next poll 3 min' }, render: (args) => ({ components: { AbStatus }, setup() { @@ -20,3 +27,17 @@ export const Template: Story = { template: '', }), }; + +export const AllStates: Story = { + render: () => ({ + components: { AbStatus }, + template: ` +
+ + + + + +
`, + }), +}; diff --git a/webui/src/components/basic/ab-status.vue b/webui/src/components/basic/ab-status.vue index 322d2eac..de2d9278 100644 --- a/webui/src/components/basic/ab-status.vue +++ b/webui/src/components/basic/ab-status.vue @@ -1,70 +1,109 @@ diff --git a/webui/src/components/basic/ab-switch.stories.ts b/webui/src/components/basic/ab-switch.stories.ts new file mode 100644 index 00000000..1d1db287 --- /dev/null +++ b/webui/src/components/basic/ab-switch.stories.ts @@ -0,0 +1,30 @@ +import type { Meta, StoryObj } from '@storybook/vue3'; +import { ref } from 'vue'; + +import AbSwitch from './ab-switch.vue'; + +const meta: Meta = { + title: 'basic/ab-switch', + component: AbSwitch, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +export const Template: Story = { + render: () => ({ + components: { AbSwitch }, + setup() { + const on = ref(true); + const off = ref(false); + return { on, off }; + }, + template: ` +
+ + + +
`, + }), +}; diff --git a/webui/src/components/basic/ab-switch.vue b/webui/src/components/basic/ab-switch.vue new file mode 100644 index 00000000..cb0bdcfd --- /dev/null +++ b/webui/src/components/basic/ab-switch.vue @@ -0,0 +1,36 @@ + + + diff --git a/webui/src/components/basic/ab-tag.stories.ts b/webui/src/components/basic/ab-tag.stories.ts index 69021fd4..8c869297 100644 --- a/webui/src/components/basic/ab-tag.stories.ts +++ b/webui/src/components/basic/ab-tag.stories.ts @@ -6,12 +6,19 @@ const meta: Meta = { title: 'basic/ab-tag', component: AbTag, tags: ['autodocs'], + argTypes: { + type: { + control: 'select', + options: ['success', 'warning', 'danger', 'info', 'neutral'], + }, + }, }; export default meta; type Story = StoryObj; export const Template: Story = { + args: { type: 'success', title: 'Airing' }, render: (args) => ({ components: { AbTag }, setup() { @@ -20,3 +27,19 @@ export const Template: Story = { template: '', }), }; + +export const AllTypes: Story = { + render: () => ({ + components: { AbTag }, + template: ` +
+ + + + + + + +
`, + }), +}; diff --git a/webui/src/components/basic/ab-tag.vue b/webui/src/components/basic/ab-tag.vue index 32409419..1c1b850c 100644 --- a/webui/src/components/basic/ab-tag.vue +++ b/webui/src/components/basic/ab-tag.vue @@ -1,67 +1,120 @@ diff --git a/webui/src/components/basic/ab-toolbar.vue b/webui/src/components/basic/ab-toolbar.vue new file mode 100644 index 00000000..7e336f6e --- /dev/null +++ b/webui/src/components/basic/ab-toolbar.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/webui/src/components/basic/ab-tooltip.stories.ts b/webui/src/components/basic/ab-tooltip.stories.ts new file mode 100644 index 00000000..a78157ea --- /dev/null +++ b/webui/src/components/basic/ab-tooltip.stories.ts @@ -0,0 +1,27 @@ +import type { Meta, StoryObj } from '@storybook/vue3'; + +import AbButton from './ab-button.vue'; +import AbTooltip from './ab-tooltip.vue'; + +const meta: Meta = { + title: 'basic/ab-tooltip', + component: AbTooltip, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +export const Template: Story = { + args: { content: 'Refresh all RSS feeds' }, + render: (args) => ({ + components: { AbTooltip, AbButton }, + setup() { + return { args }; + }, + template: ` + + Hover me + `, + }), +}; diff --git a/webui/src/components/basic/ab-tooltip.vue b/webui/src/components/basic/ab-tooltip.vue new file mode 100644 index 00000000..2b4c47f1 --- /dev/null +++ b/webui/src/components/basic/ab-tooltip.vue @@ -0,0 +1,34 @@ + + + diff --git a/webui/src/hooks/useConfirm.ts b/webui/src/hooks/useConfirm.ts new file mode 100644 index 00000000..39ae4a0b --- /dev/null +++ b/webui/src/hooks/useConfirm.ts @@ -0,0 +1,43 @@ +import { reactive } from 'vue'; + +export interface ConfirmOptions { + title: string; + body?: string; + confirmText?: string; + cancelText?: string; + /** 破坏性操作:确认按钮使用 danger 变体 */ + danger?: boolean; +} + +interface ConfirmRequest extends ConfirmOptions { + resolve: (value: boolean) => void; +} + +// 模块级单例状态:ab-confirm-host(挂载于 App.vue)消费并渲染 +const state = reactive<{ current: ConfirmRequest | null }>({ current: null }); + +/** + * Promise 式确认对话框,替代 NPopconfirm / 手搭确认弹窗: + * `if (await confirm({ title, danger: true })) { … }` + */ +export function useConfirm() { + function confirm(options: ConfirmOptions): Promise { + return new Promise((resolve) => { + // 已有未决确认时,先取消旧的,避免悬挂的 Promise + state.current?.resolve(false); + state.current = { ...options, resolve }; + }); + } + + return { confirm }; +} + +/** 仅供 ab-confirm-host 内部使用 */ +export function useConfirmState() { + function settle(value: boolean) { + state.current?.resolve(value); + state.current = null; + } + + return { state, settle }; +} diff --git a/webui/src/hooks/useMessage.ts b/webui/src/hooks/useMessage.ts index 1a8738bc..bad2151f 100644 --- a/webui/src/hooks/useMessage.ts +++ b/webui/src/hooks/useMessage.ts @@ -1,7 +1,35 @@ import { createDiscreteApi } from 'naive-ui'; +import type { GlobalThemeOverrides } from 'naive-ui'; import { createSharedComposable } from '@vueuse/core'; +// Soft Ink toast:反色墨条 —— 所有类型同一深底,语义色只出现在图标上。 +// (组件级 token 可以用 CSS 变量;只有 common 必须是字面量) +const toastTheme: GlobalThemeOverrides = { + Message: { + color: 'var(--color-text)', + colorInfo: 'var(--color-text)', + colorSuccess: 'var(--color-text)', + colorError: 'var(--color-text)', + colorWarning: 'var(--color-text)', + colorLoading: 'var(--color-text)', + textColor: 'var(--color-bg)', + textColorInfo: 'var(--color-bg)', + textColorSuccess: 'var(--color-bg)', + textColorError: 'var(--color-bg)', + textColorWarning: 'var(--color-bg)', + textColorLoading: 'var(--color-bg)', + iconColorInfo: 'var(--color-primary-light)', + iconColorSuccess: 'var(--color-success)', + iconColorError: 'var(--color-danger)', + iconColorWarning: 'var(--color-warning)', + borderRadius: 'var(--radius-md)', + boxShadow: 'var(--shadow-md)', + }, +}; + export const useMessage = createSharedComposable(() => { - const { message } = createDiscreteApi(['message']); + const { message } = createDiscreteApi(['message'], { + configProviderProps: { themeOverrides: toastTheme }, + }); return message; }); diff --git a/webui/src/i18n/en.json b/webui/src/i18n/en.json index 6688a215..533a2374 100644 --- a/webui/src/i18n/en.json +++ b/webui/src/i18n/en.json @@ -484,7 +484,11 @@ "select": "Select", "selectAll": "Select All", "items": "items", - "moreActions": "More actions" + "moreActions": "More actions", + "copy": "Copy", + "clear": "Clear", + "toggle_password": "Show or hide password", + "empty": "Nothing here yet" }, "theme": { "light": "Light", diff --git a/webui/src/i18n/zh-CN.json b/webui/src/i18n/zh-CN.json index 499cdb15..155e8d30 100644 --- a/webui/src/i18n/zh-CN.json +++ b/webui/src/i18n/zh-CN.json @@ -484,7 +484,11 @@ "select": "选择", "selectAll": "全选", "items": "项", - "moreActions": "更多操作" + "moreActions": "更多操作", + "copy": "复制", + "clear": "清空", + "toggle_password": "显示或隐藏密码", + "empty": "暂无内容" }, "theme": { "light": "浅色", diff --git a/webui/types/components.ts b/webui/types/components.ts index 5f3af19d..e922de43 100644 --- a/webui/types/components.ts +++ b/webui/types/components.ts @@ -11,6 +11,11 @@ export interface AbSettingProps { css?: string; prop?: any; bottomLine?: boolean; + /** 字段说明文字(显示在标签下方) */ + description?: string; + /** 校验错误文案(显示为错误态) */ + error?: string; + required?: boolean; } export type SettingItem = AbSettingProps & { diff --git a/webui/types/dts/auto-imports.d.ts b/webui/types/dts/auto-imports.d.ts index ebad9a54..a8cba333 100644 --- a/webui/types/dts/auto-imports.d.ts +++ b/webui/types/dts/auto-imports.d.ts @@ -105,6 +105,8 @@ declare global { const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints'] const useClipboard: typeof import('@vueuse/core')['useClipboard'] const useConfigStore: typeof import('../../src/store/config')['useConfigStore'] + const useConfirm: typeof import('../../src/hooks/useConfirm')['useConfirm'] + const useConfirmState: typeof import('../../src/hooks/useConfirm')['useConfirmState'] const useCssModule: typeof import('vue')['useCssModule'] const useCssVars: typeof import('vue')['useCssVars'] const useDarkMode: typeof import('../../src/hooks/useDarkMode')['useDarkMode'] diff --git a/webui/types/dts/components.d.ts b/webui/types/dts/components.d.ts index 15b46209..f3f915f2 100644 --- a/webui/types/dts/components.d.ts +++ b/webui/types/dts/components.d.ts @@ -12,21 +12,33 @@ declare module '@vue/runtime-core' { AbAdaptiveModal: typeof import('./../../src/components/basic/ab-adaptive-modal.vue')['default'] AbAdd: typeof import('./../../src/components/basic/ab-add.vue')['default'] AbAddRss: typeof import('./../../src/components/ab-add-rss.vue')['default'] + AbAlert: typeof import('./../../src/components/basic/ab-alert.vue')['default'] + AbBadge: typeof import('./../../src/components/basic/ab-badge.vue')['default'] AbBangumiCard: typeof import('./../../src/components/ab-bangumi-card.vue')['default'] AbBottomSheet: typeof import('./../../src/components/basic/ab-bottom-sheet.vue')['default'] + AbButton: typeof import('./../../src/components/basic/ab-button.vue')['default'] AbButtonMulti: typeof import('./../../src/components/basic/ab-button-multi.vue')['default'] AbChangeAccount: typeof import('./../../src/components/ab-change-account.vue')['default'] + AbConfirmHost: typeof import('./../../src/components/basic/ab-confirm-host.vue')['default'] AbContainer: typeof import('./../../src/components/ab-container.vue')['default'] AbDataList: typeof import('./../../src/components/basic/ab-data-list.vue')['default'] AbEditRule: typeof import('./../../src/components/ab-edit-rule.vue')['default'] + AbEmpty: typeof import('./../../src/components/basic/ab-empty.vue')['default'] + AbField: typeof import('./../../src/components/basic/ab-field.vue')['default'] AbFoldPanel: typeof import('./../../src/components/ab-fold-panel.vue')['default'] + AbIconButton: typeof import('./../../src/components/basic/ab-icon-button.vue')['default'] AbImage: typeof import('./../../src/components/ab-image.vue')['default'] + AbInput: typeof import('./../../src/components/basic/ab-input.vue')['default'] AbLabel: typeof import('./../../src/components/ab-label.vue')['default'] + AbList: typeof import('./../../src/components/basic/ab-list.vue')['default'] + AbMenu: typeof import('./../../src/components/basic/ab-menu.vue')['default'] AbMobileNav: typeof import('./../../src/components/layout/ab-mobile-nav.vue')['default'] + AbModal: typeof import('./../../src/components/basic/ab-modal.vue')['default'] AbNotificationCenter: typeof import('./../../src/components/layout/ab-notification-center.vue')['default'] AbOffsetMismatchDialog: typeof import('./../../src/components/basic/ab-offset-mismatch-dialog.vue')['default'] AbPageTitle: typeof import('./../../src/components/basic/ab-page-title.vue')['default'] AbPopup: typeof import('./../../src/components/ab-popup.vue')['default'] + AbProgress: typeof import('./../../src/components/basic/ab-progress.vue')['default'] AbPullRefresh: typeof import('./../../src/components/basic/ab-pull-refresh.vue')['default'] AbRule: typeof import('./../../src/components/ab-rule.vue')['default'] AbSearch: typeof import('./../../src/components/basic/ab-search.vue')['default'] @@ -35,12 +47,19 @@ declare module '@vue/runtime-core' { AbSearchConfirm: typeof import('./../../src/components/search/ab-search-confirm.vue')['default'] AbSearchFilters: typeof import('./../../src/components/search/ab-search-filters.vue')['default'] AbSearchModal: typeof import('./../../src/components/search/ab-search-modal.vue')['default'] + AbSegmented: typeof import('./../../src/components/basic/ab-segmented.vue')['default'] + AbSelect: typeof import('./../../src/components/basic/ab-select.vue')['default'] AbSetting: typeof import('./../../src/components/ab-setting.vue')['default'] AbSidebar: typeof import('./../../src/components/layout/ab-sidebar.vue')['default'] + AbSkeleton: typeof import('./../../src/components/basic/ab-skeleton.vue')['default'] + AbSplitButton: typeof import('./../../src/components/basic/ab-split-button.vue')['default'] AbStatus: typeof import('./../../src/components/basic/ab-status.vue')['default'] AbStatusBar: typeof import('./../../src/components/ab-status-bar.vue')['default'] AbSwipeContainer: typeof import('./../../src/components/basic/ab-swipe-container.vue')['default'] + AbSwitch: typeof import('./../../src/components/basic/ab-switch.vue')['default'] AbTag: typeof import('./../../src/components/basic/ab-tag.vue')['default'] + AbToolbar: typeof import('./../../src/components/basic/ab-toolbar.vue')['default'] + AbTooltip: typeof import('./../../src/components/basic/ab-tooltip.vue')['default'] AbTopbar: typeof import('./../../src/components/layout/ab-topbar.vue')['default'] AdvancedSection: typeof import('./../../src/components/advanced-section.vue')['default'] BangumiFilterField: typeof import('./../../src/components/bangumi-filter-field.vue')['default']