Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit 78d7f16

Browse files
committed
feat: add clearable props for text field
1 parent 656537b commit 78d7f16

File tree

5 files changed

+128
-41
lines changed

5 files changed

+128
-41
lines changed

example/src/views/TextFieldView.vue

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,27 @@ const textFields = ref<TextFieldData[]>([
183183
append: 'Append',
184184
textColor: 'primary',
185185
},
186+
{
187+
value: '',
188+
color: 'primary',
189+
append: 'Append',
190+
clearable: true,
191+
},
192+
{
193+
value: '',
194+
color: 'primary',
195+
variant: 'filled',
196+
append: 'Append',
197+
clearable: true,
198+
},
199+
{
200+
value: '',
201+
color: 'primary',
202+
variant: 'outlined',
203+
append: 'Append',
204+
clearable: true,
205+
textColor: 'primary',
206+
},
186207
]);
187208
188209
const revealableTextFields = ref<TextFieldData[]>([
@@ -234,6 +255,14 @@ const revealableTextFields = ref<TextFieldData[]>([
234255
hint: 'lorem ipsum hint',
235256
disabled: false,
236257
},
258+
{
259+
color: 'info',
260+
variant: 'outlined',
261+
label: 'API Key',
262+
textColor: 'info',
263+
clearable: true,
264+
value: 'some values',
265+
},
237266
]);
238267
</script>
239268

src/components/forms/revealable-text-field/RevealableTextField.vue

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ export interface Props {
1212
placeholder?: string;
1313
disabled?: boolean;
1414
variant?: 'default' | 'filled' | 'outlined';
15-
color?: 'grey' | ContextColorsType;
16-
textColor?: 'grey' | ContextColorsType;
15+
color?: ContextColorsType;
16+
textColor?: ContextColorsType;
1717
dense?: boolean;
1818
hint?: string;
1919
errorMessages?: string[];
@@ -22,6 +22,7 @@ export interface Props {
2222
prependIcon?: RuiIcons;
2323
appendIcon?: RuiIcons;
2424
readonly?: boolean;
25+
clearable?: boolean;
2526
}
2627
2728
defineOptions({
@@ -34,7 +35,7 @@ const props = withDefaults(defineProps<Props>(), {
3435
label: '',
3536
placeholder: '',
3637
variant: 'default',
37-
color: 'grey',
38+
color: undefined,
3839
textColor: undefined,
3940
hint: '',
4041
errorMessages: () => [],
@@ -63,25 +64,23 @@ const slots = useSlots();
6364
<slot name="prepend" />
6465
</template>
6566
<template #append>
66-
<div class="flex items-center">
67-
<RuiButton
68-
:disabled="disabled"
69-
tabindex="-1"
70-
variant="text"
71-
type="button"
72-
icon
73-
class="-mr-1 !p-2"
74-
@click="hidden = !hidden"
75-
>
76-
<RuiIcon
77-
class="text-black/[.54] dark:text-white/[.56]"
78-
size="20"
79-
:name="hidden ? 'eye-off-line' : 'eye-line'"
80-
/>
81-
</RuiButton>
67+
<RuiButton
68+
:disabled="disabled"
69+
tabindex="-1"
70+
variant="text"
71+
type="button"
72+
icon
73+
class="!p-2"
74+
@click="hidden = !hidden"
75+
>
76+
<RuiIcon
77+
class="text-black/[.54] dark:text-white/[.56]"
78+
size="20"
79+
:name="hidden ? 'eye-off-line' : 'eye-line'"
80+
/>
81+
</RuiButton>
8282

83-
<slot name="append" />
84-
</div>
83+
<slot name="append" />
8584
</template>
8685
</RuiTextField>
8786
</template>

src/components/forms/text-field/TextField.spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ describe('Forms/TextField', () => {
3737
});
3838

3939
it('passes color props', async () => {
40-
const wrapper = createWrapper();
41-
expect(wrapper.find('div[class*=wrapper]').classes()).toMatch(/_grey_/);
42-
43-
await wrapper.setProps({ color: 'primary' });
40+
const wrapper = createWrapper({
41+
propsData: {
42+
color: 'primary',
43+
},
44+
});
4445
expect(wrapper.find('div[class*=wrapper]').classes()).toMatch(/_primary_/);
4546

4647
await wrapper.setProps({ color: 'secondary' });

src/components/forms/text-field/TextField.stories.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,14 @@ export const Readonly: Story = {
177177
},
178178
};
179179

180+
export const Clearable: Story = {
181+
args: {
182+
label: 'Label',
183+
placeholder: 'Placeholder',
184+
variant: 'outlined',
185+
clearable: true,
186+
value: 'Clearable text',
187+
},
188+
};
189+
180190
export default meta;

src/components/forms/text-field/TextField.vue

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<script lang="ts" setup>
2-
import { logicOr } from '@vueuse/math';
2+
import { logicAnd, logicNot, logicOr } from '@vueuse/math';
33
import { objectOmit } from '@vueuse/shared';
44
import { type ContextColorsType } from '@/consts/colors';
5-
import Icon from '@/components/icons/Icon.vue';
5+
import Icon, { default as RuiIcon } from '@/components/icons/Icon.vue';
6+
import { default as RuiButton } from '@/components/buttons/button/Button.vue';
67
import { type RuiIcons } from '~/src';
78
89
export interface Props {
@@ -11,8 +12,8 @@ export interface Props {
1112
placeholder?: string;
1213
disabled?: boolean;
1314
variant?: 'default' | 'filled' | 'outlined';
14-
color?: 'grey' | ContextColorsType;
15-
textColor?: 'grey' | ContextColorsType;
15+
color?: ContextColorsType;
16+
textColor?: ContextColorsType;
1617
dense?: boolean;
1718
hint?: string;
1819
errorMessages?: string[];
@@ -21,6 +22,7 @@ export interface Props {
2122
prependIcon?: RuiIcons;
2223
appendIcon?: RuiIcons;
2324
readonly?: boolean;
25+
clearable?: boolean;
2426
}
2527
2628
defineOptions({
@@ -34,7 +36,7 @@ const props = withDefaults(defineProps<Props>(), {
3436
placeholder: '',
3537
disabled: false,
3638
variant: 'default',
37-
color: 'grey',
39+
color: undefined,
3840
textColor: undefined,
3941
dense: false,
4042
hint: '',
@@ -44,13 +46,23 @@ const props = withDefaults(defineProps<Props>(), {
4446
prependIcon: undefined,
4547
appendIcon: undefined,
4648
readonly: false,
49+
clearable: false,
4750
});
4851
4952
const emit = defineEmits<{
5053
(e: 'input', modelValue: string): void;
54+
(e: 'click:clear'): void;
5155
}>();
5256
53-
const { label, value, errorMessages, successMessages } = toRefs(props);
57+
const {
58+
label,
59+
value,
60+
clearable,
61+
disabled,
62+
readonly,
63+
errorMessages,
64+
successMessages,
65+
} = toRefs(props);
5466
5567
const labelWithQuote = computed(() => {
5668
const labelVal = get(label);
@@ -85,6 +97,21 @@ const hasMessages = logicOr(hasError, hasSuccess);
8597
const css = useCssModule();
8698
const attrs = useAttrs();
8799
const slots = useSlots();
100+
101+
const clearIconClicked = () => {
102+
set(internalValue, '');
103+
emit('click:clear');
104+
};
105+
106+
const input = ref();
107+
const { focused } = useFocus(input);
108+
109+
const showClearIcon = logicAnd(
110+
clearable,
111+
internalValue,
112+
logicNot(disabled),
113+
logicNot(readonly),
114+
);
88115
</script>
89116

90117
<template>
@@ -105,16 +132,17 @@ const slots = useSlots();
105132
},
106133
]"
107134
>
108-
<div class="flex items-center shrink-0">
109-
<div v-if="slots.prepend" :class="css.prepend">
135+
<div class="flex items-center gap-1 shrink-0" :class="css.prepend">
136+
<div v-if="slots.prepend">
110137
<slot name="prepend" />
111138
</div>
112-
<div v-else-if="prependIcon" :class="[css.icon, css.prepend]">
139+
<div v-else-if="prependIcon" :class="[css.icon]">
113140
<Icon :name="prependIcon" />
114141
</div>
115142
</div>
116143
<div ref="innerWrapper" class="flex flex-1 overflow-hidden">
117144
<input
145+
ref="input"
118146
v-model="internalValue"
119147
:placeholder="placeholder || ' '"
120148
:class="css.input"
@@ -133,11 +161,23 @@ const slots = useSlots();
133161
<legend />
134162
</fieldset>
135163
</div>
136-
<div class="flex items-center shrink-0">
137-
<div v-if="slots.append" :class="css.append">
164+
<div class="flex items-center gap-1 shrink-0" :class="css.append">
165+
<RuiButton
166+
v-if="showClearIcon"
167+
:class="{ hidden: !focused }"
168+
variant="text"
169+
type="button"
170+
icon
171+
class="!p-2"
172+
:color="color"
173+
@click.stop="clearIconClicked()"
174+
>
175+
<RuiIcon name="close-line" size="20" />
176+
</RuiButton>
177+
<div v-if="slots.append">
138178
<slot name="append" />
139179
</div>
140-
<div v-else-if="appendIcon" :class="[css.icon, css.append]">
180+
<div v-else-if="appendIcon" :class="[css.icon]">
141181
<Icon :name="appendIcon" />
142182
</div>
143183
</div>
@@ -281,11 +321,15 @@ const slots = useSlots();
281321
}
282322
283323
.prepend {
284-
@apply mr-2;
324+
&:not(:empty) {
325+
@apply mr-2;
326+
}
285327
}
286328
287329
.append {
288-
@apply ml-2;
330+
&:not(:empty) {
331+
@apply ml-2;
332+
}
289333
}
290334
291335
@each $color in c.$context-colors {
@@ -359,11 +403,15 @@ const slots = useSlots();
359403
@apply pt-0;
360404
361405
.prepend {
362-
@apply ml-3 mr-0;
406+
&:not(:empty) {
407+
@apply ml-3 mr-0;
408+
}
363409
}
364410
365411
.append {
366-
@apply mr-3 ml-0;
412+
&:not(:empty) {
413+
@apply mr-3 ml-0;
414+
}
367415
}
368416
369417
.input {

0 commit comments

Comments
 (0)