Skip to content

feat: Add Cascader panel, n-cascader add render-tag props #7090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- Fix `n-menu`'s disabled style not working when parent node is set to `disabled` and child node has `type: "group"`, closes [#6792](https://github.com/tusen-ai/naive-ui/issues/6792)
- Fix `n-input-group-label` not injecting `formItemInjectionKey`, causing the `size` property to fail, and close [#7066](https://github.com/tusen-ai/naive-ui/issues/7066)
- Fix the issue of style confusion in `n-carousel` when there is only one image, close [#6476](https://github.com/tusen-ai/naive-ui/issues/6476)
- Fix the issue where the cursor could not be moved with the left and right arrow keys after entering text when the filterable option was enabled in the n-cascader component. close [#6934](https://github.com/tusen-ai/naive-ui/issues/6934)

### Features

Expand All @@ -34,6 +35,11 @@
- - `n-image` adds showPreview methods, closes [#6695](https://github.com/tusen-ai/naive-ui/issues/6695).
- `n-image-preview` `n-image-group` support being used independently.
- `n-input-otp` adds `focusOnChar` util method, closes [#7073](https://github.com/tusen-ai/naive-ui/issues/7073).
- 🌟 Adds `n-cascader-panel` component. close [#5347](https://github.com/tusen-ai/naive-ui/issues/5347), [#5901](https://github.com/tusen-ai/naive-ui/issues/5901)
- Add the description for the `isLeaf` parameter in the `CascaderOption Properties` of the `n-cascader` component. close [#6276](https://github.com/tusen-ai/naive-ui/issues/6276)
- The `n-cascader` and `n-cascader-panel` components now support using the arrow keys (up, down, left, right) to navigate through dropdown options, and the Enter key to select an option. close [#6852](https://github.com/tusen-ai/naive-ui/issues/6852)
- `n-cascader` adds `render-tag` prop. close [#6389](https://github.com/tusen-ai/naive-ui/issues/6389)
- `n-cascader` adds `consistent-menu-width` prop. close [#7059](https://github.com/tusen-ai/naive-ui/issues/7059)

## 2.42.0

Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- 修复 `n-menu` 在父节点设置 `disabled`,子节点为 `type: "group"` 的禁用样式失效,关闭 [#6792](https://github.com/tusen-ai/naive-ui/issues/6792)
- 修复 `n-input-group-label` 没有注入 `formItemInjectionKey`,导致 `size` 属性失效的问题,关闭 [#7066](https://github.com/tusen-ai/naive-ui/issues/7066)
- 修复 `n-carousel` 只有一张图的情況下样式错乱的问题,关闭 [#6476](https://github.com/tusen-ai/naive-ui/issues/6476)
- 修复 `n-cascader` 开启过滤filterable后,输入内容后,光标无法通过键盘左右键移动 [#6934](https://github.com/tusen-ai/naive-ui/issues/6934)

### Features

Expand All @@ -34,6 +35,11 @@
- `n-image` 增加 showPreview 方法,关闭 [#6695](https://github.com/tusen-ai/naive-ui/issues/6695)
- `n-image-preview`,`n-image-group` 支持单独使用
- `n-input-otp` 增加 `focusOnChar` 方法,关闭 [#7073](https://github.com/tusen-ai/naive-ui/issues/7073)
- 🌟 新增 `n-cascader-panel` 组件,关闭 [#5347](https://github.com/tusen-ai/naive-ui/issues/5347)、[#5901](https://github.com/tusen-ai/naive-ui/issues/5901)
- 增加 `n-cascader` 组件 `CascaderOption Properties` `isLeaf` 参数说明,关闭 [#6276](https://github.com/tusen-ai/naive-ui/issues/6276)
- `n-cascader` `n-cascader-panel` 组件支持上下左右键去定位下拉选择项,通过回车键选择选项,关闭 [#6852](https://github.com/tusen-ai/naive-ui/issues/6852)
- `n-cascader` 新增 `render-tag` 属性,关闭 [#6389](https://github.com/tusen-ai/naive-ui/issues/6389)
- `n-cascader` 新增 `consistent-menu-width` 属性,关闭 [#7059](https://github.com/tusen-ai/naive-ui/issues/7059)

## 2.42.0

Expand Down
55 changes: 55 additions & 0 deletions src/cascader/demos/enUS/cascader-panel.demo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<markdown>
# CascaderPanel.demo
</markdown>

<script lang="ts" setup>
import type { CascaderOption } from 'naive-ui'
import Flash16Regular from '@vicons/fluent/Flash16Regular'
import { ref } from 'vue'

function getOptions(depth = 2, iterator = 1, prefix = '') {
const length = 12
const options: CascaderOption[] = []
for (let i = 1; i <= length; ++i) {
if (iterator === 1) {
options.push({
value: `${i}`,
label: `${i}`,
disabled: i % 5 === 0,
children: getOptions(depth, iterator + 1, `${String(i)}`)
})
}
else if (iterator === depth) {
options.push({
value: `${prefix}-${i}`,
label: `${prefix}-${i}`,
disabled: i % 5 === 0
})
}
else {
options.push({
value: `${prefix}-${i}`,
label: `${prefix}-${i}`,
disabled: i % 5 === 0,
children: getOptions(depth, iterator + 1, `${prefix}-${i}`)
})
}
}
return options
}

const value = ref(null)
const options = getOptions()
</script>

<template>
<span>{{ value }}</span>
<NCascaderPanel v-model:value="value" :options="options">
<template #action>
Standing on a bridge that can divide the world
</template>
<template #arrow>
<Flash16Regular />
</template>
</NCascaderPanel>
</template>
53 changes: 52 additions & 1 deletion src/cascader/demos/enUS/index.demo-entry.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ custom-field.vue
custom-render.vue
focus.vue
status.vue
cascader-panel.vue
render-tag.vue
```

## API
Expand All @@ -25,6 +27,7 @@ status.vue

| Name | Type | Default | Description | Version |
| --- | --- | --- | --- | --- |
| consistent-menu-width | `boolean` | `true` | Whether the menu keeps its width the same as the select trigger element. Setting it to `false` will also disable `virtual-scroll`. | NEXT_VERSION |
| allow-checking-not-loaded | `boolean` | `false` | Whether to allow cascade checking on not loaded nodes. If you want to use this, you should know the `value` may be incomplete. Also, you should aware about the consistency bewteen naive's checking logic and your backend's checking logic, especially when there are disabled nodes. | 2.28.1 |
| cascade | `boolean` | `true` | Whether to cascade the checkbox selection onto children. | |
| check-strategy | `string` | `'all'` | The way to show checked options. `all` means showing all checked node. `parent` means showing all checked parent node when all child node are checked (not working in single select mode). `child` means showing all child node. | |
Expand Down Expand Up @@ -52,6 +55,7 @@ status.vue
| render-prefix | `(info: { option: CascaderOption, node: VNode \| null, checked: boolean }) => VNodeChild` | `undefined` | Render function of all the options' prefix. | 2.38.2 |
| render-label | `(option: CascaderOption, checked: boolean) => VNodeChild` | `undefined` | Render function for cascader menu option label. | 2.24.0 |
| render-suffix | `(info: { option: CascaderOption, node: VNode \| null, checked: boolean }) => VNodeChild` | `undefined` | Render function of all the options' suffix. | 2.38.2 |
| render-tag | `(props: { option: SelectBaseOption, handleClose: () => void }) => VNodeChild` | `undefined` | Render function for each option tag. | NEXT_VERSION |
| separator | `string` | `' / '` | Selected option path value separator (used with `show-path`). | |
| show | `boolean` | `undefined` | Whether to show the menu. | |
| show-path | `boolean` | `true` | Whether to show the selected options as a path. | |
Expand All @@ -66,14 +70,45 @@ status.vue
| on-update:show | `(value: boolean) => void` | `undefined` | Callback executed when menu is opened & closed. | |
| on-update:value | `(value: string \| number \| Array<string \| number> \| null, option: CascaderOption \| Array<CascaderOption \| null> \| null, pathValues: Array<CascaderOption \| null> \| Array<CascaderOption[] \| null> \| null) => void` | `undefined` | Callback executed when the value changes. | |

#### CascaderOption Properties
### CascaderPanel Props

| Name | Type | Default | Description | Version |
| --- | --- | --- | --- | --- |
| allow-checking-not-loaded | `boolean` | `false` | Whether to allow cascade checking on not loaded nodes. If you want to use this, you should know the `value` may be incomplete. Also, you should aware about the consistency bewteen naive's checking logic and your backend's checking logic, especially when there are disabled nodes. | NEXT_VERSION |
| cascade | `boolean` | `true` | Whether to cascade the checkbox selection onto children. | NEXT_VERSION |
| check-strategy | `string` | `'all'` | The way to show checked options. `all` means showing all checked node. `parent` means showing all checked parent node when all child node are checked (not working in single select mode). `child` means showing all child node. | NEXT_VERSION |
| children-field | `string` | `'children'` | The children field in `CascaderOption`. | NEXT_VERSION |
| default-value | `string \| number \| Array<number \| string> \| null` | `null` | Data selected by default if no value is set. | NEXT_VERSION |
| disabled-field | `string` | `'disabled'` | The disabled field in `CascaderOption`. | NEXT_VERSION |
| expand-trigger | `'click' \| 'hover'` | `'click'` | If `remote` is set, `'hover'` won't work. | NEXT_VERSION |
| get-column-style | `(detail: { level: number }) => string \| object` | `undefined` | Function that resolves column style. `level` starts from `0`. | NEXT_VERSION |
| value-field | `string` | `'value'` | The value field in `CascaderOption`. | NEXT_VERSION |
| label-field | `string` | `'label'` | The label field in `CascaderOption`. | NEXT_VERSION |
| menu-props | `HTMLAttributes` | `undefined` | The menu's dom props. | NEXT_VERSION |
| multiple | `boolean` | `false` | Whether to allow multiple options being selected. | NEXT_VERSION |
| options | `CascaderOption[]` | `[]` | Options of the cascader. | NEXT_VERSION |
| remote | `boolean` | `false` | Whether to obtain data remotely. | NEXT_VERSION |
| render-prefix | `(info: { option: CascaderOption, node: VNode \| null, checked: boolean }) => VNodeChild` | `undefined` | Render function of all the options' prefix. | NEXT_VERSION |
| render-label | `(option: CascaderOption, checked: boolean) => VNodeChild` | `undefined` | Render function for cascader menu option label. | NEXT_VERSION |
| render-suffix | `(info: { option: CascaderOption, node: VNode \| null, checked: boolean }) => VNodeChild` | `undefined` | Render function of all the options' suffix. | NEXT_VERSION |
| separator | `string` | `' / '` | Selected option path value separator (used with `show-path`). | NEXT_VERSION |
| show-path | `boolean` | `true` | Whether to show the selected options as a path. | NEXT_VERSION |
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | Cascader size. | NEXT_VERSION |
| value | `string \| number \| Array<number \| string> \| null` | `undefined` | Value of the cascader (if being set manually). | NEXT_VERSION |
| virtual-scroll | `boolean` | `true` | Whether to enable virtual scrolling. | NEXT_VERSION |
| on-load | `(option: CascaderOption) => Promise<void>` | `undefined` | Callback when a node is loaded. Set `option.children` in the returned promise. Loading will stop after the promise is resolved or rejected. | NEXT_VERSION |
| on-update:show | `(value: boolean) => void` | `undefined` | Callback executed when menu is opened & closed. | NEXT_VERSION |
| on-update:value | `(value: string \| number \| Array<string \| number> \| null, option: CascaderOption \| Array<CascaderOption \| null> \| null, pathValues: Array<CascaderOption \| null> \| Array<CascaderOption[] \| null> \| null) => void` | `undefined` | Callback executed when the value changes. | NEXT_VERSION |

### CascaderOption Properties

| Name | Type | Description | Version |
| --- | --- | --- | --- |
| label | `string` | Label of the option. | |
| value | `string \| number` | Value of the option. | |
| disabled? | `boolean` | Whether this option is disabled. | |
| children? | `CascaderOption` | The children options of this option. | |
| isLeaf? | `boolean \| undefined` | Whether it is a "leaf node": `true` indicates that there is no child node, and clicking on it will not load or expand the next level. `false` indicates that there are child nodes, and clicking on it will try to expand the next level. | NEXT_VERSION |

### Cascader Slots

Expand All @@ -84,6 +119,15 @@ status.vue
| empty | `()` | Empty state slot for the options cascading menu. | 2.22.0 |
| not-found | `()` | Data not found slot when searching. | 2.34.0 |

### CascaderPanel Slots

| Name | Parameters | Description | Version |
| --- | --- | --- | --- |
| action | `()` | Action content displayed in the cascading menu. | NEXT_VERSION |
| arrow | `()` | Arrow content displayed in the cascading menu. | NEXT_VERSION |
| empty | `()` | Empty state slot for the options cascading menu. | NEXT_VERSION |
| not-found | `()` | Data not found slot when searching. | NEXT_VERSION |

### Cascader Methods

| Name | Type | Description | Version |
Expand All @@ -92,3 +136,10 @@ status.vue
| focus | `() => void` | Focus. | 2.24.2 |
| getCheckedData | `() => { keys: Array<string \| number>, options: Array<TreeOption \| null> }` | Get checked data. | 2.34.0 |
| getIndeterminateData | `() => { keys: Array<string \| number>, options: Array<TreeOption \| null> }` | Get indeterminate data. | 2.34.0 |

### CascaderPanel Methods

| Name | Type | Description | Version |
| --- | --- | --- | --- |
| getCheckedData | `() => { keys: Array<string \| number>, options: Array<TreeOption \| null> }` | Get checked data. | NEXT_VERSION |
| getIndeterminateData | `() => { keys: Array<string \| number>, options: Array<TreeOption \| null> }` | Get indeterminate data. | NEXT_VERSION |
67 changes: 67 additions & 0 deletions src/cascader/demos/enUS/menu-width.demo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<markdown>
# Menu width

The options menu width can be made to fit its content. Using this disables virtual scrolling.
</markdown>

<script lang="ts" setup>
import type { CascaderOption } from 'naive-ui'
import { ref } from 'vue'

function getOptions(depth = 2, iterator = 1, prefix = '') {
const length = 12
const options: CascaderOption[] = []
for (let i = 1; i <= length; ++i) {
if (iterator === 1) {
options.push({
value: `${i}`,
label: `Everybody\'s Got Something to Hide Except Me and My Monkey`,
disabled: i % 5 === 0,
children: getOptions(depth, iterator + 1, `${String(i)}`)
})
}
else if (iterator === depth) {
options.push({
value: `${prefix}-${i}`,
label: `${prefix}-${i}`,
disabled: i % 5 === 0
})
}
else {
options.push({
value: `${prefix}-${i}`,
label: `${prefix}-${i}`,
disabled: i % 5 === 0,
children: getOptions(depth, iterator + 1, `${prefix}-${i}`)
})
}
}
return options
}

const value = ref(null)
const options = getOptions()
</script>

<template>
<n-space vertical>
<n-cascader
v-model:value="value"
placeholder="没啥用的值"
:options="options"
check-strategy="child"
:consistent-menu-width="false"
:virtual-scroll="false"
/>

<n-cascader
v-model:value="value"
filterable
placeholder="没啥用的值"
:options="options"
check-strategy="child"
:consistent-menu-width="false"
:virtual-scroll="false"
/>
</n-space>
</template>
112 changes: 112 additions & 0 deletions src/cascader/demos/enUS/render-tag.demo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<markdown>
# Customize tag rendering

Give the tag a little color.
</markdown>

<script lang="ts" setup>
import type { CascaderOption, SelectRenderTag } from 'naive-ui'
import { NTag } from 'naive-ui'
import { h, ref } from 'vue'

const renderTag: SelectRenderTag = ({ option, handleClose }) => {
return h(
NTag,
{
type: 'success',
closable: true,
onMousedown: (e: FocusEvent) => {
e.preventDefault()
},
onClose: (e: MouseEvent) => {
e.stopPropagation()
handleClose()
}
},
{ default: () => option.label }
)
}

function getOptions(depth = 3, iterator = 1, prefix = '') {
const length = 12
const options: CascaderOption[] = []
for (let i = 1; i <= length; ++i) {
if (iterator === 1) {
options.push({
value: `v-${i}`,
label: `l-${i}`,
disabled: i % 5 === 0,
children: getOptions(depth, iterator + 1, `${String(i)}`)
})
}
else if (iterator === depth) {
options.push({
value: `v-${prefix}-${i}`,
label: `l-${prefix}-${i}`,
disabled: i % 5 === 0
})
}
else {
options.push({
value: `v-${prefix}-${i}`,
label: `l-${prefix}-${i}`,
disabled: i % 5 === 0,
children: getOptions(depth, iterator + 1, `${prefix}-${i}`)
})
}
}
return options
}

const checkStrategyIsChild = ref(true)
const cascade = ref(true)
const showPath = ref(true)
const hoverTrigger = ref(false)
const value = ref(null)
const filterable = ref(false)
const responsiveMaxTagCount = ref(true)
const clearFilterAfterSelect = ref(true)
const options = getOptions()

function handleUpdateValue(value: string[], options: CascaderOption[]) {
console.log(value, options)
}
</script>

<template>
<n-space vertical>
<n-space>
<n-space>
<n-switch v-model:value="checkStrategyIsChild" />Child Check Strategy
</n-space>
<n-space><n-switch v-model:value="cascade" />Cascade</n-space>
<n-space><n-switch v-model:value="showPath" />Show Path</n-space>
<n-space><n-switch v-model:value="hoverTrigger" />Hover Trigger</n-space>
<n-space><n-switch v-model:value="filterable" />Filterable</n-space>
<n-space>
<n-switch v-model:value="responsiveMaxTagCount" />Responsive MaxTagCount
</n-space>
<n-space>
<n-switch
v-model:value="clearFilterAfterSelect"
/>clearFilterAfterSelect
</n-space>
</n-space>
<n-cascader
v-model:value="value"
multiple
clearable
placeholder="没啥用的值"
:max-tag-count="responsiveMaxTagCount ? 'responsive' : undefined"
:expand-trigger="hoverTrigger ? 'hover' : 'click'"
:options="options"
:cascade="cascade"
:check-strategy="checkStrategyIsChild ? 'child' : 'all'"
:show-path="showPath"
:filterable="filterable"
:clear-filter-after-select="clearFilterAfterSelect"
:render-tag="renderTag"
@update:value="handleUpdateValue"
/>
</n-space>
</template>
Loading
Loading