Skip to content

Commit 75bd63f

Browse files
authored
feat: 下拉类组件增加动画&输入类组件增加悬浮发光效果 (#1816)
1 parent 12c2fc4 commit 75bd63f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+806
-184
lines changed

packages/devui-vue/devui/auto-complete/src/auto-complete-types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ export const autoCompleteProps = {
138138
type: Boolean,
139139
default: false,
140140
},
141+
showGlowStyle: {
142+
type: Boolean,
143+
default: true,
144+
},
141145
} as const;
142146

143147
export type AutoCompleteProps = ExtractPropTypes<typeof autoCompleteProps>;

packages/devui-vue/devui/auto-complete/src/auto-complete.scss

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@
147147
box-sizing: border-box;
148148
height: 100%;
149149

150+
&:not(.#{$devui-prefix}-auto-complete--focus) {
151+
.#{$devui-prefix}-auto-complete--glow-style:hover {
152+
box-shadow: 0 0 0 4px $devui-form-control-interactive-outline;
153+
border-color: $devui-form-control-line;
154+
}
155+
}
156+
150157
&__wrapper {
151158
display: inline-flex;
152159
align-items: center;
@@ -157,7 +164,7 @@
157164
border: 1px solid $devui-form-control-line;
158165
border-radius: $devui-border-radius;
159166
background-color: $devui-form-control-bg;
160-
transition: border-color 0.3s $devui-animation-ease-in-out-smooth;
167+
transition: border-color 0.3s $devui-animation-ease-in-out-smooth, box-shadow $devui-animation-duration-base $devui-animation-ease-in;
161168

162169
&:not(.#{$devui-prefix}-auto-complete--disabled):not(.#{$devui-prefix}-auto-complete-input__wrapper--error):hover {
163170
border-color: $devui-form-control-line-hover;
@@ -206,10 +213,22 @@
206213
}
207214
}
208215

209-
&--focus .#{$devui-prefix}-auto-complete-input__wrapper:not(.#{$devui-prefix}-auto-complete-input__wrapper--error) {
210-
border-color: $devui-form-control-line-active;
216+
&--glow-style:not(.#{$devui-prefix}-auto-complete--disabled):not(.#{$devui-prefix}-auto-complete-input__wrapper--error):hover {
217+
box-shadow: 0 0 0 4px $devui-form-control-interactive-outline;
218+
border-color: $devui-form-control-line;
219+
}
211220

212-
&:hover {
221+
&--focus {
222+
.#{$devui-prefix}-auto-complete-input__wrapper:not(.#{$devui-prefix}-auto-complete-input__wrapper--error) {
223+
border-color: $devui-form-control-line-active;
224+
225+
&:hover {
226+
border-color: $devui-form-control-line-active;
227+
}
228+
}
229+
230+
.#{$devui-prefix}-auto-complete--glow-style:not(.#{$devui-prefix}-auto-complete-input__wrapper--error) {
231+
box-shadow: 0 0 0 4px $devui-form-control-interactive-outline;
213232
border-color: $devui-form-control-line-active;
214233
}
215234
}
@@ -277,3 +296,49 @@
277296
}
278297
}
279298
}
299+
300+
.#{$devui-prefix}-auto-complete--fade {
301+
&-bottom {
302+
&-enter-from,
303+
&-leave-to {
304+
opacity: 0.8;
305+
transform: scaleY(0.8) translateY(-4px);
306+
}
307+
308+
&-enter-to,
309+
&-leave-from {
310+
opacity: 1;
311+
transform: scaleY(0.9999) translateY(0);
312+
}
313+
314+
&-enter-active {
315+
transition: transform 0.2s cubic-bezier(0.16, 0.75, 0.5, 1), opacity 0.2s cubic-bezier(0.16, 0.75, 0.5, 1);
316+
}
317+
318+
&-leave-active {
319+
transition: transform 0.2s cubic-bezier(0.5, 0, 0.84, 0.25), opacity 0.2s cubic-bezier(0.5, 0, 0.84, 0.25);
320+
}
321+
}
322+
323+
&-top {
324+
&-enter-from,
325+
&-leave-to {
326+
opacity: 0.8;
327+
transform: scaleY(0.8) translateY(4px);
328+
}
329+
330+
&-enter-to,
331+
&-leave-from {
332+
opacity: 1;
333+
transform: scaleY(0.9999) translateY(0);
334+
}
335+
336+
&-enter-active {
337+
transition: transform 0.2s cubic-bezier(0.16, 0.75, 0.5, 1), opacity 0.2s cubic-bezier(0.16, 0.75, 0.5, 1);
338+
}
339+
340+
&-leave-active {
341+
transition: transform 0.2s cubic-bezier(0.5, 0, 0.84, 0.25), opacity 0.2s cubic-bezier(0.5, 0, 0.84, 0.25);
342+
}
343+
}
344+
}

packages/devui-vue/devui/auto-complete/src/auto-complete.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default defineComponent({
4444
const inputNs = useNamespace('auto-complete-input');
4545
const isDisabled = computed(() => formContext?.disabled || disabled.value);
4646
const autoCompleteSize = computed(() => formContext?.size || props.size);
47+
const align = computed(() => (position.value.some((item) => item.includes('start') || item.includes('end')) ? 'start' : null));
4748

4849
const { handleSearch, searchList, showNoResultItemTemplate, recentlyFocus } = useSearchFn(
4950
ctx,
@@ -104,20 +105,31 @@ export default defineComponent({
104105
valueParser,
105106
});
106107
const origin = ref<HTMLElement>();
108+
const currentPosition = ref('bottom');
107109

108110
const prefixVisible = ctx.slots.prefix || props.prefix;
109111
const suffixVisible = ctx.slots.suffix || props.suffix || props.clearable;
110112

111113
const showClearable = computed(() => props.clearable && !isDisabled.value);
114+
const overlayStyles = computed(() => ({
115+
transformOrigin: currentPosition.value === 'top' ? '0% 100%' : '0% 0%',
116+
zIndex: 'var(--devui-z-index-dropdown, 1052)',
117+
}));
118+
119+
const handlePositionChange = (pos: string) => {
120+
currentPosition.value = pos.includes('top') || pos.includes('right-end') || pos.includes('left-end') ? 'top' : 'bottom';
121+
};
112122

113123
const renderBasicDropdown = () => {
114124
return (
115-
<Transition name={showAnimation ? 'fade' : ''}>
125+
<Transition name={showAnimation ? ns.m(`fade-${currentPosition.value}`) : ''}>
116126
<FlexibleOverlay
117127
origin={origin.value}
118128
position={position.value}
129+
align={align.value}
119130
v-model={visible.value}
120-
style={{ zIndex: 'var(--devui-z-index-dropdown, 1052)' }}>
131+
onPositionChange={handlePositionChange}
132+
style={overlayStyles.value}>
121133
<div
122134
class={ns.e('menu')}
123135
style={{

packages/devui-vue/devui/auto-complete/src/composables/use-auto-complete-render.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export function useAutoCompleteRender(
4444
const inputWrapperClasses = computed(() => ({
4545
[inputNs.e('wrapper')]: true,
4646
[inputNs.em('wrapper', 'error')]: isValidatorError.value,
47+
[ns.m('glow-style')]: props.showGlowStyle,
4748
[inputNs.em('wrapper', 'feedback')]: Boolean(formItemContext?.validateState) && formItemContext?.showFeedback,
4849
[ns.m('disabled')]: isDisabled.value,
4950
}));

packages/devui-vue/devui/button/src/button.scss

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ $devui-btn-lg-padding: var(--devui-btn-lg-padding, 0 24px);
1818
}
1919

2020
.#{$devui-prefix}-button {
21+
position: relative;
2122
padding: $devui-btn-padding;
2223
font-size: $devui-font-size-md;
2324
height: $devui-size-md;
@@ -26,6 +27,22 @@ $devui-btn-lg-padding: var(--devui-btn-lg-padding, 0 24px);
2627
border-width: 1px;
2728
border-color: transparent;
2829
background-color: transparent;
30+
overflow: hidden;
31+
32+
&.mousedown:not(:disabled) {
33+
transform: scale(0.95);
34+
}
35+
36+
.water-wave {
37+
position: absolute;
38+
background-color: $devui-base-bg;
39+
border-radius: 50%;
40+
opacity: 0;
41+
width: 20px;
42+
height: 20px;
43+
transform: translate(-50%, -50%);
44+
animation: waterWave $devui-animation-duration-slow $devui-animation-linear;
45+
}
2946

3047
&:hover {
3148
cursor: pointer;
@@ -320,8 +337,7 @@ $devui-btn-lg-padding: var(--devui-btn-lg-padding, 0 24px);
320337
}
321338

322339
.#{$devui-prefix}-button {
323-
transition:
324-
background-color $devui-animation-duration-slow $devui-animation-ease-in-out-smooth,
340+
transition: background-color $devui-animation-duration-slow $devui-animation-ease-in-out-smooth,
325341
border-color $devui-animation-duration-slow $devui-animation-ease-in-out-smooth,
326342
color $devui-animation-duration-slow $devui-animation-ease-in-out-smooth;
327343
white-space: nowrap;
@@ -397,7 +413,25 @@ $devui-btn-lg-padding: var(--devui-btn-lg-padding, 0 24px);
397413
}
398414

399415
@keyframes rotating {
400-
0% { transform: rotate(0); }
416+
0% {
417+
transform: rotate(0);
418+
}
401419

402-
100% { transform: rotate(180deg); }
420+
100% {
421+
transform: rotate(180deg);
422+
}
423+
}
424+
425+
@keyframes waterWave {
426+
0% {
427+
opacity: 0.2;
428+
width: 30px;
429+
height: 30px;
430+
}
431+
432+
100% {
433+
opacity: 0;
434+
width: 200px;
435+
height: 200px;
436+
}
403437
}

packages/devui-vue/devui/button/src/button.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineComponent, toRefs } from 'vue';
1+
import { defineComponent, toRefs, ref, reactive } from 'vue';
22
import type { SetupContext } from 'vue';
33
import { Icon } from '../../icon';
44
import LoadingDirective from '../../loading/src/loading-directive';
@@ -16,22 +16,45 @@ export default defineComponent({
1616
setup(props: ButtonProps, ctx: SetupContext) {
1717
const { icon, disabled, loading, nativeType } = toRefs(props);
1818
const { classes, iconClass } = useButton(props, ctx);
19+
const isMouseDown = ref(false);
20+
const showWave = ref(false);
21+
const waveStyle = reactive({
22+
top: '0px',
23+
left: '0px',
24+
});
1925

26+
const showClickWave = (e: MouseEvent) => {
27+
waveStyle.left = e.offsetX + 'px';
28+
waveStyle.top = e.offsetY + 'px';
29+
showWave.value = true;
30+
31+
setTimeout(() => {
32+
showWave.value = false;
33+
}, 300);
34+
};
2035
const onClick = (e: MouseEvent) => {
2136
if (loading.value) {
2237
return;
2338
}
39+
showClickWave(e);
2440
ctx.emit('click', e);
2541
};
2642

2743
return () => {
2844
return (
29-
<button class={classes.value} disabled={disabled.value} onClick={onClick} type={nativeType.value}>
45+
<button
46+
class={[classes.value, isMouseDown.value ? 'mousedown' : '']}
47+
disabled={disabled.value}
48+
onClick={onClick}
49+
type={nativeType.value}
50+
onMousedown={() => (isMouseDown.value = true)}
51+
onMouseup={() => (isMouseDown.value = false)}>
3052
{icon.value && <Icon name={icon.value} size="var(--devui-font-size, 12px)" color="" class={iconClass.value} />}
3153
<div class="loading-icon__container" v-show={loading.value}>
3254
<d-icon name="icon-loading" class="button-icon-loading" color="#BBDEFB"></d-icon>
3355
</div>
3456
<span class="button-content">{ctx.slots.default?.()}</span>
57+
{showWave.value && <div class="water-wave" style={waveStyle}></div>}
3558
</button>
3659
);
3760
};

packages/devui-vue/devui/cascader/src/cascader-types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,11 @@ export const cascaderProps = {
125125
default: () => true,
126126
},
127127
size: {
128-
type: String as PropType<InputSize>
128+
type: String as PropType<InputSize>,
129+
},
130+
showGlowStyle: {
131+
type: Boolean,
132+
default: true,
129133
},
130134
} as const;
131135

packages/devui-vue/devui/cascader/src/cascader.scss

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,49 @@
111111
transform: rotate(180deg);
112112
}
113113
}
114+
115+
.#{$devui-prefix}-cascader--fade {
116+
&-bottom {
117+
&-enter-from,
118+
&-leave-to {
119+
opacity: 0.8;
120+
transform: scaleY(0.8) translateY(-4px);
121+
}
122+
123+
&-enter-to,
124+
&-leave-from {
125+
opacity: 1;
126+
transform: scaleY(0.9999) translateY(0);
127+
}
128+
129+
&-enter-active {
130+
transition: transform 0.2s cubic-bezier(0.16, 0.75, 0.5, 1), opacity 0.2s cubic-bezier(0.16, 0.75, 0.5, 1);
131+
}
132+
133+
&-leave-active {
134+
transition: transform 0.2s cubic-bezier(0.5, 0, 0.84, 0.25), opacity 0.2s cubic-bezier(0.5, 0, 0.84, 0.25);
135+
}
136+
}
137+
138+
&-top {
139+
&-enter-from,
140+
&-leave-to {
141+
opacity: 0.8;
142+
transform: scaleY(0.8) translateY(4px);
143+
}
144+
145+
&-enter-to,
146+
&-leave-from {
147+
opacity: 1;
148+
transform: scaleY(0.9999) translateY(0);
149+
}
150+
151+
&-enter-active {
152+
transition: transform 0.2s cubic-bezier(0.16, 0.75, 0.5, 1), opacity 0.2s cubic-bezier(0.16, 0.75, 0.5, 1);
153+
}
154+
155+
&-leave-active {
156+
transition: transform 0.2s cubic-bezier(0.5, 0, 0.84, 0.25), opacity 0.2s cubic-bezier(0.5, 0, 0.84, 0.25);
157+
}
158+
}
159+
}

packages/devui-vue/devui/cascader/src/cascader.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineComponent, Transition, SetupContext, provide, Teleport } from 'vue';
1+
import { defineComponent, Transition, SetupContext, provide, Teleport, ref, computed } from 'vue';
22
import { cloneDeep } from 'lodash';
33
import { useNamespace } from '../../shared/hooks/use-namespace';
44
import DCascaderList from '../components/cascader-list';
@@ -45,6 +45,15 @@ export default defineComponent({
4545
} = useCascader(props, ctx);
4646
provide(POPPER_TRIGGER_TOKEN, origin);
4747

48+
const currentPosition = ref('bottom');
49+
const styles = computed(() => ({
50+
transformOrigin: currentPosition.value === 'top' ? '0% 100%' : '0% 0%',
51+
'z-index': 'var(--devui-z-index-dropdown, 1052)',
52+
}));
53+
const handlePositionChange = (pos: string) => {
54+
currentPosition.value = pos.split('-')[0] === 'top' ? 'top' : 'bottom';
55+
};
56+
4857
return () => (
4958
<div style={rootStyle.inputWidth}>
5059
<PopperTrigger>
@@ -60,6 +69,7 @@ export default defineComponent({
6069
placeholder={props.placeholder}
6170
modelValue={inputValue.value}
6271
size={props.size}
72+
show-glow-style={props.showGlowStyle}
6373
onInput={handleInput}
6474
onFocus={onFocus}
6575
onBlur={onBlur}
@@ -81,14 +91,15 @@ export default defineComponent({
8191
)}
8292
</PopperTrigger>
8393
<Teleport to="body">
84-
<Transition name="fade">
94+
<Transition name={ns.m(`fade-${currentPosition.value}`)}>
8595
<FlexibleOverlay
8696
origin={origin.value}
8797
ref={overlayRef}
8898
v-model={menuShow.value}
8999
position={position.value as Placement[]}
90100
align="start"
91-
style={{ zIndex: 'var(--devui-z-index-dropdown, 1052)' }}>
101+
style={styles.value}
102+
onPositionChange={handlePositionChange}>
92103
<div class={ns.e('drop-menu-animation')}>
93104
{!isSearching.value && (
94105
<div class={`${menuOpenClass.value} ${ns.e('dropdown-menu')}`}>

0 commit comments

Comments
 (0)