diff --git a/breaking-changes.md b/breaking-changes.md
index 50ca14445..844e014b9 100644
--- a/breaking-changes.md
+++ b/breaking-changes.md
@@ -1,9 +1,24 @@
# 不兼容更新
-## Popup
+## 重命名徽标属性
-- `v-model` 调整为 `v-model:show`
+在之前的版本中,我们通过 info 属性来展示图标右上角的徽标信息,为了表达更符合社区的命名习惯,我们将这个属性重命名为 badge,影响以下组件:
-## Switch
+- Tab
+- Icon
+- GridItem
+- TreeSelect
+- TabbarItem
+- SidebarItem
+- GoodsActionIcon
-- v-model 对应的属性名和事件名由 `value/input` 调整为 `modelValue/update:modelValue`
+同时内部使用的 Info 组件也会重命名为 Badge。
+
+## v-model API 变更
+
+- Popup: `v-model` 调整为 `v-model:show`
+- Switch: v-model 对应的属性名和事件名由 `value/input` 调整为 `modelValue/update:modelValue`
+
+## 废弃个别组件
+
+- SwitchCell: 移除此组件,可以直接使用 Cell 和 Switch 组件代替
diff --git a/src-next/nav-bar/README.md b/src-next/nav-bar/README.md
new file mode 100644
index 000000000..0a6e7fe44
--- /dev/null
+++ b/src-next/nav-bar/README.md
@@ -0,0 +1,80 @@
+# NavBar
+
+### Install
+
+```js
+import Vue from 'vue';
+import { NavBar } from 'vant';
+
+Vue.use(NavBar);
+```
+
+## Usage
+
+### Basic Usage
+
+```html
+
+```
+
+```js
+import { Toast } from 'vant';
+
+export default {
+ methods: {
+ onClickLeft() {
+ Toast('Back');
+ },
+ onClickRight() {
+ Toast('Button');
+ },
+ },
+};
+```
+
+### Use Slot
+
+```html
+
+
+
+
+
+```
+
+## API
+
+### Props
+
+| Attribute | Description | Type | Default |
+| --- | --- | --- | --- |
+| title | Title | _string_ | `''` |
+| left-text | Left Text | _string_ | `''` |
+| right-text | Right Text | _string_ | `''` |
+| left-arrow | Whether to show left arrow | _boolean_ | `false` |
+| border | Whether to show bottom border | _boolean_ | `true` |
+| fixed | Whether to fixed top | _boolean_ | `false` |
+| placeholder `v2.5.9` | Whether to generage a placeholder element when fixed | _boolean_ | `false` |
+| z-index | Z-index | _number \| string_ | `1` |
+
+### Slots
+
+| Name | Description |
+| ----- | ------------------------- |
+| title | Custom title |
+| left | Custom left side content |
+| right | Custom right side content |
+
+### Events
+
+| Event | Description | Arguments |
+| ----------- | --------------------------------- | --------- |
+| click-left | Triggered when click left button | - |
+| click-right | Triggered when click right button | - |
diff --git a/src-next/nav-bar/README.zh-CN.md b/src-next/nav-bar/README.zh-CN.md
new file mode 100644
index 000000000..100999759
--- /dev/null
+++ b/src-next/nav-bar/README.zh-CN.md
@@ -0,0 +1,82 @@
+# NavBar 导航栏
+
+### 引入
+
+```js
+import Vue from 'vue';
+import { NavBar } from 'vant';
+
+Vue.use(NavBar);
+```
+
+## 代码演示
+
+### 基础用法
+
+```html
+
+```
+
+```js
+import { Toast } from 'vant';
+
+export default {
+ methods: {
+ onClickLeft() {
+ Toast('返回');
+ },
+ onClickRight() {
+ Toast('按钮');
+ },
+ },
+};
+```
+
+### 使用插槽
+
+通过插槽自定义导航栏两侧的内容
+
+```html
+
+
+
+
+
+```
+
+## API
+
+### Props
+
+| 参数 | 说明 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| title | 标题 | _string_ | `''` |
+| left-text | 左侧文案 | _string_ | `''` |
+| right-text | 右侧文案 | _string_ | `''` |
+| left-arrow | 是否显示左侧箭头 | _boolean_ | `false` |
+| border | 是否显示下边框 | _boolean_ | `true` |
+| fixed | 是否固定在顶部 | _boolean_ | `false` |
+| placeholder `v2.5.9` | 固定在顶部时,是否在标签位置生成一个等高的占位元素 | _boolean_ | `false` |
+| z-index | 元素 z-index | _number \| string_ | `1` |
+
+### Slots
+
+| 名称 | 说明 |
+| ----- | ------------------ |
+| title | 自定义标题 |
+| left | 自定义左侧区域内容 |
+| right | 自定义右侧区域内容 |
+
+### Events
+
+| 事件名 | 说明 | 回调参数 |
+| ----------- | ------------------ | -------- |
+| click-left | 点击左侧按钮时触发 | - |
+| click-right | 点击右侧按钮时触发 | - |
diff --git a/src-next/nav-bar/demo/index.vue b/src-next/nav-bar/demo/index.vue
new file mode 100644
index 000000000..cededbc20
--- /dev/null
+++ b/src-next/nav-bar/demo/index.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src-next/nav-bar/index.js b/src-next/nav-bar/index.js
new file mode 100644
index 000000000..76b73d12f
--- /dev/null
+++ b/src-next/nav-bar/index.js
@@ -0,0 +1,105 @@
+// Utils
+import { createNamespace } from '../utils';
+import { BORDER_BOTTOM } from '../utils/constant';
+
+// Components
+import Icon from '../icon';
+
+const [createComponent, bem] = createNamespace('nav-bar');
+
+export default createComponent({
+ props: {
+ title: String,
+ fixed: Boolean,
+ zIndex: [Number, String],
+ leftText: String,
+ rightText: String,
+ leftArrow: Boolean,
+ placeholder: Boolean,
+ border: {
+ type: Boolean,
+ default: true,
+ },
+ },
+
+ emits: ['click-left', 'click-right'],
+
+ data() {
+ return {
+ height: null,
+ };
+ },
+
+ mounted() {
+ if (this.placeholder && this.fixed) {
+ this.height = this.$refs.navBar.getBoundingClientRect().height;
+ }
+ },
+
+ methods: {
+ genLeft() {
+ const leftSlot = this.$slots.left?.();
+
+ if (leftSlot) {
+ return leftSlot;
+ }
+
+ return [
+ this.leftArrow && ,
+ this.leftText && {this.leftText},
+ ];
+ },
+
+ genRight() {
+ const rightSlot = this.$slots.right?.();
+
+ if (rightSlot) {
+ return rightSlot;
+ }
+
+ if (this.rightText) {
+ return {this.rightText};
+ }
+ },
+
+ genNavBar() {
+ return (
+
+
+ {this.genLeft()}
+
+
+ {this.$slots.title ? this.$slots.title() : this.title}
+
+
+ {this.genRight()}
+
+
+ );
+ },
+
+ onClickLeft(event) {
+ this.$emit('click-left', event);
+ },
+
+ onClickRight(event) {
+ this.$emit('click-right', event);
+ },
+ },
+
+ render() {
+ if (this.placeholder && this.fixed) {
+ return (
+
+ {this.genNavBar()}
+
+ );
+ }
+
+ return this.genNavBar();
+ },
+});
diff --git a/src-next/nav-bar/index.less b/src-next/nav-bar/index.less
new file mode 100644
index 000000000..582a0a82a
--- /dev/null
+++ b/src-next/nav-bar/index.less
@@ -0,0 +1,66 @@
+@import '../style/var';
+
+.van-nav-bar {
+ position: relative;
+ z-index: @nav-bar-z-index;
+ display: flex;
+ align-items: center;
+ height: @nav-bar-height;
+ line-height: 1.5;
+ text-align: center;
+ background-color: @nav-bar-background-color;
+ user-select: none;
+
+ .van-icon {
+ color: @nav-bar-icon-color;
+ }
+
+ &__arrow {
+ min-width: 1em;
+ margin-right: @padding-base;
+ font-size: @nav-bar-arrow-size;
+ }
+
+ &--fixed {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ }
+
+ &__title {
+ max-width: 60%;
+ margin: 0 auto;
+ color: @nav-bar-title-text-color;
+ font-weight: @font-weight-bold;
+ font-size: @nav-bar-title-font-size;
+ }
+
+ &__left,
+ &__right {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ padding: 0 @padding-md;
+ font-size: @font-size-md;
+ cursor: pointer;
+
+ &:active {
+ opacity: @active-opacity;
+ }
+ }
+
+ &__left {
+ left: 0;
+ }
+
+ &__right {
+ right: 0;
+ }
+
+ &__text {
+ color: @nav-bar-text-color;
+ }
+}
diff --git a/src-next/nav-bar/test/__snapshots__/demo.spec.js.snap b/src-next/nav-bar/test/__snapshots__/demo.spec.js.snap
new file mode 100644
index 000000000..bcd8a6ba2
--- /dev/null
+++ b/src-next/nav-bar/test/__snapshots__/demo.spec.js.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders demo correctly 1`] = `
+
+`;
diff --git a/src-next/nav-bar/test/__snapshots__/index.spec.js.snap b/src-next/nav-bar/test/__snapshots__/index.spec.js.snap
new file mode 100644
index 000000000..263a07ecc
--- /dev/null
+++ b/src-next/nav-bar/test/__snapshots__/index.spec.js.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`placeholder prop 1`] = `
+
+`;
+
+exports[`render left & right slot 1`] = `
+
+
Custom Left
+
+
Custom Right
+
+`;
+
+exports[`render title slot 1`] = `
+
+`;
diff --git a/src-next/nav-bar/test/demo.spec.js b/src-next/nav-bar/test/demo.spec.js
new file mode 100644
index 000000000..5c70922b5
--- /dev/null
+++ b/src-next/nav-bar/test/demo.spec.js
@@ -0,0 +1,4 @@
+import Demo from '../demo';
+import { snapshotDemo } from '../../../test/demo';
+
+snapshotDemo(Demo);
diff --git a/src-next/nav-bar/test/index.spec.js b/src-next/nav-bar/test/index.spec.js
new file mode 100644
index 000000000..63149c6e0
--- /dev/null
+++ b/src-next/nav-bar/test/index.spec.js
@@ -0,0 +1,60 @@
+import NavBar from '..';
+import { mount, mockGetBoundingClientRect } from '../../../test';
+
+test('render left & right slot', () => {
+ const wrapper = mount(NavBar, {
+ scopedSlots: {
+ left: () => 'Custom Left',
+ right: () => 'Custom Right',
+ },
+ });
+
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('render title slot', () => {
+ const wrapper = mount(NavBar, {
+ scopedSlots: {
+ title: () => 'Custom Title',
+ },
+ });
+
+ expect(wrapper).toMatchSnapshot();
+});
+
+test('placeholder prop', () => {
+ const restore = mockGetBoundingClientRect({ height: 50 });
+
+ const wrapper = mount(NavBar, {
+ propsData: {
+ fixed: true,
+ placeholder: true,
+ },
+ });
+
+ expect(wrapper).toMatchSnapshot();
+
+ restore();
+});
+
+test('click-left event', () => {
+ const wrapper = mount(NavBar, {
+ propsData: {
+ leftText: 'left',
+ },
+ });
+
+ wrapper.find('.van-nav-bar__left').trigger('click');
+ expect(wrapper.emitted('click-left')).toBeTruthy();
+});
+
+test('click-right event', () => {
+ const wrapper = mount(NavBar, {
+ propsData: {
+ rightText: 'right',
+ },
+ });
+
+ wrapper.find('.van-nav-bar__right').trigger('click');
+ expect(wrapper.emitted('click-right')).toBeTruthy();
+});
diff --git a/vant.config.js b/vant.config.js
index b7db042f7..e81e090aa 100644
--- a/vant.config.js
+++ b/vant.config.js
@@ -294,10 +294,10 @@ module.exports = {
// path: 'index-bar',
// title: 'IndexBar 索引栏',
// },
- // {
- // path: 'nav-bar',
- // title: 'NavBar 导航栏',
- // },
+ {
+ path: 'nav-bar',
+ title: 'NavBar 导航栏',
+ },
// {
// path: 'pagination',
// title: 'Pagination 分页',
@@ -432,10 +432,10 @@ module.exports = {
path: 'col',
title: 'Layout',
},
- // {
- // path: 'popup',
- // title: 'Popup',
- // },
+ {
+ path: 'popup',
+ title: 'Popup',
+ },
{
path: 'style',
title: 'Built-in style',
@@ -628,10 +628,10 @@ module.exports = {
// path: 'index-bar',
// title: 'IndexBar',
// },
- // {
- // path: 'nav-bar',
- // title: 'NavBar',
- // },
+ {
+ path: 'nav-bar',
+ title: 'NavBar',
+ },
// {
// path: 'pagination',
// title: 'Pagination',