ソースを参照

feat(searchbar): 新增自定义设置清除图标功能, 新增聚焦时自定义样式 (#1618)

* fix: countup优化滚动逻辑

* fix: collapse 无法动态更新问题修复

* fix: 解决H5侧动态加载问题

* docs: 文档增加调试功能(Barrage、Signature、CountUp、TextArea、Collapse)

* feat: 折叠面板 collapse 单元测试

* feat: countUp 单元测试

* feat: barrage 单元测试

* fix: 签名组件单元测试

* feat: textarea 单元测试

* feat: collapse 标题多行展示,无内容不下拉,图标位置配置

* feat: textarea 自动撑开,collapse 组件能力

* fix: collapse 单元测试优化

* fix: textarea 单元测试优化

* fix: 修改input参数,maxNum 改为 maxLength,文档修改等

* fix: maxlength 值

* fix: textarea 自适应

* fix: textarea rows

* upd: notify 增加组件调用方式

* fix: demo 修改

* feat: 新增collapse组件自定义内容(不折叠)功能

* fix: notify 单元测试优化

* feat: searchbar 组件能力补充

* feat: collapse,layout 组件国际化

* upd: countup、barrage、signature、Skeleton组件国际化

* fix: demo及文档修改,组件优化

* feat: textarea 增加autofocus、disabled等属性,增加点击区域事件

* fix: 解决 flexwrap 问题

* fix: 文档修改

* fix: 优化

* fix: collaspe 数据更新支持外部调用

* fix: demo

* feat: 修复折叠面板手风琴模式

* fix: 修复notify标签式展示报错问题及class类名问题

* fix: 解决签名组件生产环境下 getContext 报错问题

* fix: 适配textarea小程序自适应高度功能

* fix: 折叠面板优化

* feat: 组件支持暗黑模式

* feat(signature): signature 组件增加签名开始,结束,进行中回调函数

* docs: 文档修改

* fix: textarea 适配支付宝小程序

* feat: searchbar 新增自定义清除图标功能

* feat: 新增聚焦自定义样式

Co-authored-by: richard1015 <51844712@qq.com>
Ymm 3 年 前
コミット
d0d06d79a6

+ 12 - 0
src/packages/__VUE/searchbar/__tests__/searchbar.spec.ts

@@ -141,6 +141,17 @@ test('clear event test', async () => {
   expect(clear.exists()).toBe(true);
 });
 
+test('custom clear icon', async () => {
+  const wrapper = mount(SearchBar, {
+    props: {
+      clearIcon: 'close',
+      modelValue: 'test'
+    }
+  });
+  const input = wrapper.find('.nut-searchbar__input-clear i');
+  expect(input.classes()).toContain('nut-icon-close');
+});
+
 test('slot test', () => {
   const wrapper = mount(SearchBar, {
     props: { modelValue: '' },
@@ -163,5 +174,6 @@ test('slot test', () => {
       }
     }
   });
+
   expect(wrapper.html()).toMatchSnapshot();
 });

+ 17 - 5
src/packages/__VUE/searchbar/demo.vue

@@ -1,7 +1,8 @@
 <template>
   <div class="demo full">
     <h2>{{ translate('basic1') }}</h2>
-    <nut-searchbar v-model="searchValue" :label="translate('word1')"> </nut-searchbar>
+    <nut-searchbar v-model="searchValue" :label="translate('word1')" :focus-style="{ border: '1px solid red' }">
+    </nut-searchbar>
 
     <h2>{{ translate('basic2') }}</h2>
     <nut-searchbar v-model="searchValue1" @search="search"> </nut-searchbar>
@@ -26,6 +27,9 @@
     >
     </nut-searchbar>
 
+    <h2>{{ translate('basic7') }}</h2>
+    <nut-searchbar v-model="searchValue6" :clear-icon="icon2"> </nut-searchbar>
+
     <h2>{{ translate('basic6') }}</h2>
     <nut-searchbar v-model="searchValue5" @click-right-icon="clickRightIcon">
       <template v-slot:leftout>
@@ -59,6 +63,7 @@ const initTranslate = () =>
       basic4: '右侧添加搜索文字',
       basic5: '更改输入框内部及外部的背景样式',
       basic6: '显示全部 icon',
+      basic7: '自定义清除按钮 icon',
       word1: '标签',
       word2: '搜索',
       title3: '标题3'
@@ -70,6 +75,7 @@ const initTranslate = () =>
       basic4: 'Add search text to the right',
       basic5: 'Change the background style inside and outside the input box',
       basic6: 'Show all icons',
+      basic7: 'custom clear button icons',
       word1: 'label',
       word2: 'search'
     }
@@ -77,6 +83,9 @@ const initTranslate = () =>
 export default createDemo({
   props: {},
   setup() {
+    const icon = 'close';
+    const icon2 =
+      'https://img10.360buyimg.com/imagetools/jfs/t1/170133/30/22902/10546/61833626E32d7ccde/a7c373ba30de9a89.png';
     initTranslate();
     const state = reactive({
       searchValue: '',
@@ -84,24 +93,27 @@ export default createDemo({
       searchValue2: '',
       searchValue3: '',
       searchValue4: '',
-      searchValue5: ''
+      searchValue5: '',
+      searchValue6: ''
     });
 
-    const search = function (e: any) {
+    const search = function () {
       Toast.text('搜索触发');
     };
 
     const clickLeft = function () {
       Toast.text('点击回退按钮');
     };
-    const clickLeftIcon = function (a: string, b: any) {
+    const clickLeftIcon = function (a: string, b: Event) {
       console.log(a, b);
     };
-    const clickRightIcon = function (a: string, b: any) {
+    const clickRightIcon = function (a: string, b: Event) {
       console.log(a, b);
     };
 
     return {
+      icon,
+      icon2,
       clickLeft,
       clickLeftIcon,
       clickRightIcon,

+ 30 - 1
src/packages/__VUE/searchbar/doc.md

@@ -129,7 +129,7 @@ app.use(SearchBar).use(Icon);
 :::demo
 ```html
 <template>
-  <nut-searchbar v-model="searchValue4" background="linear-gradient(to right, #9866F0, #EB4D50)" input-background="#fff"> </nut-searchbar>
+  <nut-searchbar v-model="searchValue" background="linear-gradient(to right, #9866F0, #EB4D50)" input-background="#fff"> </nut-searchbar>
 </template>
 <script lang="ts">
   import { toRefs, reactive } from 'vue';
@@ -147,6 +147,33 @@ app.use(SearchBar).use(Icon);
 </script>
 ```
 :::
+
+### 自定义清除按钮 icon
+:::demo
+```html
+<template>
+  <nut-searchbar v-model="searchValue" :clear-icon="icon"> </nut-searchbar>
+</template>
+<script lang="ts">
+  import { toRefs, reactive } from 'vue';
+  export default {
+    setup() {
+      const state = reactive({
+        searchValue: ""
+      });
+      const icon =
+      'https://img10.360buyimg.com/imagetools/jfs/t1/170133/30/22902/10546/61833626E32d7ccde/a7c373ba30de9a89.png';
+
+      return {
+        ...toRefs(state),
+        icon
+      };
+    }
+  };
+</script>
+```
+:::
+
 ### 显示全部 icon
 :::demo
 ```html
@@ -195,11 +222,13 @@ app.use(SearchBar).use(Icon);
 | input-type    | 输入框类型   | String | 'text'      |
 | placeholder        | 输入框默认暗纹  | String | '请输入'   |
 | clearable          | 是否展示清除按钮 | Boolean | true     |
+| close-icon `v3.2.2`     | 自定义关闭图标 | String | 'circle-close'   |
 | background      | 输入框外部背景 | String |   '#fff'   |
 | input-background   | 输入框内部背景 | String |   '#f7f7f7'   |
 | confirm-type   | 键盘右下角按钮的文字(`仅支持小程序`),仅在`type='text'`时生效,可选值 `send`:发送、`search`:搜索、`next`:下一个、`go`:前往、`done`:完成 | String |   `done`   |
 | input-background   | 输入框内部背景 | String |   '#f7f7f7'   |
 | autofocus `v3.1.21` | 是否自动聚焦 | boolean | false |
+| focus-style `v3.2.2` | 聚焦时搜索框样式 | Object | - |
 | disabled `v3.1.21` | 是否禁用输入框 | boolean | false |
 | readonly `v3.1.21`| 输入框只读 | boolean | false |
 | input-align `v3.1.21`| 对齐方式,可选`center` `right` | string | `left` |

+ 2 - 0
src/packages/__VUE/searchbar/index.scss

@@ -26,6 +26,7 @@
   padding: $searchbar-padding;
   background: $searchbar-background;
   box-sizing: border-box;
+
   &__search-label {
     padding: 0 5px;
     color: #323233;
@@ -40,6 +41,7 @@
     border-radius: $searchbar-input-border-radius;
     box-shadow: $searchbar-input-box-shadow;
     background: $searchbar-input-background;
+    box-sizing: border-box;
 
     .nut-searchbar__input-inner {
       display: flex;

+ 33 - 13
src/packages/__VUE/searchbar/index.taro.vue

@@ -3,7 +3,7 @@
     <view v-if="$slots.leftout" class="nut-searchbar__search-icon nut-searchbar__left-search-icon">
       <slot name="leftout"></slot>
     </view>
-    <view class="nut-searchbar__search-input" :style="inputSearchbarStyle">
+    <view class="nut-searchbar__search-input" :style="{ ...inputSearchbarStyle, ...focusCss }">
       <view v-if="$slots.leftin" class="nut-searchbar__search-icon nut-searchbar__iptleft-search-icon">
         <slot name="leftin"></slot>
       </view>
@@ -24,11 +24,16 @@
             @focus="valueFocus"
             @blur="valueBlur"
             @confirm="handleSubmit"
-            :style="styleSearchbar"
+            :style="(styleSearchbar as CSSProperties)"
           />
         </form>
-        <view @click="handleClear" class="nut-searchbar__input-clear" v-if="clearable" v-show="modelValue.length > 0">
-          <nut-icon name="circle-close" size="12" color="#555"></nut-icon>
+        <view
+          @click="handleClear"
+          class="nut-searchbar__input-clear"
+          v-if="clearable"
+          v-show="(modelValue as string).length > 0"
+        >
+          <nut-icon :name="clearIcon" size="12" color="#555"></nut-icon>
         </view>
       </view>
       <view v-if="$slots.rightin" class="nut-searchbar__search-icon nut-searchbar__iptright-sarch-icon">
@@ -42,13 +47,13 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, computed, ref, onMounted, PropType } from 'vue';
+import { toRefs, reactive, computed, ref, onMounted, PropType, Ref, CSSProperties } from 'vue';
 import { createComponent } from '@/packages/utils/create';
 const { create, translate } = createComponent('searchbar');
-interface Events {
-  eventName: 'change' | 'focus' | 'blur' | 'clear' | 'update:modelValue';
-  params: (string | number | Event)[];
-}
+// interface Events {
+//   eventName: 'change' | 'focus' | 'blur' | 'clear' | 'update:modelValue';
+//   params: (string | number | Event)[];
+// }
 export type confirmTextType = 'send' | 'search' | 'next' | 'go' | 'done';
 
 export default create({
@@ -73,6 +78,10 @@ export default create({
       type: Boolean,
       default: true
     },
+    clearIcon: {
+      type: String,
+      default: 'circle-close'
+    },
     background: {
       type: String,
       default: ''
@@ -89,6 +98,11 @@ export default create({
       type: Boolean,
       default: false
     },
+    focusStyle: {
+      type: Object,
+      // eslint-disable-next-line @typescript-eslint/no-empty-function
+      default: () => {}
+    },
     disabled: {
       type: Boolean,
       default: false
@@ -142,10 +156,12 @@ export default create({
       emit('change', val, event);
     };
 
+    const focusCss = ref({});
     const valueFocus = (event: Event) => {
       const input = event.target as HTMLInputElement;
       let value = input.value;
       state.active = true;
+      focusCss.value = props.focusStyle;
       emit('focus', value, event);
     };
 
@@ -159,6 +175,7 @@ export default create({
       if (props.maxLength && value.length > Number(props.maxLength)) {
         value = value.slice(0, Number(props.maxLength));
       }
+      focusCss.value = {};
       emit('blur', value, event);
     };
 
@@ -184,15 +201,17 @@ export default create({
       emit('click-right-icon', props.modelValue, event);
     };
 
-    const styleSearchbar: any = computed(() => {
+    const styleSearchbar = computed(() => {
       return {
-        'text-align': props.inputAlign
+        style: {
+          textAlign: props.inputAlign
+        }
       };
     });
-    const inputsearch: any = ref(null);
+    const inputsearch: Ref<HTMLElement | null> = ref(null);
     onMounted(() => {
       if (props.autofocus) {
-        inputsearch.value.focus();
+        (inputsearch.value as HTMLElement).focus();
       }
     });
     return {
@@ -205,6 +224,7 @@ export default create({
       handleSubmit,
       searchbarStyle,
       inputSearchbarStyle,
+      focusCss,
       translate,
       clickInput,
       leftIconClick,

+ 31 - 14
src/packages/__VUE/searchbar/index.vue

@@ -4,7 +4,7 @@
     <view v-if="$slots.leftout" class="nut-searchbar__search-icon nut-searchbar__left-search-icon">
       <slot name="leftout"></slot>
     </view>
-    <view class="nut-searchbar__search-input" :style="inputSearchbarStyle">
+    <view class="nut-searchbar__search-input" :style="{ ...inputSearchbarStyle, ...focusCss }">
       <view
         v-if="$slots.leftin"
         class="nut-searchbar__search-icon nut-searchbar__iptleft-search-icon"
@@ -27,11 +27,16 @@
             @input="valueChange"
             @focus="valueFocus"
             @blur="valueBlur"
-            :style="styleSearchbar"
+            :style="(styleSearchbar as CSSProperties)"
           />
         </form>
-        <view @click="handleClear" class="nut-searchbar__input-clear" v-if="clearable" v-show="modelValue.length > 0">
-          <nut-icon name="circle-close" size="12" color="#555"></nut-icon>
+        <view
+          @click="handleClear"
+          class="nut-searchbar__input-clear"
+          v-if="clearable"
+          v-show="(modelValue as string).length > 0"
+        >
+          <nut-icon :name="clearIcon" size="12" color="#555"></nut-icon>
         </view>
       </view>
       <view
@@ -49,14 +54,13 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, computed, onMounted, ref } from 'vue';
+import { toRefs, reactive, computed, onMounted, ref, Ref, CSSProperties } from 'vue';
 import { createComponent } from '@/packages/utils/create';
-import { nextTick } from '@tarojs/taro';
 const { create, translate } = createComponent('searchbar');
-interface Events {
-  eventName: 'change' | 'focus' | 'blur' | 'clear' | 'update:modelValue';
-  params: (string | number | Event)[];
-}
+// interface Events {
+//   eventName: 'change' | 'focus' | 'blur' | 'clear' | 'update:modelValue';
+//   params: (string | number | Event)[];
+// }
 export default create({
   props: {
     modelValue: {
@@ -83,6 +87,10 @@ export default create({
       type: Boolean,
       default: true
     },
+    clearIcon: {
+      type: String,
+      default: 'circle-close'
+    },
     background: {
       type: String,
       default: ''
@@ -91,6 +99,11 @@ export default create({
       type: String,
       default: ''
     },
+    focusStyle: {
+      type: Object,
+      // eslint-disable-next-line @typescript-eslint/no-empty-function
+      default: () => {}
+    },
     autofocus: {
       type: Boolean,
       default: false
@@ -148,10 +161,12 @@ export default create({
       emit('change', val, event);
     };
 
+    const focusCss = ref({});
     const valueFocus = (event: Event) => {
       const input = event.target as HTMLInputElement;
       let value = input.value;
       state.active = true;
+      focusCss.value = props.focusStyle;
       emit('focus', value, event);
     };
 
@@ -165,6 +180,7 @@ export default create({
       if (props.maxLength && value.length > Number(props.maxLength)) {
         value = value.slice(0, Number(props.maxLength));
       }
+      focusCss.value = {};
       emit('blur', value, event);
     };
 
@@ -190,15 +206,15 @@ export default create({
       emit('click-right-icon', props.modelValue, event);
     };
 
-    const styleSearchbar: any = computed(() => {
+    const styleSearchbar = computed(() => {
       return {
-        'text-align': props.inputAlign
+        textAlign: props.inputAlign
       };
     });
-    const inputsearch: any = ref(null);
+    const inputsearch: Ref<HTMLElement | null> = ref(null);
     onMounted(() => {
       if (props.autofocus) {
-        inputsearch.value.focus();
+        (inputsearch.value as HTMLElement).focus();
       }
     });
 
@@ -212,6 +228,7 @@ export default create({
       handleSubmit,
       searchbarStyle,
       inputSearchbarStyle,
+      focusCss,
       translate,
       clickInput,
       leftIconClick,

+ 4 - 1
src/packages/__VUE/textarea/index.scss

@@ -42,10 +42,13 @@
     color: $textarea-text-color;
     text-align: left;
     background-color: transparent;
-    border: 0;
+    border: none;
     resize: none;
     line-height: 20px;
   }
+  &__ali {
+    line-height: 17px;
+  }
   .cpoyText {
     position: absolute;
     top: -999999px;

+ 19 - 7
src/packages/__VUE/textarea/index.taro.vue

@@ -6,7 +6,7 @@
     <textarea
       v-else
       ref="textareaRef"
-      class="nut-textarea__textarea"
+      :class="['nut-textarea__textarea', env == 'ALIPAY' && 'nut-textarea__ali']"
       :style="styles"
       :rows="rows"
       :disabled="disabled"
@@ -89,7 +89,6 @@ export default create({
       return {
         textAlign: props.textAlign,
         height: props.autosize ? heightSet.value : 'null'
-        // resize: props.autosize ? 'vertical' : 'none'
       };
     });
 
@@ -121,9 +120,7 @@ export default create({
       if (props.disabled) return;
       if (props.readonly) return;
       const input = event.target as HTMLInputElement;
-
       let value = input.value;
-
       emitChange(value, event);
       emit('blur', event);
     };
@@ -179,10 +176,9 @@ export default create({
 
     const getRefHeight = () => {
       const query = Taro.createSelectorQuery();
-      // const query = Taro.createSelectorQuery();
       query.selectAll('.nut-textarea__textarea').boundingClientRect();
       let uid = textareaRef.value ? textareaRef.value.uid : '0';
-      query.exec((res) => {
+      query.exec((res: any) => {
         if (res[0] && textareaRef.value) {
           let _item: any = Array.from(res[0]).filter((item: any) => item.id == uid);
           textareaHeight.value = _item[0]['height'] || 20;
@@ -192,10 +188,25 @@ export default create({
       });
     };
 
+    const getRefWidth = () => {
+      const query = Taro.createSelectorQuery();
+      query.select('.nut-textarea__textarea').boundingClientRect();
+      query.exec((res: any) => {
+        if (res[0] && textareaRef.value) {
+          copyTxtStyle.value.width = res[0]['width'] + 'px';
+        }
+      });
+    };
+    const env = Taro.getEnv();
     onMounted(() => {
       if (props.autosize) {
         eventCenter.once((getCurrentInstanceTaro() as any).router.onReady, () => {
-          getRefHeight();
+          if (Taro.getEnv() === 'ALIPAY') {
+            getRefWidth();
+            copyHeight();
+          } else {
+            getRefHeight();
+          }
           // setTimeout(() => {
           //   nextTick(getContentHeight);
           // }, 300);
@@ -204,6 +215,7 @@ export default create({
     });
 
     return {
+      env,
       textareaRef,
       classes,
       styles,

+ 10 - 2
src/sites/mobile-taro/vue/src/dentry/pages/searchbar/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="demo full">
     <h2>基础用法</h2>
-    <nut-searchbar v-model="searchValue"> </nut-searchbar>
+    <nut-searchbar v-model="searchValue" :focus-style="{ border: '1px solid red' }"> </nut-searchbar>
 
     <h2>搜索事件监听</h2>
     <nut-searchbar v-model="searchValue1" @search="search"> </nut-searchbar>
@@ -26,6 +26,9 @@
     >
     </nut-searchbar>
 
+    <h2>自定义清除按钮 icon</h2>
+    <nut-searchbar v-model="searchValue6" :clear-icon="icon"> </nut-searchbar>
+
     <h2>显示全部 icon</h2>
     <nut-searchbar v-model="searchValue5">
       <template v-slot:leftout>
@@ -49,6 +52,9 @@ import { toRefs, reactive } from 'vue';
 export default {
   props: {},
   setup() {
+    const icon =
+      'https://img10.360buyimg.com/imagetools/jfs/t1/170133/30/22902/10546/61833626E32d7ccde/a7c373ba30de9a89.png';
+
     const state = reactive({
       searchValue: '',
       searchValue1: '',
@@ -56,10 +62,11 @@ export default {
       searchValue3: '',
       searchValue4: '',
       searchValue5: '',
+      searchValue6: '',
       confirmType: 'search'
     });
 
-    const search = function (e: any) {
+    const search = function () {
       console.log('搜索触发');
     };
 
@@ -68,6 +75,7 @@ export default {
     };
 
     return {
+      icon,
       clickLeft,
       search,
       ...toRefs(state)