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(NeTextInput): add isSearch prop, prefix & suffix slots #79

Merged
merged 2 commits into from
Nov 6, 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
62 changes: 60 additions & 2 deletions src/components/NeTextInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
-->

<script setup lang="ts">
import { computed, ref, useAttrs } from 'vue'
import { computed, ref, useAttrs, useSlots } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleExclamation as fasCircleExclamation } from '@fortawesome/free-solid-svg-icons'
import { faEye as fasEye } from '@fortawesome/free-solid-svg-icons'
import { faEyeSlash as fasEyeSlash } from '@fortawesome/free-solid-svg-icons'
import { faMagnifyingGlass, faXmark } from '@fortawesome/free-solid-svg-icons'

const props = defineProps({
label: {
Expand Down Expand Up @@ -40,6 +41,14 @@ const props = defineProps({
optional: {
type: Boolean
},
isSearch: {
type: Boolean,
default: false
},
clearSearchLabel: {
type: String,
default: 'Clear'
},
isPassword: {
type: Boolean,
default: false
Expand Down Expand Up @@ -73,6 +82,8 @@ defineOptions({
inheritAttrs: false
})

const slots = useSlots()

// add fontawesome icons
library.add(fasCircleExclamation)
library.add(fasEye)
Expand All @@ -92,7 +103,12 @@ let isPasswordVisible = ref(false)
const componentId = computed(() => (props.id ? props.id : uuidv4()))

const inputStyles = computed(() =>
[inputBaseStyle, props.invalidMessage ? inputInvalidStyle : inputValidStyle].join(' ')
[
inputBaseStyle,
props.invalidMessage ? inputInvalidStyle : inputValidStyle,
(slots.prefix || props.isSearch) && 'pl-10',
(slots.suffix || props.isSearch) && 'pr-10'
].join(' ')
)

const inputRef = ref()
Expand Down Expand Up @@ -130,6 +146,11 @@ function togglePasswordVisible() {
function focus() {
inputRef.value.focus()
}

function clearText() {
emit('update:modelValue', '')
focus()
}
</script>

<template>
Expand All @@ -148,6 +169,25 @@ function focus() {
<span v-if="optional" class="ml-2 font-normal">{{ optionalLabel }}</span>
</label>
<div class="relative rounded-md shadow-sm">
<!-- search icon -->
<div
v-if="isSearch"
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
>
<FontAwesomeIcon
:icon="faMagnifyingGlass"
class="h-4 w-4 text-gray-500 dark:text-gray-400"
aria-hidden="true"
/>
</div>
<!-- prefix -->
<div
v-else-if="$slots.prefix"
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
>
<slot name="prefix"></slot>
</div>
<!-- text input -->
<input
:id="componentId"
ref="inputRef"
Expand Down Expand Up @@ -189,6 +229,24 @@ function focus() {
aria-hidden="true"
/>
</div>
<!-- clear search icon -->
<div
v-else-if="isSearch && modelValue"
class="absolute inset-y-0 right-0 flex items-center pr-3"
>
<button type="button" :disabled="disabled" class="flex items-center" @click="clearText">
<span class="sr-only">{{ clearSearchLabel }}</span>
<FontAwesomeIcon
:icon="faXmark"
class="h-4 w-4 text-gray-500 dark:text-gray-400"
aria-hidden="true"
/>
</button>
</div>
<!-- suffix -->
<div v-else-if="$slots.suffix" class="absolute inset-y-0 right-0 flex items-center pr-3">
<slot name="suffix"></slot>
</div>
</div>
<!-- invalid message -->
<p
Expand Down
57 changes: 53 additions & 4 deletions stories/NeTextInput.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import type { Meta, StoryObj } from '@storybook/vue3'

import { NeTextInput, NeTooltip } from '../src/main'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faEnvelope, faDollarSign } from '@fortawesome/free-solid-svg-icons'

const meta = {
title: 'NeTextInput',
Expand All @@ -17,6 +19,8 @@ const meta = {
helperText: '',
invalidMessage: '',
optional: false,
isSearch: false,
clearSearchLabel: 'Clear',
disabled: false,
id: '',
isPassword: false,
Expand All @@ -29,7 +33,7 @@ const meta = {
export default meta
type Story = StoryObj<typeof meta>

const template = '<NeTextInput v-bind="args" class="max-w-md" />'
const template = '<NeTextInput v-bind="args" class="max-w-xs" />'

export const Default: Story = {
render: (args) => ({
Expand Down Expand Up @@ -96,10 +100,10 @@ export const Password: Story = {
},
template: template
}),
args: { isPassword: true, label: 'Enter password', placeholder: '' }
args: { isPassword: true, label: 'Enter password', placeholder: 'Current password' }
}

const typeNumberTemplate = '<NeTextInput v-bind="args" type="number" class="max-w-md" />'
const typeNumberTemplate = '<NeTextInput v-bind="args" type="number" class="max-w-xs" />'

export const TypeNumber: Story = {
render: (args) => ({
Expand All @@ -113,7 +117,7 @@ export const TypeNumber: Story = {
}

const templateWithTooltip =
'<NeTextInput v-bind="args" class="max-w-md">\
'<NeTextInput v-bind="args" class="max-w-xs">\
<template #tooltip>\
<NeTooltip>\
<template #content>Tooltip</template>\
Expand All @@ -131,3 +135,48 @@ export const WithTooltip: Story = {
}),
args: {}
}

const templateWithPrefix = `<NeTextInput v-bind="args" class="max-w-xs">
<template #prefix>
<FontAwesomeIcon :icon="faEnvelope" class="h-4 w-4 text-gray-500 dark:text-gray-400" aria-hidden="true" />
</template>
</NeTextInput>`

export const WithPrefix: Story = {
render: (args) => ({
components: { NeTextInput, FontAwesomeIcon },
setup() {
return { args, faEnvelope }
},
template: templateWithPrefix
}),
args: {}
}

const templateWithSuffix = `<NeTextInput v-bind="args" class="max-w-xs">
<template #suffix>
<FontAwesomeIcon :icon="faDollarSign" class="h-4 w-4 text-gray-500 dark:text-gray-400" aria-hidden="true" />
</template>
</NeTextInput>`

export const WithSuffix: Story = {
render: (args) => ({
components: { NeTextInput, FontAwesomeIcon },
setup() {
return { args, faDollarSign }
},
template: templateWithSuffix
}),
args: {}
}

export const Search: Story = {
render: (args) => ({
components: { NeTextInput },
setup() {
return { args }
},
template: template
}),
args: { isSearch: true }
}