Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web-core): update UiInput component #8117

Merged
merged 4 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
<template>
<ComponentStory
v-slot="{ properties }"
v-slot="{ properties, settings }"
:params="[
model().type('string').preset(''),
model().type('string | number').preset(''),
prop('accent').enum('info', 'warning', 'danger').required().preset('info').widget(),
prop('id').str().widget(),
prop('type').enum('text', 'number', 'password').preset('text').widget(),
prop('disabled').bool().widget(),
iconProp().preset(faMagnifyingGlass).type('IconDefinition'),
prop('clearable').bool().widget(),
prop('href').str().widget(),
prop('label').str().widget(),
prop('info').str().widget(),
iconProp(),
iconProp('labelIcon'),
prop('placeholder').str().widget(),
prop('required').bool().widget(),
slot(),
slot('info'),
setting('defaultSlot').widget(text()).preset('Some label'),
setting('info').widget(text()).preset('Lorem ipsum dolor sit amet, consectetur adipiscing elit.'),
]"
>
<UiInput placeholder="Enter your search" v-bind="properties" />
<UiInput placeholder="Enter your search" v-bind="properties">
{{ settings.defaultSlot }}
<template v-if="settings.info" #info>{{ settings.info }}</template>
</UiInput>
</ComponentStory>
</template>

<script lang="ts" setup>
import ComponentStory from '@/components/component-story/ComponentStory.vue'
import { iconProp, model, prop } from '@/libs/story/story-param'
import { iconProp, model, prop, setting, slot } from '@/libs/story/story-param'
import { text } from '@/libs/story/story-widget'
import UiInput from '@core/components/ui/input/UiInput.vue'
import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'
</script>
213 changes: 121 additions & 92 deletions @xen-orchestra/web-core/lib/components/ui/input/UiInput.vue
Original file line number Diff line number Diff line change
@@ -1,141 +1,170 @@
<!-- WIP -->
<!-- v5 -->
<template>
<div class="ui-input">
<VtsIcon :icon accent="current" class="before" />
<input :id v-model.trim="modelValue" class="typo p1-regular input" type="search" v-bind="attrs" />
<VtsIcon
v-if="!attrs.disabled && modelValue"
:icon="faXmark"
class="after"
accent="info"
@click="modelValue = ''"
/>
<div class="ui-input" :class="toVariants({ accent })">
<UiLabel v-if="slots.default || label" :accent="labelAccent" :required :icon="labelIcon" :href :for="id">
<slot>{{ label }}</slot>
</UiLabel>
<div>
<VtsIcon :icon accent="current" class="before" />
<input :id v-model.trim="modelValue" class="typo p1-regular input text-ellipsis" :type :disabled v-bind="attrs" />
<VtsIcon
v-if="!attrs.disabled && modelValue && clearable"
:icon="faXmark"
class="after"
accent="info"
@click="modelValue = ''"
/>
</div>
<UiInfo v-if="slots.info || info" :accent>
<slot name="info">{{ info }}</slot>
</UiInfo>
</div>
</template>

<script lang="ts" setup>
import VtsIcon from '@core/components/icon/VtsIcon.vue'
import { uniqueId } from '@core/utils/unique-id.util'
import UiInfo from '@core/components/ui/info/UiInfo.vue'
import UiLabel from '@core/components/ui/label/UiLabel.vue'
import { toVariants } from '@core/utils/to-variants.util'
import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
import { faXmark } from '@fortawesome/free-solid-svg-icons'
import { computed, useAttrs } from 'vue'
import { computed, useAttrs, useId } from 'vue'

type InputAccent = 'info' | 'warning' | 'danger'
type InputType = 'text' | 'number' | 'password' | 'search'

defineOptions({
inheritAttrs: false,
})

defineProps<{
const { accent, id = useId() } = defineProps<{
accent: InputAccent
label?: string
info?: string
id?: string
disabled?: boolean
clearable?: boolean
href?: string
required?: boolean
labelIcon?: IconDefinition
icon?: IconDefinition
type?: InputType
}>()

const modelValue = defineModel<string>({ required: true })
const modelValue = defineModel<string | number>({ required: true })

const slots = defineSlots<{
default?(): any
info?(): any
}>()

const attrs = useAttrs()

const id = computed(() => uniqueId('input-'))
const labelAccent = computed(() => (accent === 'info' ? 'neutral' : accent))
</script>

<style lang="postcss" scoped>
/* COLOR VARIANTS */
.input {
& {
--border-color: var(--color-neutral-border);
--background-color: var(--color-neutral-background-primary);
/* IMPLEMENTATION */
.ui-input {
position: relative;
display: flex;
flex-direction: column;
gap: 0.4rem;

.input {
border-radius: 0.4rem;
border-width: 0.1rem;
border-style: solid;
background-color: var(--color-neutral-background-primary);
color: var(--color-neutral-txt-primary);
height: 4rem;
outline: none;
width: 100%;
padding-block: 0.8rem;
padding-inline: 1.6rem;

&::placeholder {
color: var(--color-neutral-txt-secondary);
}

&:focus {
border-width: 0.2rem;
}

&:has(+ .after) {
padding-inline-end: 4.8rem;
}
}

&:is(:hover, :focus-visible) {
--border-color: var(--color-info-item-hover);
--background-color: var(--color-neutral-background-primary);
}
/* VARIANT */

&:focus {
--border-color: var(--color-info-item-base);
--background-color: var(--color-neutral-background-primary);
}
&.accent--info {
.input {
border-color: var(--color-neutral-border);

&:active {
--border-color: var(--color-info-item-active);
--background-color: var(--color-neutral-background-primary);
}
&:hover {
border-color: var(--color-info-item-hover);
}

&:disabled {
--border-color: var(--color-neutral-border);
--background-color: var(--color-neutral-background-disabled);
}
}
&:focus {
border-color: var(--color-info-item-base);
}

/* BORDER VARIANTS */
.input {
--border-width: 0.1rem;
&:active {
border-color: var(--color-info-item-active);
}

&:is(:hover, :focus-visible) {
--border-width: 0.1rem;
&:disabled {
border-color: var(--color-neutral-border);
background-color: var(--color-neutral-background-disabled);
}
}
}

&:focus {
--border-width: 0.2rem;
}
&.accent--warning {
.input {
border-color: var(--color-warning-item-base);

&:active {
--border-width: 0.1rem;
&:disabled {
border-color: var(--color-neutral-border);
background-color: var(--color-neutral-background-disabled);
}
}
}

&:disabled {
--border-width: 0.1rem;
&.accent--danger {
.input {
border-color: var(--color-danger-item-base);

&:disabled {
border-color: var(--color-neutral-border);
background-color: var(--color-neutral-background-disabled);
}
}
}
}

/* IMPLEMENTATION */
.ui-input {
position: relative;
/* ICON POSITION */

.before + .input {
padding-inline-start: 4.8rem;
}
}

.input {
border: var(--border-width) solid var(--border-color);
border-radius: 0.8rem;
outline: none;
width: 100%;
height: 4rem;
padding: 0.8rem 1.6rem;
color: var(--color-neutral-txt-primary);
background-color: var(--background-color);

&:has(+ .after) {
padding-inline-end: 4.8rem;
}

&:disabled {
cursor: not-allowed;
.before,
.after {
position: absolute;
inset-block: 1.2rem;
}

&::placeholder {
.before {
color: var(--color-neutral-txt-secondary);
inset-inline-start: 1.6rem;
pointer-events: none;
z-index: 1;
}

&[type='search']::-webkit-search-cancel-button {
display: none;
.after {
inset-inline-end: 1.6rem;
cursor: pointer;
}
}

.before,
.after {
position: absolute;
inset-block: 1.2rem;
}

.before {
color: var(--color-neutral-txt-secondary);
inset-inline-start: 1.6rem;
pointer-events: none;
z-index: 1;
}

.after {
inset-inline-end: 1.6rem;
cursor: pointer;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<UiInput
:id
v-model="value"
type="text"
accent="info"
:aria-label="uiStore.isMobile ? $t('core.query-search-bar.label') : undefined"
:icon="uiStore.isDesktop ? faMagnifyingGlass : undefined"
:placeholder="$t('core.query-search-bar.placeholder')"
Expand Down
1 change: 1 addition & 0 deletions @xen-orchestra/web/src/components/SidebarSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
:aria-label="$t('sidebar.search-tree-view')"
:icon="faMagnifyingGlass"
:placeholder="$t('sidebar.search-tree-view')"
accent="info"
/>
</div>
</template>
Expand Down
Loading