Skip to content

feat: popover#7

Merged
vaebe merged 13 commits intomainfrom
feat-popover-new
Nov 13, 2025
Merged

feat: popover#7
vaebe merged 13 commits intomainfrom
feat-popover-new

Conversation

@vaebe
Copy link
Owner

@vaebe vaebe commented Nov 13, 2025

Popover 组件

📦 涉及文件

  • packages/ccui/ui/popover/src/popover-types.ts - 类型定义
  • packages/ccui/ui/popover/src/popover.tsx - 组件实现
  • packages/ccui/ui/popover/src/popover.scss - 样式文件
  • packages/ccui/ui/popover/test/popover.test.ts - 测试用例
  • packages/docs/components/popover/index.md - 组件文档

✨ 新增功能

  • 触发方式扩展: 新增 contextmenu 右键菜单触发
  • 虚拟触发: 支持 virtualTriggeringvirtualRef 属性,实现触发元素与展示内容分离
  • 自动关闭: 新增 autoClose 属性,支持定时自动关闭
  • 动画系统: 支持自定义 transition 动画,新增动画生命周期事件
  • 传送功能: 新增 teleported 属性,支持将弹出框传送到 body
  • 键盘支持: 支持 triggerKeys 自定义键盘触发按键
  • 更多配置: 新增 tabindexpersistent 等配置项

Summary by CodeRabbit

  • New Features

    • Introduced Popover component with multiple trigger modes, placements, light/dark themes, auto-close, animations, and teleport support.
  • Documentation

    • Added comprehensive Popover docs with examples and API reference.
    • Removed legacy tooltip development spec.
  • Tests

    • Added extensive Popover test coverage for interactions and features.
  • Chores

    • Updated test scripts to add watch mode and explicit run command.
    • Added .codeflicker to .gitignore.

@coderabbitai
Copy link

coderabbitai bot commented Nov 13, 2025

Walkthrough

Adds a new CCUI Popover component (implementation, types, styles), a Vue plugin entrypoint, tests and docs, updates test scripts, adds a .gitignore rule, and removes an obsolete tooltip spec.

Changes

Cohort / File(s) Summary
Test Configuration
packages/ccui/package.json
Change test script from vitest to vitest run and add test:watch (vitest)
Plugin Entry Point
packages/ccui/ui/popover/index.ts
Export Popover as named export and default plugin object; add install(app: App) to both plugin object and Popover for global registration
Component Types
packages/ccui/ui/popover/src/popover-types.ts
New typed declarations: PopoverPlacement, PopoverEffect, PopoverTrigger, exported popoverProps, and derived PopoverProps
Component Styling
packages/ccui/ui/popover/src/popover.scss
New SCSS: base layout, dark/light themes, arrow directional rules, transitions/Element Plus compatibility, disabled state, responsive rules
Component Implementation
packages/ccui/ui/popover/src/popover.tsx
New Vue 3 component CPopover (default) using @floating-ui/vue; supports controlled/uncontrolled visibility, triggers (click/hover/focus/contextmenu/manual), virtual trigger, showAfter/hideAfter, auto-close, arrow, teleport, Transition hooks, emits lifecycle events, and exposes hide
Component Tests
packages/ccui/ui/popover/test/popover.test.ts
New comprehensive Vitest suite covering rendering, triggers, timing, keyboard and accessibility behavior, virtual trigger, teleport, animation lifecycle events, and instance methods
Documentation
packages/docs/components/popover/index.md
New docs page with usage examples, demos (various triggers, slots, virtual, autoclose), API reference (props, events, slots, exposes)
Ignored Files
.gitignore
Add rule to ignore .codeflicker files (preserve existing .claude)
Removed Spec
.codeflicker/specs/tooltip-component-development.md
Delete obsolete tooltip development/specification document

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Trigger
    participant Popover
    participant FloatingUI
    participant Body

    User->>Trigger: interact (click/hover/focus/contextmenu)
    Trigger->>Popover: dispatch event
    Popover->>Popover: emit before-show
    Popover->>FloatingUI: request position (placement/arrow/offset)
    FloatingUI-->>Popover: position + middlewareData
    Popover->>Body: teleport popper (if enabled)
    Popover->>Popover: render inside Transition
    Popover->>Popover: emit show / after-enter

    Note right of Popover: hide via escape, outside click, timeout, or trigger
    User->>Popover: outside action / timeout
    Popover->>Popover: emit before-hide
    Popover->>Popover: Transition leave
    Popover->>Body: unmount popper
    Popover->>Popover: emit hide / after-leave
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay special attention to:
    • packages/ccui/ui/popover/src/popover.tsx (event/timer/virtual-trigger logic, Floating UI middleware, teardown)
    • packages/ccui/ui/popover/src/popover-types.ts (prop typings and defaults)
    • packages/ccui/ui/popover/test/popover.test.ts (test correctness and timing reliance on fake timers)
    • packages/ccui/ui/popover/src/popover.scss (scoping, BEM classes, responsive behaviors)

Poem

I nibble code and hop with glee, 🐇
A popper blooms where clicks may be,
Arrows point and themes delight,
Tests and docs tucked in at night,
Small paws push—release takes flight. ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: popover' clearly summarizes the main change: adding a new Popover component with comprehensive functionality including types, styles, tests, and documentation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-popover-new

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/docs/components/popover/index.md (1)

547-575: Align trigger-keys docs with actual keyboard values

After fixing runtime, the documented default should reflect the real KeyboardEvent.key space value (and ideally mention that 'Space'/'Spacebar' are also accepted). Please update the table text accordingly; otherwise consumers will copy 'Space' and wonder why it fails.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a406074 and c809723.

📒 Files selected for processing (7)
  • packages/ccui/package.json (1 hunks)
  • packages/ccui/ui/popover/index.ts (1 hunks)
  • packages/ccui/ui/popover/src/popover-types.ts (1 hunks)
  • packages/ccui/ui/popover/src/popover.scss (1 hunks)
  • packages/ccui/ui/popover/src/popover.tsx (1 hunks)
  • packages/ccui/ui/popover/test/popover.test.ts (1 hunks)
  • packages/docs/components/popover/index.md (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/ccui/ui/popover/src/popover.tsx (2)
packages/ccui/ui/popover/src/popover-types.ts (1)
  • popoverProps (21-126)
packages/ccui/ui/shared/hooks/use-namespace.ts (1)
  • useNamespace (30-44)
🔇 Additional comments (1)
packages/ccui/ui/popover/test/popover.test.ts (1)

318-320: Update ARIA assertion for unique popper id

Once each instance gets its own ID, this expectation must reflect the dynamic value (e.g., assert it starts with ccui-popover__popper-). Otherwise the test will fail and we keep encouraging duplicate IDs.

Use a pattern match such as toMatch(/^ccui-popover__popper-/) (or read the actual aria-describedby value from the rendered node and assert equality with the popper’s id attribute) so the test tracks the new runtime behavior.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/ccui/ui/popover/src/popover.tsx (2)

12-12: The persistent prop is defined but never used.

The persistent prop is declared in the component props but is not referenced anywhere in the implementation. Consider either implementing the intended functionality or removing the prop definition if it's not needed.


338-340: Document XSS risks for rawContent feature.

Using innerHTML with the rawContent prop bypasses Vue's built-in XSS protection. While this may be intentional for advanced use cases, ensure the documentation clearly warns users that props.content must be from a trusted source when rawContent is enabled, as it can execute arbitrary HTML/scripts.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c809723 and 4de621d.

📒 Files selected for processing (5)
  • .codeflicker/specs/tooltip-component-development.md (0 hunks)
  • .gitignore (1 hunks)
  • packages/ccui/ui/popover/src/popover-types.ts (1 hunks)
  • packages/ccui/ui/popover/src/popover.tsx (1 hunks)
  • packages/ccui/ui/popover/test/popover.test.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • .codeflicker/specs/tooltip-component-development.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ccui/ui/popover/test/popover.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/ccui/ui/popover/src/popover.tsx (2)
packages/ccui/ui/popover/src/popover-types.ts (2)
  • popoverProps (21-126)
  • PopoverProps (128-128)
packages/ccui/ui/shared/hooks/use-namespace.ts (1)
  • useNamespace (30-44)
🔇 Additional comments (12)
.gitignore (1)

40-41: LGTM!

The addition of .claude and .codeflicker to the ignore list is appropriate for tool-specific files.

packages/ccui/ui/popover/src/popover-types.ts (2)

3-19: LGTM!

The type definitions for PopoverPlacement, PopoverEffect, and PopoverTrigger are comprehensive and well-structured. The inclusion of contextmenu trigger aligns with the PR objectives.


123-125: LGTM! Keyboard trigger keys fixed.

The default triggerKeys now correctly uses ' ' (space character) instead of 'Space', which matches the browser's KeyboardEvent.key value. This resolves the previous keyboard activation issue.

packages/ccui/ui/popover/src/popover.tsx (9)

1-14: LGTM!

Component structure, imports, and event declarations are well-organized. The comprehensive emit events support both lifecycle (before-show, show, before-hide, hide) and animation hooks (before-enter, after-enter, before-leave, after-leave).


15-55: LGTM!

The state management is well-structured:

  • Proper separation of controlled/uncontrolled visibility modes
  • Virtual trigger support correctly delegates to virtualRef when enabled
  • Floating UI integration uses appropriate middleware (offset, flip, shift, arrow)

57-89: LGTM!

The arrow positioning logic correctly calculates styles based on Floating UI's middleware data, and the timer cleanup helper properly handles all timer types.


91-136: LGTM!

The show/hide logic is well-implemented:

  • Properly handles both controlled and uncontrolled modes
  • Correct event emission sequence
  • autoClose timer is set after the popover is shown
  • Uses nextTick to ensure positioning updates happen after DOM updates

179-207: LGTM! Keyboard handling properly normalized.

The keyboard event handling now includes proper normalization:

  • The normalizeTriggerKey helper maps space key aliases (' ', 'space', 'spacebar') to a canonical form
  • Both the event key and prop trigger keys are normalized before comparison
  • This resolves the previous review concern about inconsistent space key handling

138-178: LGTM!

Event handlers are well-implemented:

  • Each handler appropriately guards on trigger type
  • Click and contextmenu handlers correctly toggle visibility
  • Context menu handler properly prevents the default browser context menu
  • enterable support allows users to hover over the popover content without it closing

Also applies to: 208-217


221-316: LGTM!

Lifecycle management is solid:

  • autoUpdate is properly set up and cleaned up
  • Virtual trigger event listeners are correctly managed in both mount/unmount
  • Document-level listeners (outside clicks, Escape key) are dynamically added/removed based on visibility
  • All cleanup is comprehensive in onUnmounted

318-335: LGTM!

The render helpers are well-structured:

  • renderArrow conditionally renders with proper styling
  • renderHeader prioritizes slots over props
  • Exposing the hide method allows programmatic control from parent components

Also applies to: 348-350


352-426: LGTM!

The render function is well-structured:

  • Correctly handles both regular and virtual triggering modes
  • Conditionally applies event handlers only when not using virtual triggers
  • Properly integrates Transition for animations with lifecycle hooks
  • Conditionally uses Teleport based on the teleported prop
  • The enterable prop correctly controls pointer events on the popper

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
packages/ccui/ui/popover/src/popover.tsx (2)

297-303: Watcher only handles visible becoming true.

The watch on props.visible only updates positioning when the value becomes truthy. While the actualVisible watcher handles cleanup on hide, explicitly handling both transitions would make the logic clearer.

    watch(() => props.visible, (newVal) => {
-      if (newVal !== undefined && newVal) {
+      if (newVal !== undefined) {
        nextTick(() => {
          update()
        })
      }
    })

29-35: Validate virtualRef when virtualTriggering is enabled.

If virtualTriggering is true but virtualRef is not provided, actualTriggerRef returns undefined, causing positioning to fail silently. Add validation or a dev-mode warning.

    const actualTriggerRef = computed(() => {
      if (props.virtualTriggering && props.virtualRef) {
        return props.virtualRef
      }
+      if (props.virtualTriggering && !props.virtualRef) {
+        console.warn('[CPopover] virtualTriggering is enabled but virtualRef is not provided')
+      }
      return triggerRef.value
    })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4de621d and 71fe2ce.

📒 Files selected for processing (1)
  • packages/ccui/ui/popover/src/popover.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/ccui/ui/popover/src/popover.tsx (2)
packages/ccui/ui/popover/src/popover-types.ts (2)
  • popoverProps (21-126)
  • PopoverProps (128-128)
packages/ccui/ui/shared/hooks/use-namespace.ts (1)
  • useNamespace (30-44)
🔇 Additional comments (6)
packages/ccui/ui/popover/src/popover.tsx (6)

104-110: Verify hideAfter is intentionally skipped for click triggers.

Line 104 skips the hideAfter delay when trigger === 'click', causing immediate hiding. Ensure this behavior is intentional and documented, as users might expect consistent delay behavior across all trigger types.


304-319: LGTM! Document event listeners are managed correctly.

The watcher properly adds/removes document-level event listeners based on visibility, uses capture phase for correct outside-click detection, and ensures cleanup when the popover is hidden.


8-8: LGTM! Unique ID generation addresses previous review concerns.

The counter-based approach generates a unique popperId for each popover instance, fixing the duplicate ID issue flagged in earlier reviews. The ID is correctly used for both the popper element and aria-describedby.

Also applies to: 16-16, 380-380, 404-404


182-210: LGTM! Key normalization addresses previous review concerns.

The normalizeTriggerKey helper correctly handles space key aliases (' ', 'space', 'spacebar') and normalizes both the event key and triggerKeys prop before comparison, fixing the flaky space toggling issue from earlier reviews.


79-92: LGTM! Timer management is well-implemented.

The clearTimers helper prevents timer conflicts by clearing all pending timers before new operations. The doShow and doHide functions correctly manage delays and the autoClose feature, ensuring cleanup and proper sequencing.

Also applies to: 112-139


355-429: LGTM! Render function correctly handles all popover modes.

The render function properly distinguishes between virtual and non-virtual triggering modes, conditionally attaches events, emits animation lifecycle hooks, and supports both teleported and inline rendering. The structure is clear and maintainable.

computed(() => actualTriggerRef.value),
popperRef,
{
placement: props.placement as any,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove as any type assertion to preserve type safety.

The as any assertion bypasses TypeScript checking. Import the correct Placement type from @floating-ui/vue and ensure PopoverPlacement aligns with it, or use a proper type assertion.

-        placement: props.placement as any,
+        placement: props.placement,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
placement: props.placement as any,
placement: props.placement,
🤖 Prompt for AI Agents
In packages/ccui/ui/popover/src/popover.tsx around line 50, replace the unsafe
"as any" on placement with a proper type: import the Placement type from
"@floating-ui/vue", update or alias your PopoverPlacement to be compatible with
that Placement (or cast to Placement explicitly), then remove "as any" and
ensure the placement prop is typed/converted to Placement so TypeScript checking
is preserved and the file compiles.

@vaebe vaebe merged commit ce8709a into main Nov 13, 2025
2 checks passed
@vaebe vaebe deleted the feat-popover-new branch November 13, 2025 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant