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

Commit

Permalink
feat: add clearable props for text field
Browse files Browse the repository at this point in the history
  • Loading branch information
lukicenturi authored and LefterisJP committed Nov 29, 2023
1 parent 4f3f982 commit d2e3577
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 41 deletions.
29 changes: 29 additions & 0 deletions example/src/views/TextFieldView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,27 @@ const textFields = ref<TextFieldData[]>([
append: 'Append',
textColor: 'primary',
},
{
value: '',
color: 'primary',
append: 'Append',
clearable: true,
},
{
value: '',
color: 'primary',
variant: 'filled',
append: 'Append',
clearable: true,
},
{
value: '',
color: 'primary',
variant: 'outlined',
append: 'Append',
clearable: true,
textColor: 'primary',
},
]);
const revealableTextFields = ref<TextFieldData[]>([
Expand Down Expand Up @@ -234,6 +255,14 @@ const revealableTextFields = ref<TextFieldData[]>([
hint: 'lorem ipsum hint',
disabled: false,
},
{
color: 'info',
variant: 'outlined',
label: 'API Key',
textColor: 'info',
clearable: true,
value: 'some values',
},
]);
</script>

Expand Down
41 changes: 20 additions & 21 deletions src/components/forms/revealable-text-field/RevealableTextField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export interface Props {
placeholder?: string;
disabled?: boolean;
variant?: 'default' | 'filled' | 'outlined';
color?: 'grey' | ContextColorsType;
textColor?: 'grey' | ContextColorsType;
color?: ContextColorsType;
textColor?: ContextColorsType;
dense?: boolean;
hint?: string;
errorMessages?: string[];
Expand All @@ -22,6 +22,7 @@ export interface Props {
prependIcon?: RuiIcons;
appendIcon?: RuiIcons;
readonly?: boolean;
clearable?: boolean;
}
defineOptions({
Expand All @@ -34,7 +35,7 @@ const props = withDefaults(defineProps<Props>(), {
label: '',
placeholder: '',
variant: 'default',
color: 'grey',
color: undefined,
textColor: undefined,
hint: '',
errorMessages: () => [],
Expand Down Expand Up @@ -63,25 +64,23 @@ const slots = useSlots();
<slot name="prepend" />
</template>
<template #append>
<div class="flex items-center">
<RuiButton
:disabled="disabled"
tabindex="-1"
variant="text"
type="button"
icon
class="-mr-1 !p-2"
@click="hidden = !hidden"
>
<RuiIcon
class="text-black/[.54] dark:text-white/[.56]"
size="20"
:name="hidden ? 'eye-off-line' : 'eye-line'"
/>
</RuiButton>
<RuiButton
:disabled="disabled"
tabindex="-1"
variant="text"
type="button"
icon
class="!p-2"
@click="hidden = !hidden"
>
<RuiIcon
class="text-black/[.54] dark:text-white/[.56]"
size="20"
:name="hidden ? 'eye-off-line' : 'eye-line'"
/>
</RuiButton>

<slot name="append" />
</div>
<slot name="append" />
</template>
</RuiTextField>
</template>
9 changes: 5 additions & 4 deletions src/components/forms/text-field/TextField.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ describe('Forms/TextField', () => {
});

it('passes color props', async () => {
const wrapper = createWrapper();
expect(wrapper.find('div[class*=wrapper]').classes()).toMatch(/_grey_/);

await wrapper.setProps({ color: 'primary' });
const wrapper = createWrapper({
propsData: {
color: 'primary',
},
});
expect(wrapper.find('div[class*=wrapper]').classes()).toMatch(/_primary_/);

await wrapper.setProps({ color: 'secondary' });
Expand Down
10 changes: 10 additions & 0 deletions src/components/forms/text-field/TextField.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,14 @@ export const Readonly: Story = {
},
};

export const Clearable: Story = {
args: {
label: 'Label',
placeholder: 'Placeholder',
variant: 'outlined',
clearable: true,
value: 'Clearable text',
},
};

export default meta;
80 changes: 64 additions & 16 deletions src/components/forms/text-field/TextField.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script lang="ts" setup>
import { logicOr } from '@vueuse/math';
import { logicAnd, logicNot, logicOr } from '@vueuse/math';
import { objectOmit } from '@vueuse/shared';
import { type ContextColorsType } from '@/consts/colors';
import Icon from '@/components/icons/Icon.vue';
import Icon, { default as RuiIcon } from '@/components/icons/Icon.vue';
import { default as RuiButton } from '@/components/buttons/button/Button.vue';
import { type RuiIcons } from '~/src';
export interface Props {
Expand All @@ -11,8 +12,8 @@ export interface Props {
placeholder?: string;
disabled?: boolean;
variant?: 'default' | 'filled' | 'outlined';
color?: 'grey' | ContextColorsType;
textColor?: 'grey' | ContextColorsType;
color?: ContextColorsType;
textColor?: ContextColorsType;
dense?: boolean;
hint?: string;
errorMessages?: string[];
Expand All @@ -21,6 +22,7 @@ export interface Props {
prependIcon?: RuiIcons;
appendIcon?: RuiIcons;
readonly?: boolean;
clearable?: boolean;
}
defineOptions({
Expand All @@ -34,7 +36,7 @@ const props = withDefaults(defineProps<Props>(), {
placeholder: '',
disabled: false,
variant: 'default',
color: 'grey',
color: undefined,
textColor: undefined,
dense: false,
hint: '',
Expand All @@ -44,13 +46,23 @@ const props = withDefaults(defineProps<Props>(), {
prependIcon: undefined,
appendIcon: undefined,
readonly: false,
clearable: false,
});
const emit = defineEmits<{
(e: 'input', modelValue: string): void;
(e: 'click:clear'): void;
}>();
const { label, value, errorMessages, successMessages } = toRefs(props);
const {
label,
value,
clearable,
disabled,
readonly,
errorMessages,
successMessages,
} = toRefs(props);
const labelWithQuote = computed(() => {
const labelVal = get(label);
Expand Down Expand Up @@ -85,6 +97,21 @@ const hasMessages = logicOr(hasError, hasSuccess);
const css = useCssModule();
const attrs = useAttrs();
const slots = useSlots();
const clearIconClicked = () => {
set(internalValue, '');
emit('click:clear');
};
const input = ref();
const { focused } = useFocus(input);
const showClearIcon = logicAnd(
clearable,
internalValue,
logicNot(disabled),
logicNot(readonly),
);
</script>

<template>
Expand All @@ -105,16 +132,17 @@ const slots = useSlots();
},
]"
>
<div class="flex items-center shrink-0">
<div v-if="slots.prepend" :class="css.prepend">
<div class="flex items-center gap-1 shrink-0" :class="css.prepend">
<div v-if="slots.prepend">
<slot name="prepend" />
</div>
<div v-else-if="prependIcon" :class="[css.icon, css.prepend]">
<div v-else-if="prependIcon" :class="[css.icon]">
<Icon :name="prependIcon" />
</div>
</div>
<div ref="innerWrapper" class="flex flex-1 overflow-hidden">
<input
ref="input"
v-model="internalValue"
:placeholder="placeholder || ' '"
:class="css.input"
Expand All @@ -133,11 +161,23 @@ const slots = useSlots();
<legend />
</fieldset>
</div>
<div class="flex items-center shrink-0">
<div v-if="slots.append" :class="css.append">
<div class="flex items-center gap-1 shrink-0" :class="css.append">
<RuiButton
v-if="showClearIcon"
:class="{ hidden: !focused }"
variant="text"
type="button"
icon
class="!p-2"
:color="color"
@click.stop="clearIconClicked()"
>
<RuiIcon name="close-line" size="20" />
</RuiButton>
<div v-if="slots.append">
<slot name="append" />
</div>
<div v-else-if="appendIcon" :class="[css.icon, css.append]">
<div v-else-if="appendIcon" :class="[css.icon]">
<Icon :name="appendIcon" />
</div>
</div>
Expand Down Expand Up @@ -281,11 +321,15 @@ const slots = useSlots();
}
.prepend {
@apply mr-2;
&:not(:empty) {
@apply mr-2;
}
}
.append {
@apply ml-2;
&:not(:empty) {
@apply ml-2;
}
}
@each $color in c.$context-colors {
Expand Down Expand Up @@ -359,11 +403,15 @@ const slots = useSlots();
@apply pt-0;
.prepend {
@apply ml-3 mr-0;
&:not(:empty) {
@apply ml-3 mr-0;
}
}
.append {
@apply mr-3 ml-0;
&:not(:empty) {
@apply mr-3 ml-0;
}
}
.input {
Expand Down

0 comments on commit d2e3577

Please sign in to comment.