Skip to content

Conversation

@vaebe
Copy link
Owner

@vaebe vaebe commented Nov 17, 2025

PR: 新增 InputNumber 组件与优化 Slider 组件

概述

此PR包含两个主要功能的实现:

  1. 新增 InputNumber 组件
  2. 重构和优化 Slider 组件

测试

  • 为新组件添加了完整的单元测试
  • 更新了现有组件的测试用例

Summary by CodeRabbit

  • New Features

    • Added InputNumber and Slider components with controls, formatting, precision, keyboard support, tooltips/marks, vertical mode, and plugin-style registration.
  • Documentation

    • Added comprehensive guides and demos for InputNumber and Slider; improved Timeline API tables.
  • Bug Fixes

    • More reliable popover visibility and lifecycle/cleanup behavior.
  • Tests

    • Extensive tests for InputNumber and Slider; updated Popover and Timeline tests for more stable DOM behavior.
  • Chores

    • CI workflow step renamed; lint config ignores node_modules.

@coderabbitai
Copy link

coderabbitai bot commented Nov 17, 2025

Walkthrough

Adds InputNumber and Slider components (types, styles, composables, TSX components, plugin entries, tests, and docs); centralizes Popover visibility/init/cleanup and updates Popover tests (disable teleport); wraps Timeline item icons with markRaw in runtime and tests; small CI workflow rename and test filter change; adds node_modules to ls-lint ignore; documentation table formatting updates.

Changes

Cohort / File(s) Summary
InputNumber Component
packages/ccui/ui/input-number/index.ts, packages/ccui/ui/input-number/src/input-number-types.ts, packages/ccui/ui/input-number/src/input-number.scss, packages/ccui/ui/input-number/src/input-number.tsx, packages/ccui/ui/input-number/test/input-number.test.ts, packages/docs/components/input-number/index.md
New InputNumber plugin entry and named export; comprehensive prop/emit/instance type definitions; SCSS mixins and size variants; TSX component with formatting, clamping, controls, keyboard, exposed instance API; tests and docs added.
Slider Component
packages/ccui/ui/slider/index.ts, packages/ccui/ui/slider/src/slider-types.ts, packages/ccui/ui/slider/src/slider.scss, packages/ccui/ui/slider/src/composables/use-slider.ts, packages/ccui/ui/slider/src/composables/use-slider-tooltip.ts, packages/ccui/ui/slider/src/slider.tsx, packages/ccui/ui/slider/test/slider.test.ts, packages/docs/components/slider/index.md
New Slider plugin entry and named export; typed slider props; styles; multiple composables (value, calculation, style, interaction, keyboard, marks, input); tooltip composable; TSX component using composables; tests and docs added.
Popover behavior & tests
packages/ccui/ui/popover/src/popover.tsx, packages/ccui/ui/popover/test/popover.test.ts
Normalize actualVisible to boolean; defer and centralize autoUpdate/document-listener init and cleanup using nextTick and refs; refine controlled vs uncontrolled watch logic; tests updated to set teleported: false and adjust assertions.
Timeline icon handling & tests
packages/ccui/ui/timeline/src/timeline-item.tsx, packages/ccui/ui/timeline/test/timeline.test.ts
Wrap component icon props with markRaw before rendering to avoid Vue proxying; tests updated accordingly.
CI / Lint config
.github/workflows/buildComponents.yml, .github/workflows/test.yml, .ls-lint.yml
Renamed workflow and step to BuildComponents and replaced "Run tests" step with "Run build:components"; changed test workflow filter from ccui to vue3-ccui; added node_modules to ls-lint ignore.
Docs formatting
packages/docs/components/timeline/index.md
Reworked Timeline API tables for alignment and consistent presentation (formatting-only).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CInputNumber
    participant Parent
    Note right of CInputNumber `#DDFFDD`: normalize → validate → format
    User->>CInputNumber: type / click controls / press arrows
    CInputNumber->>CInputNumber: validate, clamp (min/max), apply precision
    CInputNumber->>Parent: emit update:modelValue / input
    User->>CInputNumber: blur
    CInputNumber->>CInputNumber: format on blur
    CInputNumber->>Parent: emit change / blur
Loading
sequenceDiagram
    participant User
    participant CSlider
    participant Composables
    participant Parent
    Note right of Composables `#F0F8FF`: percent/value calc, interaction, tooltip
    User->>CSlider: drag / click / keyboard
    CSlider->>Composables: useSliderInteraction / useSliderKeyboard / useSliderCalculation
    Composables->>Composables: compute percent ↔ value, clamp to [min,max]
    Composables->>Parent: emit update:modelValue / input / change
    CSlider->>Composables: useSliderStyle / useSliderTooltip
    Composables->>CSlider: styles, tooltip content
Loading
sequenceDiagram
    participant App
    participant Popover
    participant AutoUpdate
    participant Document
    Note right of Popover `#FFF4E6`: visibility normalization & lifecycle
    App->>Popover: mount (props.visible true/false)
    Popover->>Popover: actualVisible = Boolean(props.visible)
    Popover->>AutoUpdate: init only when actualVisible true (nextTick)
    Popover->>Document: attach listeners on visible
    Document->>Popover: events (mousedown/keydown) handled
    Popover->>AutoUpdate: cleanup and remove document listeners on hide/unmount
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay extra attention to:
    • InputNumber: formatting/precision/clamping, regex validation, exposed methods and event emission semantics.
    • Slider: composables interaction (drag/keyboard/percent↔value), range handling, tooltip integration and ARIA attributes.
    • Popover: controlled vs uncontrolled sync, nextTick usage, autoUpdate lifecycle and document listener cleanup.
    • Tests: many tests disable teleport — confirm mounting/query assumptions and that teleported:false changes do not mask integration issues.

Possibly related PRs

  • feat: timeline #9 — related to Timeline icon handling and the use of markRaw for icon components.
  • feat: popover #7 — related to Popover/tooltip lifecycle and visibility/control logic changes.

Poem

🐇
I counted ticks and rounded light,
Wrapped icons snug to dodge the bite,
Sliders slid and numbers stayed precise,
Popovers woke only when concise,
Two widgets hopped into the site. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the two main changes: adding a new InputNumber component and optimizing the Slider component, which aligns with the changeset's primary objectives.
✨ 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-slider

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0677924 and 1494ee3.

📒 Files selected for processing (1)
  • packages/ccui/ui/slider/src/composables/use-slider.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ccui/ui/slider/src/composables/use-slider.ts

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: 2

🧹 Nitpick comments (12)
packages/ccui/ui/slider/src/slider.scss (1)

157-319: Consider improving touch and keyboard affordances

The base wrapper/button styling is solid, with hover/active/focus states and a responsive tweak. For even better UX on touch/keyboard:

  • You might add touch-action: none; to &__wrapper to further reduce unintended page scrolling during drags on some mobile browsers.
  • If you want to avoid always showing the focus ring on mouse click, consider switching &:focus to &:focus-visible for the button.

Both are optional and can be deferred.

packages/ccui/ui/slider/src/slider-types.ts (1)

3-112: Slider prop surface is well-structured

The prop definitions cover the expected areas (range, size, input, tooltip, marks, a11y) and align with the SCSS modifiers (large/default/small, vertical, placements, etc.). Nothing blocking here.

If you want a tiny cleanup, you could introduce a shared formatter type, e.g. type SliderFormatter = (value: number) => string, and reuse it for formatTooltip, formatValueText, and tipsRenderer to reduce duplication, but this is purely ergonomic.

packages/ccui/ui/slider/src/composables/use-slider.ts (2)

112-225: Global drag listeners are fine, but consider unmount cleanup / touch options

The drag logic (document-level mousemove/mouseup/touchmove/touchend listeners) is correct and cleaned up on handleDragEnd. Two optional robustness tweaks:

  • If the slider component can be unmounted while dragging (route change, v-if, etc.), it’s safer for the parent component to call handleDragEnd or otherwise remove listeners in an onBeforeUnmount hook to avoid lingering handlers.
  • To make event.preventDefault() effective on touchmove across more browsers, you may want to register the touchmove listener with { passive: false } or rely on CSS like touch-action: none on the track/handle.

Both are defensive improvements and not blockers.


341-365: useSliderInput assumes a single value; ensure it’s not used for range sliders

handleInputChange clamps and then writes a scalar into currentValue:

const clampedValue = Math.max(props.min, Math.min(props.max, value))
currentValue.value = clampedValue
emit('change', clampedValue)

If this composable is ever used when props.range === true, it will collapse the range array into a single number and break the range semantics.

If the design is “input box only for single-value sliders”, that’s fine—just ensure useSliderInput is only wired in that mode (and perhaps guard with a runtime check + dev warning). Otherwise, you may want to extend this logic to edit the active handle in a range [start, end] instead of replacing the entire value.

packages/ccui/ui/slider/src/composables/use-slider-tooltip.ts (1)

73-78: Consider simplifying boolean expression.

The condition on line 74-77 can be refactored for better readability:

  const shouldShowDefaultTooltipForButton = (index: number) => {
-   return props.showTooltip
-     && props.tipsRenderer === null
-     && props.showDefaultTooltip
-     && (hoverIndex.value === index || isDragging.value || props.persistent)
+   const isVisible = hoverIndex.value === index || isDragging.value || props.persistent
+   return props.showTooltip && props.tipsRenderer === null && props.showDefaultTooltip && isVisible
  }
packages/ccui/ui/input-number/src/input-number.scss (1)

59-91: Consider adding validation for size-variant parameters.

The size-variant mixin accepts 7 parameters but lacks validation. While SCSS doesn't support runtime validation, consider adding comments documenting expected value types and units.

Add parameter documentation:

+// @param {length} $width - Component width (e.g., 150px)
+// @param {length} $height - Component height (e.g., 32px)
+// @param {length} $font-size - Font size (e.g., 14px)
+// @param {length} $inner-height - Inner input height (e.g., 30px)
+// @param {length} $control-width - Control button width (e.g., 32px)
+// @param {length} $svg-size - SVG icon size (e.g., 12px)
+// @param {length} $padding - Inner padding (default: 0 11px)
 @mixin size-variant($width, $height, $font-size, $inner-height, $control-width, $svg-size, $padding: 0 11px) {
packages/ccui/ui/input-number/src/input-number-types.ts (1)

34-91: Types and props look consistent; consider reusing InputNumberValue for modelValue

The props and associated types are well aligned with the component API. For better cohesion and easier future changes, you could reuse the InputNumberValue alias directly in the prop typing:

modelValue: {
  type: Number as PropType<InputNumberValue>,
  default: undefined,
},

Purely a style/maintainability tweak; current code is correct.

packages/ccui/ui/slider/test/slider.test.ts (3)

164-177: Clarify tipsRenderer null behavior and assertion

The test name/comment say “should hide tooltip”, but the expectations are:

  • CTooltip exists
  • content is ''
  • showTooltip is not explicitly set (relies on default)

If the intended contract is “no tooltip at all when tipsRenderer is null”, this should assert exists() === false. If the intent is “tooltip shell exists but with empty content”, consider updating the test name/comment to reflect that to avoid confusion for future maintainers.


310-329: Strengthen Home/End keyboard tests with value assertions

The Home/End keyboard test only checks that update:modelValue was emitted. To better guard against regressions, you might also assert the emitted values are clamped to min and max, e.g. last emitted value equals 0 for Home and 100 for End.

This would tie the test directly to the intended accessibility behavior of those keys.


649-661: Fix test name vs props for persistent + showTooltip

The test is named props - persistent false with showTooltip false, but the props passed are:

persistent: true,
showTooltip: false,

The expectations (CTooltip does not exist) match “showTooltip=false wins regardless of persistent”. To avoid confusion, consider either:

  • Renaming the test to match the props (e.g. “persistent true with showTooltip false”), or
  • Setting persistent: false if the name is what you want to assert.

Behavior is fine; this is just for clarity.

packages/ccui/ui/input-number/src/input-number.tsx (2)

50-71: Normalize all entry points through formatValue for consistent behavior

formatValue correctly applies allowEmpty, precision, and min/max, but:

  • setValue uses updateValue directly.
  • The watch on props.modelValue also assigns innerValue directly.

This means external updates (via instance API or v-model changes from the parent) can bypass normalization and display out-of-range or unformatted values, while user interactions go through formatValue.

For consistency, consider:

const instance: InputNumberInstance = {
  getValue: () => innerValue.value,
  setValue: (value: InputNumberValue) => updateValue(formatValue(value)),
  // …
}

watch(
  () => props.modelValue,
  (newValue) => {
    const formatted = formatValue(newValue as any)
    if (formatted !== innerValue.value) {
      innerValue.value = formatted
    }
  },
  { immediate: true },
)

This keeps the value invariant (min/max/precision/empty handling) regardless of how it’s set.

Also applies to: 191-212


248-261: Avoid writing ±Infinity as min/max HTML attributes

With the current defaults (min = -Infinity, max = Infinity), the rendered input will get:

<input min="-Infinity" max="Infinity" ...>

Browsers tend to treat these as invalid and effectively ignore them, but it’s cleaner and less surprising to omit the attributes when unbounded. For example:

<input
  // ...
  min={Number.isFinite(props.min) ? props.min : undefined}
  max={Number.isFinite(props.max) ? props.max : undefined}
/>

This preserves your internal clamping logic while keeping the DOM attributes semantically correct.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cda97a0 and 48476eb.

📒 Files selected for processing (18)
  • .github/workflows/buildComponents.yml (1 hunks)
  • .github/workflows/test.yml (1 hunks)
  • .ls-lint.yml (1 hunks)
  • packages/ccui/ui/input-number/index.ts (1 hunks)
  • packages/ccui/ui/input-number/src/input-number-types.ts (1 hunks)
  • packages/ccui/ui/input-number/src/input-number.scss (1 hunks)
  • packages/ccui/ui/input-number/src/input-number.tsx (1 hunks)
  • packages/ccui/ui/input-number/test/input-number.test.ts (1 hunks)
  • packages/ccui/ui/slider/index.ts (1 hunks)
  • packages/ccui/ui/slider/src/composables/use-slider-tooltip.ts (1 hunks)
  • packages/ccui/ui/slider/src/composables/use-slider.ts (1 hunks)
  • packages/ccui/ui/slider/src/slider-types.ts (1 hunks)
  • packages/ccui/ui/slider/src/slider.scss (1 hunks)
  • packages/ccui/ui/slider/src/slider.tsx (1 hunks)
  • packages/ccui/ui/slider/test/slider.test.ts (1 hunks)
  • packages/docs/components/input-number/index.md (1 hunks)
  • packages/docs/components/slider/index.md (1 hunks)
  • packages/docs/components/timeline/index.md (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
packages/ccui/ui/slider/src/slider.tsx (4)
packages/ccui/ui/slider/src/slider-types.ts (2)
  • sliderProps (7-110)
  • SliderProps (112-112)
packages/ccui/ui/shared/hooks/use-namespace.ts (1)
  • useNamespace (31-47)
packages/ccui/ui/slider/src/composables/use-slider.ts (7)
  • useSliderValue (5-22)
  • useSliderCalculation (24-40)
  • useSliderStyle (42-110)
  • useSliderInteraction (112-225)
  • useSliderKeyboard (227-286)
  • useSliderMarks (288-339)
  • useSliderInput (341-366)
packages/ccui/ui/slider/src/composables/use-slider-tooltip.ts (1)
  • useSliderTooltip (5-126)
packages/ccui/ui/slider/src/composables/use-slider-tooltip.ts (1)
packages/ccui/ui/slider/src/slider-types.ts (1)
  • SliderProps (112-112)
packages/ccui/ui/input-number/src/input-number.tsx (2)
packages/ccui/ui/input-number/src/input-number-types.ts (4)
  • inputNumberProps (34-91)
  • InputNumberProps (93-93)
  • InputNumberValue (8-8)
  • InputNumberInstance (114-127)
packages/ccui/ui/shared/hooks/use-namespace.ts (1)
  • useNamespace (31-47)
packages/ccui/ui/slider/src/composables/use-slider.ts (1)
packages/ccui/ui/slider/src/slider-types.ts (1)
  • SliderProps (112-112)
packages/ccui/ui/slider/test/slider.test.ts (2)
packages/ccui/ui/shared/hooks/use-namespace.ts (1)
  • useNamespace (31-47)
packages/ccui/ui/slider/index.ts (1)
  • Slider (8-8)
🪛 LanguageTool
packages/docs/components/slider/index.md

[uncategorized] ~13-~13: 动词的修饰一般为‘形容词(副词)+地+动词’。您的意思是否是:基本"地"滑
Context: ...要调整设置值时,如音量、亮度等 - 当需要选择范围值时 ## 基本用法 基本的滑块用法。 :::demo ```vue <script> import {...

(wb4)

packages/docs/components/input-number/index.md

[locale-violation] ~50-~50: 'input' é un xenismo. É preferíbel dicir "entrada"
Context: ... ::: ## 禁用状态 通过disabled` 属性指定是否禁用 input 组件。 :::demo ```vue <script> import { ...

(GL_BARBARISM_REPLACE)


[locale-violation] ~288-~288: 'show' é un xenismo. É preferíbel dicir "divertimento"
Context: ... | false | 是否允许空值 | | show-glow-style | boolean |...

(GL_BARBARISM_REPLACE)


[locale-violation] ~296-~296: 'Input' é un xenismo. É preferíbel dicir "entrada"
Context: ... | 在 Input 失去焦点时触发 | | focus | (event: Event) ...

(GL_BARBARISM_REPLACE)


[locale-violation] ~297-~297: 'Input' é un xenismo. É preferíbel dicir "entrada"
Context: ... | 在 Input 获得焦点时触发 | | input | `(currentValue: nu...

(GL_BARBARISM_REPLACE)


[locale-violation] ~298-~298: 'input' é un xenismo. É preferíbel dicir "entrada"
Context: ... | 在 Input 获得焦点时触发 | | input | `(currentValue: number | undefined)...

(GL_BARBARISM_REPLACE)


[locale-violation] ~298-~298: 'Input' é un xenismo. É preferíbel dicir "entrada"
Context: ...efined)` | 在 Input 值改变时触发 | ## InputNumber方法 | 方法名 | 说...

(GL_BARBARISM_REPLACE)


[locale-violation] ~304-~304: 'input' é un xenismo. É preferíbel dicir "entrada"
Context: ...----------------- | ---- | | focus | 使 input 获取焦点 | - | | blur | 使 input 失去焦点...

(GL_BARBARISM_REPLACE)


[locale-violation] ~305-~305: 'input' é un xenismo. É preferíbel dicir "entrada"
Context: ... | 使 input 获取焦点 | - | | blur | 使 input 失去焦点 | - | | select | 选中 input 中的文...

(GL_BARBARISM_REPLACE)


[locale-violation] ~306-~306: 'input' é un xenismo. É preferíbel dicir "entrada"
Context: ...| 使 input 失去焦点 | - | | select | 选中 input 中的文字 | - |

(GL_BARBARISM_REPLACE)

🔇 Additional comments (21)
.github/workflows/test.yml (1)

33-34: Test workflow now runs only ccui tests

pnpm --filter ccui test is fine assuming ccui is the intended workspace package and you don’t need tests from other packages in this workflow. If the monorepo grows, consider whether this job should cover additional packages or stay scoped to ccui.

.ls-lint.yml (1)

16-24: Global node_modules ignore is appropriate

Adding a top-level node_modules ignore is a good hardening of the lint config and complements the more specific package-level ignores.

.github/workflows/buildComponents.yml (1)

33-34: Build workflow scoped to CLI package

pnpm --filter cli build:components looks correct as long as the cli package owns the components build entrypoint and there’s no need to run a broader workspace build here.

packages/docs/components/timeline/index.md (1)

224-256: Timeline API tables formatting looks consistent

The revised tables for Timeline Slots, Timeline-Item Props, Events, and Slots have consistent column counts and clearer headers, which should render more predictably in the docs. No functional issues spotted.

packages/ccui/ui/slider/src/slider.scss (1)

134-155: With-input layout is clear and flexible

The &--with-input block cleanly handles the slider + input-number layout (row-reverse, flex, min-width safeguards). This should integrate well with the new CInputNumber component.

packages/docs/components/input-number/index.md (1)

1-306: LGTM! Well-structured documentation.

The InputNumber documentation is comprehensive and well-organized, covering:

  • Usage scenarios and basic examples
  • All component states (disabled, readonly, etc.)
  • Configuration options (min/max, step, precision, sizes, controls)
  • Complete API reference with props, events, and methods
packages/docs/components/slider/index.md (1)

1-507: LGTM! Comprehensive Slider documentation.

The Slider documentation thoroughly covers all component features:

  • Multiple usage scenarios (basic, range, vertical, steps, marks, etc.)
  • Tooltip customization and placement options
  • Size variants and disabled states
  • Accessibility features
  • Complete API reference
packages/ccui/ui/input-number/test/input-number.test.ts (1)

1-291: LGTM! Comprehensive test coverage.

The test suite thoroughly covers:

  • Component rendering and structure
  • v-model binding and reactivity
  • User interactions (buttons, keyboard, input changes)
  • Boundary validation (min/max limits)
  • State variations (disabled, readonly, sizes, controls)
  • Focus/blur events
  • Edge cases (allowEmpty, precision)
  • Public API methods exposure

The bilingual test descriptions (English and Chinese) are a nice touch for international teams.

packages/ccui/ui/input-number/index.ts (2)

10-17: Minor duplication: install method defined twice.

Both the component itself (lines 4-6) and the default export object (lines 14-16) have identical install methods. While this provides flexibility (use as named import or default), it creates maintenance overhead.

This pattern is acceptable if it's a project-wide convention for component exports.


4-6: Component name is properly defined; no issues found.

The InputNumber component explicitly defines name: 'CInputNumber' in its defineComponent configuration (line 12 of input-number.tsx), which ensures the non-null assertion in the install method is safe and justified.

packages/ccui/ui/slider/index.ts (2)

10-17: LGTM! Consistent plugin pattern.

The plugin wrapper follows the same pattern as InputNumber, providing both named and default exports with install methods. The explicit void return type annotation is a nice touch for type safety.


4-6: Component name is properly defined—non-null assertion is safe.

The Slider component explicitly defines name: 'CSlider' (line 20 in packages/ccui/ui/slider/src/slider.tsx), confirming the non-null assertion in Slider.name! is appropriate and will not cause runtime errors.

packages/ccui/ui/slider/src/composables/use-slider-tooltip.ts (2)

17-28: Clean tooltip formatting logic.

The cascading logic correctly handles:

  1. Explicit null tipsRenderer (no custom tooltip)
  2. Custom formatTooltip function
  3. Custom tipsRenderer function
  4. Default string conversion fallback

101-108: LGTM! Smart placement logic.

The tooltip placement logic correctly:

  • Respects user-specified placement when provided
  • Falls back to orientation-aware defaults (right for vertical, top for horizontal)
packages/ccui/ui/input-number/src/input-number.scss (3)

4-57: LGTM! Well-organized mixins.

The mixins are well-structured and reusable:

  • disabled-state: Consistent disabled styling with hover state override
  • focus-state: Accessible focus indicator with 2px shadow
  • hide-controls: Clean button hiding utility
  • control-button-base: DRY button styling with all interaction states

163-171: LGTM! Proper number input styling.

Lines 163-171 correctly remove browser default number input spinners and apply textfield appearance for cross-browser consistency.


247-253: LGTM! Size variants properly implemented.

Both size variants (lg and sm) correctly utilize the size-variant mixin with appropriate parameters. The values appear well-proportioned.

packages/ccui/ui/slider/src/slider.tsx (4)

68-106: LGTM! Well-structured button renderer.

The renderButton helper cleanly encapsulates:

  • ARIA attributes for accessibility
  • Event handlers for mouse, touch, and keyboard
  • Conditional tooltip wrapping based on showTooltip prop
  • Proper disabled state handling

159-175: LGTM! Proper InputNumber integration.

The conditional rendering correctly:

  • Only shows InputNumber when showInput is true and not in range mode
  • Passes through all relevant props (modelValue, min, max, step, disabled, size, controls, precision)
  • Wires up change handlers properly

194-210: LGTM! Efficient stops rendering.

The stops rendering correctly:

  • Calculates stop positions based on min, max, and step
  • Uses proper positioning (bottom for vertical, left for horizontal)
  • Generates appropriate number of stops

213-231: LGTM! Clean marks implementation.

The marks rendering:

  • Iterates over marks object keys
  • Applies custom styles via getMarkStyle
  • Renders both mark lines and labels
  • Handles both string and object mark definitions

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 (4)
packages/ccui/ui/timeline/src/timeline-item.tsx (1)

38-39: Functional implementation with minor pattern consideration.

The icon rendering logic works correctly. Using markRaw here prevents Vue from making the component reactive, which can be beneficial for performance.

However, the typical Vue pattern is to apply markRaw at the component definition site rather than at render time. If props.icon is already reactive when it reaches this point, markRaw won't undo that. Conversely, if it's not reactive, h() doesn't automatically make components reactive, so markRaw may be unnecessary.

Consider verifying whether:

  1. This addresses a specific reactivity issue you encountered
  2. Users should apply markRaw when passing icons (making this defensive)
  3. Or if this can be simplified
Vue 3 markRaw best practices for component props
packages/ccui/ui/timeline/test/timeline.test.ts (1)

162-167: Test correctly validates icon component rendering.

The test properly verifies component icon functionality by wrapping IconComponent with markRaw, which aligns with the implementation changes.

Note that both the test (here) and the implementation (line 39 of timeline-item.tsx) apply markRaw. If the implementation already handles this internally, tests and users shouldn't need to wrap their icons. This double-wrapping is safe but potentially redundant.

Consider documenting whether:

  • Users should wrap icon components with markRaw when passing them as props
  • Or if the component handles this internally (making user-side wrapping unnecessary)

This clarifies the API contract and avoids confusion.

packages/ccui/ui/popover/test/popover.test.ts (1)

527-555: Make the trigger explicit in the exposes-methods test

In the second mount (wrapper = mount(Popover, { props: { content: 'Test', teleported: false } ... })), the test relies on clicking the trigger to show the popover before calling wrapper.vm.hide(). To avoid coupling this test to the component’s default trigger value, it would be clearer and more robust to specify the trigger explicitly:

-      wrapper = mount(Popover, {
-        props: { content: 'Test', teleported: false },
-        slots: { default: '<button>Trigger</button>' },
-      })
+      wrapper = mount(Popover, {
+        props: { content: 'Test', trigger: 'click', teleported: false },
+        slots: { default: '<button>Trigger</button>' },
+      })

This makes the intent obvious and keeps the test stable if the default trigger ever changes.

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

274-285: Virtual trigger cleanup block is now orphaned

In onUnmounted you remove a number of event listeners from props.virtualRef:

if (props.virtualTriggering && props.virtualRef) {
  const virtualEl = props.virtualRef
  virtualEl.removeEventListener('mouseenter', handleMouseEnter)
  virtualEl.removeEventListener('mouseleave', handleMouseLeave)
  virtualEl.removeEventListener('click', handleClick)
  virtualEl.removeEventListener('focus', handleFocus)
  virtualEl.removeEventListener('blur', handleBlur)
  virtualEl.removeEventListener('keydown', handleKeydown)
  virtualEl.removeEventListener('contextmenu', handleContextMenu)
}

But there is no corresponding addEventListener for virtualRef anywhere in the current code path (listeners are now only attached via triggerEvents when !props.virtualTriggering). This makes the cleanup block effectively dead and potentially confusing for future maintenance.

If virtual triggering is now intended to be manual-only (as the tests suggest), you can safely remove this cleanup block. If you still want interactive virtual triggers (hover/click/focus on an external element), the matching addEventListener logic should be reintroduced instead.

Given current usage, I recommend removing the dead cleanup to reflect the actual behavior:

-      // 清理虚拟触发元素的事件监听
-      if (props.virtualTriggering && props.virtualRef) {
-        const virtualEl = props.virtualRef
-        virtualEl.removeEventListener('mouseenter', handleMouseEnter)
-        virtualEl.removeEventListener('mouseleave', handleMouseLeave)
-        virtualEl.removeEventListener('click', handleClick)
-        virtualEl.removeEventListener('focus', handleFocus)
-        virtualEl.removeEventListener('blur', handleBlur)
-        virtualEl.removeEventListener('keydown', handleKeydown)
-        virtualEl.removeEventListener('contextmenu', handleContextMenu)
-      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48476eb and 96b207a.

📒 Files selected for processing (6)
  • .github/workflows/buildComponents.yml (2 hunks)
  • .github/workflows/test.yml (1 hunks)
  • packages/ccui/ui/popover/src/popover.tsx (3 hunks)
  • packages/ccui/ui/popover/test/popover.test.ts (24 hunks)
  • packages/ccui/ui/timeline/src/timeline-item.tsx (2 hunks)
  • packages/ccui/ui/timeline/test/timeline.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/test.yml
🧰 Additional context used
🧬 Code graph analysis (1)
packages/ccui/ui/popover/test/popover.test.ts (1)
packages/ccui/ui/tooltip/test/tooltip.test.ts (10)
  • it (182-286)
  • it (288-307)
  • wrapper (6-487)
  • wrapper (344-374)
  • it (465-486)
  • it (95-180)
  • wrapper (201-221)
  • wrapper (96-110)
  • wrapper (466-485)
  • wrapper (289-306)
🔇 Additional comments (10)
.github/workflows/buildComponents.yml (2)

1-1: Verify workflow rename aligns with CI/CD strategy.

The workflow was renamed from "Test" to "BuildComponents", reflecting a narrower focus on building components rather than running tests. Since the PR adds new tests for InputNumber and Slider components, confirm that test execution has been properly configured in an alternative workflow (e.g., test.yml mentioned in the AI summary context).


33-34: All verification concerns are satisfied; no issues found.

The review's concerns have been verified:

  1. ✅ Tests are properly configured in test.yml which runs pnpm --filter vue3-ccui test using vitest
  2. ✅ Both InputNumber and Slider components have dedicated test files:
    • packages/ccui/ui/input-number/test/input-number.test.ts
    • packages/ccui/ui/slider/test/slider.test.ts
  3. ✅ No test coverage gaps exist; both new components are included in the test suite (16 of 19 component directories contain tests)

The separation of test execution (test.yml) from the build step (buildComponents.yml) is an appropriate CI design pattern.

packages/ccui/ui/timeline/src/timeline-item.tsx (1)

2-2: LGTM - Import addition for markRaw.

The import of markRaw from Vue is correctly added to support the icon rendering optimization.

packages/ccui/ui/timeline/test/timeline.test.ts (1)

3-3: LGTM - Import addition for test alignment.

The markRaw import correctly supports the updated test for component icon handling.

packages/ccui/ui/popover/test/popover.test.ts (5)

41-57: Using teleported: false in most unit tests is a good, stable pattern

Disabling teleport for the majority of tests keeps the popper DOM local to the wrapper, which makes queries straightforward and avoids relying on global document structure. You still have a dedicated teleport 功能 test to validate the teleported path, so coverage remains balanced between internal and teleported rendering modes.

Also applies to: 76-90, 92-107, 110-141, 143-189


211-237: Interaction and delay tests align well with the current visibility logic

The hover/focus/disabled and showAfter/hideAfter tests, combined with hideAfter: 0 where needed and fake timers for delay cases, accurately exercise doShow/doHide behavior and timer handling. The expectations around visibility toggling and delayed show/hide match the implementation, and using teleported: false keeps the assertions focused on behavior rather than Teleport.

Also applies to: 240-287


320-333: ARIA test correctly follows dynamic popper IDs

Reading the id from .ccui-popover__popper and asserting aria-describedby matches that value is the right move now that each popover instance has a unique, generated popper ID. This also makes the test resilient to future ID format changes as long as the trigger and popper stay in sync.


336-381: Outside-click and auto-close scenarios are covered appropriately

The click-outside tests (with and without hideOnClickOutside) and the autoClose test together validate the document-level mousedown/keydown handling and the auto-close timer. Using attachTo: document.body where needed and vi.useFakeTimers in the auto-close test aligns well with the implementation’s reliance on global events and timers.

Also applies to: 384-432


434-469: Keyboard-trigger test nicely exercises the new focus + triggerKeys behavior

This test covers the full cycle for focus trigger with custom triggerKeys:

  • Focus shows the popover.
  • Enter toggles it off then back on.
  • Space (' ') is normalized and toggles it off then back on.
  • blur closes it.

That matches the normalizeTriggerKey and handleKeydown logic and gives good coverage for keyboard accessibility.

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

350-402: ARIA wiring and boolean actualVisible normalization look solid

The combination of:

  • actualVisible as a strict boolean, and
  • aria-describedby={actualVisible.value ? popperId : undefined} on the trigger, with id={popperId} and role="dialog" on the popper

gives you per-instance unique IDs, ensures the ARIA reference only exists when the popper is actually in the DOM, and matches the updated tests. This is a clean, accessible setup for multiple popovers on the same page.

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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96b207a and 9e4fe86.

📒 Files selected for processing (2)
  • packages/ccui/ui/popover/src/popover.tsx (3 hunks)
  • packages/ccui/ui/slider/src/composables/use-slider.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/ccui/ui/popover/src/popover.tsx (1)
packages/ccui/ui/tooltip/src/tooltip.tsx (7)
  • actualVisible (180-184)
  • isControlled (25-25)
  • props (24-24)
  • newVal (203-211)
  • props (192-192)
  • props (141-150)
  • props (158-162)
packages/ccui/ui/slider/src/composables/use-slider.ts (1)
packages/ccui/ui/slider/src/slider-types.ts (1)
  • SliderProps (112-112)
🔇 Additional comments (9)
packages/ccui/ui/popover/src/popover.tsx (4)

27-30: Good: Strict boolean coercion improves type safety.

Wrapping the result with Boolean() ensures actualVisible is always a strict boolean rather than a truthy/falsy value, which prevents edge cases and improves consistency.


258-270: Excellent: This fixes the initial visibility issue from the previous review.

The updated onMounted now correctly attaches document listeners (mousedown, keydown) when the component mounts with props.visible === true. This ensures outside-click and Escape-to-close work immediately, even when visibility never toggles. The previous critical issue is resolved.


289-301: LGTM: Controlled-mode synchronization logic is correct.

The watcher properly synchronizes props.visible to internal state with boolean coercion and change detection. The update() call at Line 296 is slightly redundant since the actualVisible watcher will subsequently set up autoUpdate (which also calls update), but this is harmless and may provide immediate positioning.


302-317: LGTM: Core visibility lifecycle handling is sound.

The actualVisible watcher correctly manages the autoUpdate cleanup and document listener lifecycle. The defensive checks for element readiness (Line 305) and safe cleanup invocation (Line 306) prevent edge-case errors. This works correctly in tandem with onMounted for comprehensive visibility management.

packages/ccui/ui/slider/src/composables/use-slider.ts (5)

5-22: LGTM!

The two-way binding implementation correctly emits both update:modelValue and input events, properly supporting Vue's v-model pattern.


24-56: Excellent fix for step snapping!

The calculation now correctly snaps values relative to min and guards against degenerate configurations (range ≤ 0, step ≤ 0), addressing the issue raised in the previous review.


58-126: LGTM!

The style calculations correctly handle both single and range modes across vertical and horizontal orientations.


243-302: LGTM!

Keyboard navigation correctly handles both single and range modes, with proper clamping to prevent handles from crossing or exceeding bounds.


304-355: LGTM!

The marks calculation correctly filters out-of-range values and provides flexible styling and labeling for both string and object mark definitions.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e4fe86 and 0677924.

📒 Files selected for processing (3)
  • packages/ccui/ui/slider/src/composables/use-slider.ts (1 hunks)
  • packages/ccui/ui/slider/src/slider.tsx (1 hunks)
  • packages/docs/components/slider/index.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ccui/ui/slider/src/slider.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/ccui/ui/slider/src/composables/use-slider.ts (1)
packages/ccui/ui/slider/src/slider-types.ts (1)
  • SliderProps (112-112)
🪛 LanguageTool
packages/docs/components/slider/index.md

[uncategorized] ~13-~13: 动词的修饰一般为‘形容词(副词)+地+动词’。您的意思是否是:基本"地"滑
Context: ...要调整设置值时,如音量、亮度等 - 当需要选择范围值时 ## 基本用法 基本的滑块用法。 :::demo ```vue <script> import {...

(wb4)

🔇 Additional comments (8)
packages/docs/components/slider/index.md (2)

13-13: Disregard grammar suggestion at line 13.

Static analysis flagged "基本的滑块用法" as a potential grammar issue, but this is correct Chinese (adjective + 的 + noun). The suggestion is a false positive and should be ignored.


1-541: API documentation accurately reflects component implementation.

Verification confirms all 28 documented props exist in the component with correct types, default values, and options. The two documented events (change, input) are properly implemented. Documentation uses correct kebab-case naming convention for Vue props.

packages/ccui/ui/slider/src/composables/use-slider.ts (6)

5-22: LGTM: Clean v-model implementation.

The computed wrapper correctly implements the standard Vue v-model pattern with proper event emissions.


24-56: LGTM: Step snapping now correctly aligned to min.

The calculation logic properly guards degenerate configurations and snaps steps relative to min as suggested in the previous review. This fixes the issue where non-zero, non-multiple min values would produce incorrect snap points.


58-126: LGTM: Style calculations handle all orientation and mode combinations correctly.

The computed styles properly handle single/range modes and vertical/horizontal orientations with appropriate CSS properties.


251-310: LGTM: Keyboard navigation properly handles all modes.

The keyboard handler correctly implements arrow key stepping and Home/End jumps, with proper constraints for both range and single-value modes.


312-363: LGTM: Mark handling is robust and flexible.

The marks composable correctly filters marks to the valid range and supports both string and object mark definitions with custom styling.


128-249: Memory leak concern has been successfully addressed.

Verification confirms the cleanup function is properly called in the consumer component's onBeforeUnmount hook (packages/ccui/ui/slider/src/slider.tsx, lines 60-62). The cleanup wiring is correct and complete.

@vaebe vaebe merged commit b275306 into main Nov 17, 2025
4 checks passed
@vaebe vaebe deleted the feat-slider branch November 17, 2025 11:30
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.

2 participants