Skip to content

feat: collapsible component and support formitem collapsible - #7816

Merged
jinmao88 merged 54 commits into
vbenjs:mainfrom
2yllll:feature-collapsible-component
Apr 15, 2026
Merged

jinmao88 merged 54 commits into
vbenjs:mainfrom
2yllll:feature-collapsible-component

Conversation

@2yllll

@2yllll 2yllll commented Apr 14, 2026 •

Copy link
Copy Markdown
Contributor
  • shadcn-ui 增加 collapsible组件,collapsible-params组件
  • form新增支持单项折叠,新增支持在 dependencies 里访问extendApi的能力(主要访问component实例执行expose的方法)
  • collapsible-params组件在Form表单应用

Summary by CodeRabbit

  • New Features

    • Collapsible form fields and a collapsible parameter editor with fold/unfold, reset, and programmatic updates.
    • New example page demonstrating collapsible forms and parameters.
  • UI

    • Chevron toggle controls for collapsible areas and improved card header spacing.
    • New collapsible textarea field in the example.
  • Behavior

    • Default form layout set to vertical; example shows dynamic parameter injection and validation when toggled.
  • Documentation

    • Added locale entries and a new example route for collapsible form demos.

xingyu4j and others added 30 commits April 13, 2026 17:52
* perf: replace `onUnMounted` to `tryOnScopeDispose`

* perf: replace `onUnMounted` to `tryOnScopeDispose`
1. remove unknown rule out of oxlint
2. add the corresponding back to eslint-config
3. fixed the eslint error for package.json
- 关闭 vitest/require-mock-type-parameters 规则
- 添加 rootDir 编译选项指向 ./src 目录
- 保持现有编译配置不变
- 排除测试文件和 node_modules 目录
2yllll added 8 commits April 13, 2026 19:20
feat: add collapsible 组件,form表单增加单项可折叠,支持schema配置默认关闭/开启
- shadcn-ui 增加 collapsible组件,collapsible-params组件
- form新增支持单项折叠
- collapsible-params组件在Form表单应用
@2yllll
2yllll requested review from a team, anncwb, jinmao88, mynetfan and vince292007 as code owners April 14, 2026 01:54
@changeset-bot

changeset-bot Bot commented Apr 14, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: c3aa639

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Apr 14, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a collapsible form UI feature: new VbenCollapsible and VbenCollapsibleParams components and types, integrates collapsible behavior into form-field and form types, threads ExtendedFormApi into dependency callbacks, supplies an example/demo, locales/routes, and adds a workspace dependency for shadcn-ui.

Changes

Cohort / File(s) Summary
Workspace Dependency
apps/web-naive/package.json
Added @vben-core/shadcn-ui as a workspace dependency.
New Collapsible Components
packages/@core/ui-kit/shadcn-ui/src/components/collapsible/...
packages/@core/ui-kit/shadcn-ui/src/components/index.ts
Added collapsible.vue, collapsible-params.vue, collapsible-params-item.vue, type.ts, and index export — new components, props/emits/models, exposed methods, and types for collapsible params; re-exported from shadcn-ui barrel.
Form UI Integration
packages/@core/ui-kit/form-ui/src/form-render/form-field.vue, .../form-label.vue, .../form-render/dependencies.ts, .../types.ts, .../index.ts
Integrated collapsible UI into form-field (toggle state, label extra slot), added collapsible/defaultCollapsed to FormCommonConfig, updated dependency callback signatures to accept ExtendedFormApi, and re-exported FormLayout.
Collapsible Usage in Playground
playground/src/views/examples/form/collapsible.vue, playground/src/router/routes/modules/examples.ts, playground/src/locales/langs/en-US/examples.json, playground/src/locales/langs/zh-CN/examples.json
Added a new example page and route for collapsible form params; added locale keys for form.collapsible.
Adapter Registration
playground/src/adapter/component/index.ts
Registered CollapsibleParams component type and props in the component adapter and initialized VbenCollapsibleParams.
UI Kit Minor
packages/@core/ui-kit/form-ui/src/components/form-actions.vue
Removed commented-out computed block (non-functional).
Icons
packages/@core/base/icons/src/lucide.ts
Re-exported ChevronsDown from lucide-vue-next.
Demo Update
apps/web-naive/src/views/demos/form/basic.vue
Changed demo form layout to vertical, added a collapsible textarea field, and adjusted card header classes.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant VbenForm as VbenForm
    participant Deps as useDependencies
    participant Controller as ExtendedFormApi
    participant VbenCollapsible as VbenCollapsible
    participant VbenParams as VbenCollapsibleParams
    participant Item as CollapsibleParamsItem

    User->>VbenForm: interact / toggle field / submit
    VbenForm->>Deps: evaluate dependency callbacks (show/if/rules/props)
    Deps->>Controller: derive controller (injectFormProps) and pass it
    Deps->>Controller: call callbacks (formValues, formApi, controller)
    VbenForm->>VbenCollapsible: render field, bind v-model:open
    VbenCollapsible->>VbenParams: manage open state, provide toggle/reset/update
    VbenParams->>Item: render rows and model binding
    Item->>VbenForm: emit update:value on change
    User->>VbenCollapsible: click trigger -> toggle (ChevronsDown rotates)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

feature

Suggested reviewers

  • jinmao88
  • mynetfan
  • anncwb
  • vince292007

Poem

Hop, I tuck my ears and bound,
Chevrons turn with a gentle sound,
Rows fold snug where secrets hide,
Params peek out on the other side,
A rabbit cheers — forms open wide! 🐇✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete. It lacks structured sections from the template (Type of change, Checklist), contains only Chinese text without clear structure, and provides minimal details compared to the substantial nature of the changes. Complete the description by selecting appropriate change type checkboxes, providing detailed explanation in English, and completing the review checklist items as specified in the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature: adding a collapsible component and supporting collapsible form items, which aligns with the substantial changes throughout the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (1)
packages/@core/ui-kit/form-ui/src/types.ts (1)

148-151: Consider documenting defaults consistently for collapse flags.

defaultCollapsed has @default false, but collapsible lacks a default note. Adding @default false to collapsible would make API docs clearer and consistent.

Also applies to: 164-168

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@core/ui-kit/form-ui/src/types.ts around lines 148 - 151, Add
consistent `@default` tags for the collapse-related props: annotate collapsible
with "@default false" to match defaultCollapsed, and add the same "@default
false" note to the other collapse flag(s) around the same block (e.g., the prop
defined near lines 164-168) so the API docs consistently show that those boolean
collapse flags default to false; update the JSDoc comments for the properties
named "collapsible" and the other collapse-related prop(s) referenced in this
file accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web-naive/src/views/demos/form/basic.vue`:
- Around line 120-128: The paramsValidator's max_length range disagrees with
paramsSchema's min (paramsSchema sets min: 500 while paramsValidator uses [1,
131_072]); update the max_length call in paramsValidator (the
getNumberValidator('max_length', [...] entry) to use the same minimum as
paramsSchema (min 500) and keep the same max (131_072) so both paramsSchema and
paramsValidator are aligned.
- Around line 32-45: getNumberValidator currently returns
z.number(...).default(null), which is a type mismatch because z.number() cannot
accept null; update the schema to allow nulls by using nullable() before
default, i.e. change the returned schema from getNumberValidator so that the
z.number(...) chain includes .nullable() and then .default(null); ensure the
change is applied inside the getNumberValidator function where z.number,
validator, and .default(null) are referenced.

In `@packages/`@core/ui-kit/form-ui/src/form-render/form-label.vue:
- Line 29: The extra slot is currently rendered inside the label element (slot
name="extra" in form-label.vue) which causes interactive controls (the toggle
rendered by form-field.vue) to be children of a <label>; move the <slot
name="extra"> out of the label so the toggle/control is a sibling, not a
descendant. Update the template in form-label.vue to render the slot
after/adjacent to the label text container, preserve any classes/ARIA attributes
used by the label, and verify that form-field.vue's toggle handlers (the
interactive element referenced around lines 373-378) still function and keep
necessary event modifiers and focus/aria relationships so semantics and
accessibility are correct.

In
`@packages/`@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params-item.vue:
- Around line 81-86: The range display condition incorrectly uses a truthy check
so a valid min of 0 is hidden; update the conditional on the span that
references data.option.min and data.option.max to explicitly check for presence
(e.g., ensure data.option.min and data.option.max are not null/undefined) rather
than relying on truthiness — change the condition that currently reads like
"data.option.min && data.option.max" to an explicit existence check (such as
using !== undefined / !== null or != null) for data.option.min and
data.option.max so ranges with min: 0 render correctly.

In `@playground/src/locales/langs/en-US/examples.json`:
- Line 29: Update the English label for the "collapsible" key in examples.json
to a more natural UI phrase; replace the current value "Collapsible FormItem
Content" with a cleaner option such as "Collapsible Form Item" (or "Collapsible
Form Field") so the UI text reads naturally.

In `@playground/src/views/examples/form/collapsible.vue`:
- Around line 105-113: The max_length bounds are inconsistent between
paramsSchema (which sets min: 500) and paramsValidator (which currently uses
getNumberValidator('max_length', [1, 131_072])), causing the UI and validator to
disagree; update paramsValidator to use getNumberValidator('max_length', [500,
131_072]) so the validator matches paramsSchema, ensuring both paramsSchema and
paramsValidator (and any calls to getNumberValidator for 'max_length') enforce
the same minimum.
- Around line 199-206: The string "设置表单值" is orphaned inside the RadioGroup and
won't render because RadioGroup renders from its options prop; remove the inline
text from the RadioGroup block (or, if intended as a separate label, move it
outside the <RadioGroup>), keeping the existing props (options="layouts",
v-model:value="layout", `@update`:value="onLayoutChange") intact so only the
options drive the RadioGroup content.
- Around line 22-35: The getNumberValidator function currently returns
z.number().default(null) which is invalid; update the schema returned by
getNumberValidator to allow null or undefined instead of forcing a numeric
default — for example, change the chain on the validator inside
getNumberValidator to use .nullable().default(null) if null should be permitted,
or .optional() (and remove the default) if the field may be absent; adjust the
limit handling to still apply to the underlying number schema
(validator.min/max) so references to getNumberValidator and the validator
variable remain correct.

---

Nitpick comments:
In `@packages/`@core/ui-kit/form-ui/src/types.ts:
- Around line 148-151: Add consistent `@default` tags for the collapse-related
props: annotate collapsible with "@default false" to match defaultCollapsed, and
add the same "@default false" note to the other collapse flag(s) around the same
block (e.g., the prop defined near lines 164-168) so the API docs consistently
show that those boolean collapse flags default to false; update the JSDoc
comments for the properties named "collapsible" and the other collapse-related
prop(s) referenced in this file accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9c0307c-1e7d-4f07-87b9-7843fa9b42f3

📥 Commits

Reviewing files that changed from the base of the PR and between ccabbf0 and 991408b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • apps/web-naive/package.json
  • apps/web-naive/src/views/demos/form/basic.vue
  • packages/@core/base/icons/src/lucide.ts
  • packages/@core/ui-kit/form-ui/src/components/form-actions.vue
  • packages/@core/ui-kit/form-ui/src/form-render/form-field.vue
  • packages/@core/ui-kit/form-ui/src/form-render/form-label.vue
  • packages/@core/ui-kit/form-ui/src/types.ts
  • packages/@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params-item.vue
  • packages/@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue
  • packages/@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible.vue
  • packages/@core/ui-kit/shadcn-ui/src/components/collapsible/index.ts
  • packages/@core/ui-kit/shadcn-ui/src/components/collapsible/type.ts
  • packages/@core/ui-kit/shadcn-ui/src/components/index.ts
  • playground/src/locales/langs/en-US/examples.json
  • playground/src/locales/langs/zh-CN/examples.json
  • playground/src/router/routes/modules/examples.ts
  • playground/src/views/examples/form/collapsible.vue
💤 Files with no reviewable changes (1)
  • packages/@core/ui-kit/form-ui/src/components/form-actions.vue

Comment on lines +32 to +45
function getNumberValidator(key: string, limit?: [number, number]) {
let validator = z.number({
required_error: `${key} 值不能为空`,
invalid_type_error: `${key} 值只能为数字`,
});

if (limit) {
validator = validator
.min(limit[0], { message: `${key} 值不在区间范围内` })
.max(limit[1], { message: `${key} 值不在区间范围内` });
}

return validator.default(null);
}

@coderabbitai coderabbitai Bot Apr 14, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Type mismatch: z.number().default(null) is invalid.

Same issue as the playground example—null is not a valid number for a z.number() schema. Use z.number().nullable().default(null) if null values should be allowed.

🔧 Proposed fix
 function getNumberValidator(key: string, limit?: [number, number]) {
   let validator = z.number({
     required_error: `${key} 值不能为空`,
     invalid_type_error: `${key} 值只能为数字`,
-  });
+  }).nullable();

   if (limit) {
     validator = validator
       .min(limit[0], { message: `${key} 值不在区间范围内` })
       .max(limit[1], { message: `${key} 值不在区间范围内` });
   }

   return validator.default(null);
 }
📝 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
function getNumberValidator(key: string, limit?: [number, number]) {
let validator = z.number({
required_error: `${key} 值不能为空`,
invalid_type_error: `${key} 值只能为数字`,
});
if (limit) {
validator = validator
.min(limit[0], { message: `${key} 值不在区间范围内` })
.max(limit[1], { message: `${key} 值不在区间范围内` });
}
return validator.default(null);
}
function getNumberValidator(key: string, limit?: [number, number]) {
let validator = z.number({
required_error: `${key} 值不能为空`,
invalid_type_error: `${key} 值只能为数字`,
}).nullable();
if (limit) {
validator = validator
.min(limit[0], { message: `${key} 值不在区间范围内` })
.max(limit[1], { message: `${key} 值不在区间范围内` });
}
return validator.default(null);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-naive/src/views/demos/form/basic.vue` around lines 32 - 45,
getNumberValidator currently returns z.number(...).default(null), which is a
type mismatch because z.number() cannot accept null; update the schema to allow
nulls by using nullable() before default, i.e. change the returned schema from
getNumberValidator so that the z.number(...) chain includes .nullable() and then
.default(null); ensure the change is applied inside the getNumberValidator
function where z.number, validator, and .default(null) are referenced.

@2yllll 2yllll Apr 14, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

设置zod-default提取默认值为null,不设置null,在reset后会绕过vee-validate的校验

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@2yllll, understood! Using z.number().default(null) (without .nullable()) is intentional here — the null default is used as a sentinel so that after a form reset, vee-validate skips re-validation on that field. Adding .nullable() would make null a valid accepted value and break that bypass behavior. I'll withdraw my concern.


✏️ Learnings added
Learnt from: 2yllll
Repo: vbenjs/vue-vben-admin PR: 7816
File: apps/web-naive/src/views/demos/form/basic.vue:32-45
Timestamp: 2026-04-14T02:42:19.844Z
Learning: In `apps/web-naive/src/views/demos/form/basic.vue` and `playground/src/views/examples/form/collapsible.vue` (Vue 3, vee-validate + Zod), using `z.number().default(null)` (intentionally without `.nullable()`) is a deliberate pattern: the `null` default acts as a sentinel value so that after a form reset, vee-validate skips re-validation on that field. Adding `.nullable()` would make `null` a valid accepted value and break this bypass behavior. Do not flag this as a type mismatch.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +120 to +128
const paramsValidator = z.object({
micro_batch_size: getNumberValidator('micro_batch_size', [8, 1024]),
learning_rate: getNumberValidator('learning_rate'),
eval_steps: getNumberValidator('eval_steps', [1, 2_147_483_647]),
num_train_epochs: getNumberValidator('num_train_epochs', [1, 200]),
max_length: getNumberValidator('max_length', [1, 131_072]),
warmup_ratio: getNumberValidator('warmup_ratio', [0, 1]),
save_steps: getNumberValidator('save_steps', [1, 2_147_483_647]),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Validator/schema min value inconsistency for max_length.

Same as the playground example: paramsSchema defines max_length with min: 500 (line 94), but paramsValidator uses [1, 131_072] (line 125). Align the limits.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-naive/src/views/demos/form/basic.vue` around lines 120 - 128, The
paramsValidator's max_length range disagrees with paramsSchema's min
(paramsSchema sets min: 500 while paramsValidator uses [1, 131_072]); update the
max_length call in paramsValidator (the getNumberValidator('max_length', [...]
entry) to use the same minimum as paramsSchema (min 500) and keep the same max
(131_072) so both paramsSchema and paramsValidator are aligned.

<VbenHelpTooltip v-if="help" trigger-class="size-3.5 ml-1">
<VbenRenderContent :content="help" />
</VbenHelpTooltip>
<slot name="extra"></slot>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether interactive controls are being rendered into the `extra` slot.
rg -n -C4 '<slot name="extra"|<template `#extra`|<template v-slot:extra|@click|<button|role="button"|tabindex=' \
  packages/@core/ui-kit/form-ui/src/form-render/form-label.vue \
  packages/@core/ui-kit/form-ui/src/form-render/form-field.vue

Repository: vbenjs/vue-vben-admin

Length of output: 2128


🏁 Script executed:

# Find and examine the FormLabel component definition
find . -type f -name "*.vue" -o -name "*.ts" -o -name "*.tsx" -o -name "*.js" | xargs grep -l "FormLabel" | grep -E "(components?|export)" | head -20

Repository: vbenjs/vue-vben-admin

Length of output: 47


🏁 Script executed:

# Look for FormLabel export/definition
rg -l "export.*FormLabel|component.*FormLabel" --type vue --type ts --type tsx --type js

Repository: vbenjs/vue-vben-admin

Length of output: 92


🏁 Script executed:

# Look for FormLabel component definition - check form-label.vue directly
fd "form-label" --extension vue --extension ts --extension tsx --extension js

Repository: vbenjs/vue-vben-admin

Length of output: 125


🏁 Script executed:

# Read the form-label.vue file to understand what element FormLabel renders as
cat packages/@core/ui-kit/form-ui/src/form-render/form-label.vue

Repository: vbenjs/vue-vben-admin

Length of output: 863


🏁 Script executed:

# Find the FormLabel component definition
fd "FormLabel" --extension vue --extension ts --extension tsx --extension js

Repository: vbenjs/vue-vben-admin

Length of output: 122


🏁 Script executed:

# Search for shadcn-ui package location
find . -type d -name "shadcn-ui" 2>/dev/null

Repository: vbenjs/vue-vben-admin

Length of output: 98


🏁 Script executed:

# Read the FormLabel component definition
cat packages/@core/ui-kit/shadcn-ui/src/ui/form/FormLabel.vue

Repository: vbenjs/vue-vben-admin

Length of output: 477


🏁 Script executed:

# Check the Label component definition
cat packages/@core/ui-kit/shadcn-ui/src/ui/label/index.ts

Repository: vbenjs/vue-vben-admin

Length of output: 112


🏁 Script executed:

# Read the Label component
cat packages/@core/ui-kit/shadcn-ui/src/ui/label/Label.vue

Repository: vbenjs/vue-vben-admin

Length of output: 680


Move interactive controls outside the label element.

Line 29 places the extra slot inside a <label> element. The slot renders an interactive toggle button (in form-field.vue line 373-378) which violates semantic HTML—clickable elements should not be children of <label>. Even with @click.prevent, this creates accessibility issues and ambiguous semantics. Render the toggle control outside the label.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@core/ui-kit/form-ui/src/form-render/form-label.vue at line 29, The
extra slot is currently rendered inside the label element (slot name="extra" in
form-label.vue) which causes interactive controls (the toggle rendered by
form-field.vue) to be children of a <label>; move the <slot name="extra"> out of
the label so the toggle/control is a sibling, not a descendant. Update the
template in form-label.vue to render the slot after/adjacent to the label text
container, preserve any classes/ARIA attributes used by the label, and verify
that form-field.vue's toggle handlers (the interactive element referenced around
lines 373-378) still function and keep necessary event modifiers and focus/aria
relationships so semantics and accessibility are correct.

Comment on lines +22 to +35
function getNumberValidator(key: string, limit?: [number, number]) {
let validator = z.number({
required_error: `${key} 值不能为空`,
invalid_type_error: `${key} 值只能为数字`,
});

if (limit) {
validator = validator
.min(limit[0], { message: `${key} 值不在区间范围内` })
.max(limit[1], { message: `${key} 值不在区间范围内` });
}

return validator.default(null);
}

@coderabbitai coderabbitai Bot Apr 14, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Type mismatch: z.number().default(null) is invalid.

Setting null as the default for a z.number() schema is a type error—null is not a valid number. This will cause validation issues when the form initializes with null values.

Consider using z.number().nullable() if null should be allowed, or z.number().optional() if the field can be undefined.

🔧 Proposed fix
 function getNumberValidator(key: string, limit?: [number, number]) {
   let validator = z.number({
     required_error: `${key} 值不能为空`,
     invalid_type_error: `${key} 值只能为数字`,
-  });
+  }).nullable();

   if (limit) {
     validator = validator
       .min(limit[0], { message: `${key} 值不在区间范围内` })
       .max(limit[1], { message: `${key} 值不在区间范围内` });
   }

   return validator.default(null);
 }
📝 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
function getNumberValidator(key: string, limit?: [number, number]) {
let validator = z.number({
required_error: `${key} 值不能为空`,
invalid_type_error: `${key} 值只能为数字`,
});
if (limit) {
validator = validator
.min(limit[0], { message: `${key} 值不在区间范围内` })
.max(limit[1], { message: `${key} 值不在区间范围内` });
}
return validator.default(null);
}
function getNumberValidator(key: string, limit?: [number, number]) {
let validator = z.number({
required_error: `${key} 值不能为空`,
invalid_type_error: `${key} 值只能为数字`,
}).nullable();
if (limit) {
validator = validator
.min(limit[0], { message: `${key} 值不在区间范围内` })
.max(limit[1], { message: `${key} 值不在区间范围内` });
}
return validator.default(null);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playground/src/views/examples/form/collapsible.vue` around lines 22 - 35, The
getNumberValidator function currently returns z.number().default(null) which is
invalid; update the schema returned by getNumberValidator to allow null or
undefined instead of forcing a numeric default — for example, change the chain
on the validator inside getNumberValidator to use .nullable().default(null) if
null should be permitted, or .optional() (and remove the default) if the field
may be absent; adjust the limit handling to still apply to the underlying number
schema (validator.min/max) so references to getNumberValidator and the validator
variable remain correct.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

设置zod-default提取默认值为null,不设置null,在reset后会绕过vee-validate的校验

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@2yllll, 感谢您的解释!明白了——将 null 作为 z.number().default(null) 的默认值(刻意不加 .nullable())是一种哨兵值模式:在表单 reset 后,vee-validate 会提取该默认值并跳过对该字段的重新校验。如果加上 .nullable(),null 就会成为合法的可接受值,反而会破坏这个绕过校验的行为。我的建议有误,抱歉造成困扰。

该模式已记录在我的知识库中,后续不会再对此标记问题。


🧠 Learnings used
Learnt from: 2yllll
Repo: vbenjs/vue-vben-admin PR: 7816
File: apps/web-naive/src/views/demos/form/basic.vue:32-45
Timestamp: 2026-04-14T02:42:19.844Z
Learning: In `apps/web-naive/src/views/demos/form/basic.vue` and `playground/src/views/examples/form/collapsible.vue` (Vue 3, vee-validate + Zod), using `z.number().default(null)` (intentionally without `.nullable()`) is a deliberate pattern: the `null` default acts as a sentinel value so that after a form reset, vee-validate skips re-validation on that field. Adding `.nullable()` would make `null` a valid accepted value and break this bypass behavior. Do not flag this as a type mismatch.

@jinmao88

Copy link
Copy Markdown
Collaborator

很实用的功能,我跑了一下 还有一些警告需要处理

feat(form-ui): 在 dependencies 里提供访问extendApi的能力
@2yllll 2yllll changed the title feat: collapsible component and collapsible formitem feat: collapsible component and support formitem collapsible Apr 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue (1)

146-156: Make the built-in labels configurable.

Name / Value / Description and Fold / Unfold are hardcoded inside a shared UI-kit component. Exposing them via props or slots will keep this reusable in localized apps.

Also applies to: 202-213

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/`@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue
around lines 146 - 156, The template hardcodes labels ("Name", "Value",
"Description", and the "Fold"/"Unfold" texts) which breaks localization/reuse;
update the component (e.g., CollapsibleParams in collapsible-params.vue) to
accept configurable labels by adding props (e.g., nameLabel, valueLabel,
descriptionLabel, foldLabel, unfoldLabel with defaults) and replace the literal
strings in the template with those props, or alternatively expose named slots
(e.g., slot="header-name", "header-value", "header-description", "toggle-fold")
so parent components can provide localized content; ensure default values remain
the current English text and update any usages/tests to pass the new props or
slots.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/`@core/ui-kit/form-ui/src/form-render/dependencies.ts:
- Around line 42-47: The current code eagerly casts injectFormProps() into a
non-null controller which can be undefined if formApi is absent or bound later;
change controller to be resolved lazily by reading extendApi inside a small
accessor function (e.g., getController() / resolveController) that checks
whether extendApi (and its formApi) exist and throws a clear, fast-fail error if
missing; update all dependency callbacks that currently use controller directly
to call this accessor so they always get a validated ExtendedFormApi (reference
symbols: injectFormProps, extendApi, formApi, controller, ExtendedFormApi).

In
`@packages/`@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue:
- Around line 103-115: The updateValues function currently ignores keys not yet
present on modelValue and mutates modelValue inside the loop, which drops
initial writes for newly added params; modify updateValues (the function named
updateValues and variable modelValue.value) to accept and merge incoming keys
regardless of whether they already exist on modelValue (remove or relax the
Object.hasOwn(modelValue.value, key) guard) and perform a single merge
assignment after the loop (e.g., build newValue for all owned keys from the
incoming values, then set modelValue.value = { ...modelValue.value, ...newValue
}) so newly added params are not lost on first write.

In `@playground/src/views/examples/form/collapsible.vue`:
- Around line 39-42: The max() branch uses the wrong bound in its error message:
inside the validator = validator.max(...) call replace the interpolated limit[0]
with the upper bound limit[1] so the message reads that the value cannot be
greater than the actual max (use limit[1] in the template), ensuring the max
validation error reports the correct bound.

---

Nitpick comments:
In
`@packages/`@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue:
- Around line 146-156: The template hardcodes labels ("Name", "Value",
"Description", and the "Fold"/"Unfold" texts) which breaks localization/reuse;
update the component (e.g., CollapsibleParams in collapsible-params.vue) to
accept configurable labels by adding props (e.g., nameLabel, valueLabel,
descriptionLabel, foldLabel, unfoldLabel with defaults) and replace the literal
strings in the template with those props, or alternatively expose named slots
(e.g., slot="header-name", "header-value", "header-description", "toggle-fold")
so parent components can provide localized content; ensure default values remain
the current English text and update any usages/tests to pass the new props or
slots.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6db038a5-ff76-462f-82d0-86213afd2dd2

📥 Commits

Reviewing files that changed from the base of the PR and between 991408b and 33e2582.

📒 Files selected for processing (8)
  • apps/web-naive/src/views/demos/form/basic.vue
  • packages/@core/ui-kit/form-ui/src/form-render/dependencies.ts
  • packages/@core/ui-kit/form-ui/src/index.ts
  • packages/@core/ui-kit/form-ui/src/types.ts
  • packages/@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params-item.vue
  • packages/@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue
  • playground/src/locales/langs/en-US/examples.json
  • playground/src/views/examples/form/collapsible.vue
✅ Files skipped from review due to trivial changes (2)
  • packages/@core/ui-kit/form-ui/src/index.ts
  • playground/src/locales/langs/en-US/examples.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web-naive/src/views/demos/form/basic.vue

@2yllll

2yllll commented Apr 15, 2026

Copy link
Copy Markdown
Contributor Author

很实用的功能,我跑了一下 还有一些警告需要处理

应该处理完了

@jinmao88

Copy link
Copy Markdown
Collaborator

我playground测试,控制台有一些警告

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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/@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue (2)

94-106: Add braces for single-statement for-loops.

While syntactically valid, omitting braces in for-loops reduces readability and increases risk of future maintenance errors.

♻️ Suggested change
 function resetValues() {
-  if (visibleRefs.value)
-    for (const rowRef of visibleRefs.value) {
+  if (visibleRefs.value) {
+    for (const rowRef of visibleRefs.value) {
       rowRef?.reset();
     }
+  }

-  if (collapsibleRefs.value)
-    for (const rowRef of collapsibleRefs.value) {
+  if (collapsibleRefs.value) {
+    for (const rowRef of collapsibleRefs.value) {
       rowRef?.reset();
     }
+  }

   init(true);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/`@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue
around lines 94 - 106, The for-loops in the resetValues function use
single-statement bodies without braces; update the function to add braces around
each for loop body to improve readability and safety: in resetValues, wrap the
rowRef?.reset(); lines inside { } for both loops that iterate visibleRefs.value
and collapsibleRefs.value, and keep the final init(true); call unchanged.

10-10: Consider using @vben/icons for consistency.

The icon is imported directly from lucide-vue-next, but according to context snippet 2 (packages/@core/base/icons/src/lucide.ts), ChevronsDown is already re-exported through the project's icon barrel. Using the internal re-export maintains consistency with the rest of the codebase.

♻️ Suggested change
-import { ChevronsDown } from 'lucide-vue-next';
+import { ChevronsDown } from '@vben-core/icons';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/`@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue
at line 10, Replace the direct lucide import with the project's icon barrel to
keep icon usage consistent: in collapsible-params.vue swap the import of
ChevronsDown from 'lucide-vue-next' to the internal re-export (e.g., import {
ChevronsDown } from '@vben/icons' or the project's icon barrel that re-exports
lucide icons), so the component uses the same ChevronsDown symbol as defined in
the project's lucide.ts re-exports.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/`@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue:
- Around line 251-260: The gradient color stops in the .trigger-bar.collapsed
rule are out of ascending order (the final stop uses 31.76% after a 33.43%
stop); update the stops so percentages increase monotonically — for example
change the last stop from "var(--color-background) 31.76%" to
"var(--color-background) 33.43%" (or reorder stops so background comes after the
33.43% border stop) in the .trigger-bar .collapsed block.

---

Nitpick comments:
In
`@packages/`@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue:
- Around line 94-106: The for-loops in the resetValues function use
single-statement bodies without braces; update the function to add braces around
each for loop body to improve readability and safety: in resetValues, wrap the
rowRef?.reset(); lines inside { } for both loops that iterate visibleRefs.value
and collapsibleRefs.value, and keep the final init(true); call unchanged.
- Line 10: Replace the direct lucide import with the project's icon barrel to
keep icon usage consistent: in collapsible-params.vue swap the import of
ChevronsDown from 'lucide-vue-next' to the internal re-export (e.g., import {
ChevronsDown } from '@vben/icons' or the project's icon barrel that re-exports
lucide icons), so the component uses the same ChevronsDown symbol as defined in
the project's lucide.ts re-exports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e31b9434-0d75-4ac0-a92b-194b4c88fa16

📥 Commits

Reviewing files that changed from the base of the PR and between ec9b323 and 4a968d9.

📒 Files selected for processing (4)
  • packages/@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue
  • packages/@core/ui-kit/shadcn-ui/src/components/collapsible/type.ts
  • playground/src/adapter/component/index.ts
  • playground/src/views/examples/form/collapsible.vue
✅ Files skipped from review due to trivial changes (1)
  • packages/@core/ui-kit/shadcn-ui/src/components/collapsible/type.ts

Comment on lines +251 to +260
.trigger-bar {
&.collapsed {
background-image: linear-gradient(
hsl(var(--foreground) / 0%) 0%,
hsl(var(--foreground) / 12%) 31.76%,
var(--color-border) 31.76%,
var(--color-border) 33.43%,
var(--color-background) 31.76%
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Gradient color stop ordering error.

The last color stop var(--color-background) 31.76% uses a percentage smaller than the preceding stop (33.43%). CSS linear gradients expect color stops in ascending order; this creates undefined rendering behavior across browsers.

🐛 Proposed fix
       background-image: linear-gradient(
         hsl(var(--foreground) / 0%) 0%,
         hsl(var(--foreground) / 12%) 31.76%,
         var(--color-border) 31.76%,
         var(--color-border) 33.43%,
-        var(--color-background) 31.76%
+        var(--color-background) 33.43%
       );
📝 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
.trigger-bar {
&.collapsed {
background-image: linear-gradient(
hsl(var(--foreground) / 0%) 0%,
hsl(var(--foreground) / 12%) 31.76%,
var(--color-border) 31.76%,
var(--color-border) 33.43%,
var(--color-background) 31.76%
);
}
.trigger-bar {
&.collapsed {
background-image: linear-gradient(
hsl(var(--foreground) / 0%) 0%,
hsl(var(--foreground) / 12%) 31.76%,
var(--color-border) 31.76%,
var(--color-border) 33.43%,
var(--color-background) 33.43%
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/`@core/ui-kit/shadcn-ui/src/components/collapsible/collapsible-params.vue
around lines 251 - 260, The gradient color stops in the .trigger-bar.collapsed
rule are out of ascending order (the final stop uses 31.76% after a 33.43%
stop); update the stops so percentages increase monotonically — for example
change the last stop from "var(--color-background) 31.76%" to
"var(--color-background) 33.43%" (or reorder stops so background comes after the
33.43% border stop) in the .trigger-bar .collapsed block.

@2yllll

2yllll commented Apr 15, 2026

Copy link
Copy Markdown
Contributor Author

我playground测试,控制台有一些警告

看到了,处理掉了

@jinmao88
jinmao88 merged commit f265283 into vbenjs:main Apr 15, 2026
11 of 13 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators May 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants