initial
This commit is contained in:
106
layers/ui/components/search/index.vue
Normal file
106
layers/ui/components/search/index.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
cn.b(),
|
||||
cn.m(size),
|
||||
cn.is('focused', focused),
|
||||
cn.is('disabled', disabled),
|
||||
]"
|
||||
>
|
||||
<label :class="cn.e('wrapper')">
|
||||
<Component
|
||||
:is="searchIcon"
|
||||
:class="cn.e('icon')"
|
||||
/>
|
||||
|
||||
<input
|
||||
ref="inputRef"
|
||||
v-model="value"
|
||||
type="search"
|
||||
:class="cn.e('input')"
|
||||
:placeholder="label"
|
||||
:disabled="disabled"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
>
|
||||
|
||||
<UiButton
|
||||
v-show="value.length > 0"
|
||||
size="small"
|
||||
type="link"
|
||||
color="secondary"
|
||||
:class="cn.e('clear')"
|
||||
:icon="clearIcon"
|
||||
@click="value = ''"
|
||||
@mousedown.prevent
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { debounce } from 'lodash-es'
|
||||
import useClassname from '../../composables/use-classname'
|
||||
import UiButton from '../button/index.vue'
|
||||
|
||||
export interface Props {
|
||||
label?: string
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
disabled?: false
|
||||
modelValue?: string
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'UiSearch',
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 'medium',
|
||||
disabled: false,
|
||||
modelValue: '',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [term: string]
|
||||
}>()
|
||||
|
||||
const cn = useClassname('ui-search')
|
||||
|
||||
const inputRef = ref(null)
|
||||
const value = ref(props.modelValue)
|
||||
const focused = ref(false)
|
||||
|
||||
const searchIcon = computed(() => {
|
||||
return resolveComponent(props.size === 'small' ? 'ui-icon-s-search' : 'ui-icon-search')
|
||||
})
|
||||
|
||||
const clearIcon = computed(() => {
|
||||
return props.size === 'small' ? 's-cross' : 'cross'
|
||||
})
|
||||
|
||||
const onInput = debounce(() => {
|
||||
emit('update:modelValue', value.value)
|
||||
}, 300)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(term) => {
|
||||
value.value = term
|
||||
},
|
||||
)
|
||||
|
||||
watch(value, onInput)
|
||||
|
||||
function focus() {
|
||||
inputRef.value?.focus()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
focus,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use 'styles';
|
||||
</style>
|
||||
125
layers/ui/components/search/styles.scss
Normal file
125
layers/ui/components/search/styles.scss
Normal file
@@ -0,0 +1,125 @@
|
||||
@use '../../styles/variables' as *;
|
||||
@use '../../styles/mixins' as *;
|
||||
|
||||
.ui-search {
|
||||
$self: &;
|
||||
|
||||
&__wrapper {
|
||||
--border-color: var(--search-border-color);
|
||||
--border-width: 1px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
outline: var(--border-width) solid var(--border-color);
|
||||
outline-offset: calc(var(--border-width) * -1);
|
||||
color: var(--search-color);
|
||||
border-radius: 12px;
|
||||
flex: 1;
|
||||
|
||||
#{$self}--large & {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
#{$self}--small & {
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#{$self}:not(.is-disabled) & {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
#{$self}:not(.is-disabled) &:hover,
|
||||
#{$self}.is-focused & {
|
||||
--border-width: 2px;
|
||||
}
|
||||
|
||||
#{$self}.is-disabled & {
|
||||
--border-color: var(--search-disabled-border-color);
|
||||
background: var(--search-disabled-background);
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: var(--search-icon-color);
|
||||
margin-right: 8px;
|
||||
pointer-events: none;
|
||||
transition: color .2s ease-out;
|
||||
|
||||
#{$self}--small & {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#{$self}.is-focused & {
|
||||
color: var(--search-active-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
&__input {
|
||||
@include txt-i-m;
|
||||
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
color: var(--search-value-color);
|
||||
caret-color: var(--search-caret-color);
|
||||
appearance: none;
|
||||
|
||||
&::-webkit-search-decoration,
|
||||
&::-webkit-search-cancel-button,
|
||||
&::-webkit-search-results-button,
|
||||
&::-webkit-search-results-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
&::-ms-reveal,
|
||||
&::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:-webkit-autofill {
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: var(--search-color);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--search-placeholder-color);
|
||||
}
|
||||
|
||||
#{$self}--small & {
|
||||
@include txt-r-m;
|
||||
}
|
||||
|
||||
#{$self}.is-disabled & {
|
||||
color: $clr-grey-400;
|
||||
}
|
||||
}
|
||||
|
||||
&__clear {
|
||||
margin-left: 8px;
|
||||
|
||||
#{$self}--small & {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* prettier-ignore */
|
||||
@include element-variant(
|
||||
'search',
|
||||
'',
|
||||
(
|
||||
'border-color': $clr-grey-300,
|
||||
'color': $clr-black,
|
||||
'placeholder-color': $clr-grey-400,
|
||||
'icon-color': $clr-grey-400,
|
||||
'active-icon-color': $clr-grey-500,
|
||||
'disabled-background': $clr-grey-100,
|
||||
'disabled-border-color': $clr-grey-300
|
||||
)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user