feat: add input component
This commit is contained in:
@@ -15,6 +15,13 @@ function buildSidebar() {
|
||||
...getComponents(),
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Form',
|
||||
collapsible: true,
|
||||
items: [
|
||||
...getFormComponents(),
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Utils',
|
||||
collapsible: true,
|
||||
@@ -55,6 +62,12 @@ function getComponents() {
|
||||
]
|
||||
}
|
||||
|
||||
function getFormComponents() {
|
||||
return [
|
||||
{ text: 'Input', link: 'components/input/input.md' },
|
||||
]
|
||||
}
|
||||
|
||||
function getUtils() {
|
||||
return [
|
||||
{ text: 'Flowbite Themable', link: '/components/flowbiteThemable/flowbiteThemable.md' },
|
||||
|
||||
6
docs/components/input/examples/InputDisabledExample.vue
Normal file
6
docs/components/input/examples/InputDisabledExample.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<Input :disabled="true" placeholder="enter your first name" label="First name" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Input } from '../../../../src/index'
|
||||
</script>
|
||||
6
docs/components/input/examples/InputExample.vue
Normal file
6
docs/components/input/examples/InputExample.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<Input placeholder="enter your first name" label="First name" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Input } from '../../../../src/index'
|
||||
</script>
|
||||
12
docs/components/input/examples/InputHelperExample.vue
Normal file
12
docs/components/input/examples/InputHelperExample.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div class="vp-raw">
|
||||
<Input placeholder="enter your first name" label="First name">
|
||||
<template #helper>
|
||||
We’ll never share your details. Read our <a href="#" class="font-medium text-blue-600 hover:underline dark:text-blue-500">Privacy Policy</a>.
|
||||
</template>
|
||||
</Input>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Input } from '../../../../src/index'
|
||||
</script>
|
||||
12
docs/components/input/examples/InputPrefixExample.vue
Normal file
12
docs/components/input/examples/InputPrefixExample.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div class="vp-raw">
|
||||
<Input placeholder="enter your search query" label="Search">
|
||||
<template #prefix>
|
||||
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</template>
|
||||
</Input>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Input } from '../../../../src/index'
|
||||
</script>
|
||||
10
docs/components/input/examples/InputSizeExample.vue
Normal file
10
docs/components/input/examples/InputSizeExample.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div class="vp-raw flex flex-col align-center gap-2 flex-wrap">
|
||||
<Input size="sm" placeholder="enter your first name" label="Small" />
|
||||
<Input size="md" placeholder="enter your last name" label="Medium" />
|
||||
<Input size="lg" placeholder="enter your second name" label="Large" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Input } from '../../../../src/index'
|
||||
</script>
|
||||
15
docs/components/input/examples/InputSuffixExample.vue
Normal file
15
docs/components/input/examples/InputSuffixExample.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="vp-raw">
|
||||
<Input size="lg" placeholder="enter your search query" label="Search">
|
||||
<template #prefix>
|
||||
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<Button>Search</Button>
|
||||
</template>
|
||||
</Input>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Input, Button } from '../../../../src/index'
|
||||
</script>
|
||||
112
docs/components/input/input.md
Normal file
112
docs/components/input/input.md
Normal file
@@ -0,0 +1,112 @@
|
||||
<script setup>
|
||||
import InputExample from './examples/InputExample.vue';
|
||||
import InputSizeExample from './examples/InputSizeExample.vue';
|
||||
import InputDisabledExample from './examples/InputDisabledExample.vue';
|
||||
import InputHelperExample from './examples/InputHelperExample.vue';
|
||||
import InputPrefixExample from './examples/InputPrefixExample.vue';
|
||||
import InputSuffixExample from './examples/InputSuffixExample.vue'
|
||||
</script>
|
||||
|
||||
# Input
|
||||
|
||||
#### Get started with a collection of input fields built with Tailwind CSS to start accepting data from the user based on multiple sizes, variants, and input types
|
||||
|
||||
---
|
||||
|
||||
:::tip
|
||||
Original reference: [https://flowbite.com/docs/forms/input-field/](https://flowbite.com/docs/forms/input-field/)
|
||||
:::
|
||||
|
||||
The input field is an important part of the form element that can be used to create interactive controls to accept data from the user based on multiple input types, such as text, email, number, password, URL, phone number, and more.
|
||||
|
||||
On this page you will find all of the input types based on multiple variants, styles, colors, and sizes built with the utility classes from Tailwind CSS and components from Flowbite.
|
||||
|
||||
## Default
|
||||
```vue
|
||||
<script setup>
|
||||
import { Input } from 'flowbite-vue'
|
||||
</script>
|
||||
<template>
|
||||
<Input placeholder="enter your first name" label="First name" />
|
||||
</template>
|
||||
```
|
||||
|
||||
<InputExample />
|
||||
|
||||
## Size
|
||||
```vue
|
||||
<script setup>
|
||||
import { Input } from 'flowbite-vue'
|
||||
</script>
|
||||
<template>
|
||||
<Input size="sm" placeholder="enter your first name" label="Small" />
|
||||
<Input size="md" placeholder="enter your last name" label="Medium" />
|
||||
<Input size="lg" placeholder="enter your second name" label="Large" />
|
||||
</template>
|
||||
```
|
||||
|
||||
<InputSizeExample />
|
||||
|
||||
## Disabled
|
||||
```vue
|
||||
<script setup>
|
||||
import { Input } from 'flowbite-vue'
|
||||
</script>
|
||||
<template>
|
||||
<Input :disabled="true" placeholder="enter your first name" label="First name" />
|
||||
</template>
|
||||
```
|
||||
|
||||
<InputDisabledExample />
|
||||
|
||||
## Slot - Helper
|
||||
```vue
|
||||
<script setup>
|
||||
import { Input } from 'flowbite-vue'
|
||||
</script>
|
||||
<template>
|
||||
<Input placeholder="enter your first name" label="First name">
|
||||
<template #helper>
|
||||
We’ll never share your details. Read our <a href="#" class="font-medium text-blue-600 hover:underline dark:text-blue-500">Privacy Policy</a>.
|
||||
</template>
|
||||
</Input>
|
||||
</template>
|
||||
```
|
||||
|
||||
<InputHelperExample />
|
||||
|
||||
## Slot - Prefix
|
||||
```vue
|
||||
<script setup>
|
||||
import { Input } from 'flowbite-vue'
|
||||
</script>
|
||||
<template>
|
||||
<Input placeholder="enter your first name" label="First name">
|
||||
<template #prefix>
|
||||
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</template>
|
||||
</Input>
|
||||
</template>
|
||||
```
|
||||
|
||||
<InputPrefixExample />
|
||||
|
||||
## Slot - Suffix
|
||||
```vue
|
||||
<script setup>
|
||||
import { Input } from 'flowbite-vue'
|
||||
</script>
|
||||
<template>
|
||||
<Input size="lg" placeholder="enter your search query" label="Search">
|
||||
<template #prefix>
|
||||
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<Button>Search</Button>
|
||||
</template>
|
||||
</Input>
|
||||
</template>
|
||||
```
|
||||
|
||||
<InputSuffixExample />
|
||||
|
||||
650
package-lock.json
generated
650
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "flowbite-vue",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"repository": "https://github.com/themesberg/flowbite-vue.git",
|
||||
"author": "themesberg",
|
||||
"license": "MIT",
|
||||
@@ -22,9 +22,10 @@
|
||||
"start": "vitepress serve docs --port $PORT",
|
||||
"build": "vitepress build docs && cp -r ./docs/assets/* ./docs/.vitepress/dist/assets/",
|
||||
"build:package": "vite build",
|
||||
"build:types": "vue-tsc --declaration --emitDeclarationOnly",
|
||||
"build:types": "vue-tsc --declaration --emitDeclarationOnly && tsc-alias -p tsconfig.json",
|
||||
"build:production": "npm run build:types && npm run build:package",
|
||||
"lint": "eslint --ext .ts,.vue --ignore-path .gitignore --fix src",
|
||||
"clear": "rm -fr ./dist && rm -fr ./dist_types",
|
||||
"format": "prettier . --write",
|
||||
"test": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
@@ -53,6 +54,7 @@
|
||||
"postcss-prefix-selector": "^1.16.0",
|
||||
"prettier": "^2.3.2",
|
||||
"tailwindcss": "^3",
|
||||
"tsc-alias": "^1.7.0",
|
||||
"typescript": "^4.7.3",
|
||||
"vite": "^2.4.3",
|
||||
"vitest": "^0.16.0",
|
||||
@@ -61,7 +63,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^8.9.1",
|
||||
"http-server": "^14.1.1",
|
||||
"vitepress": "^1.0.0-alpha.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import { useButtonSpinner } from './composables/useButtonSpinner'
|
||||
import FlowbiteThemableChild from '@/components/utils/FlowbiteThemable/components/FlowbiteThemableChild/FlowbiteThemableChild.vue'
|
||||
|
||||
import type { ButtonGradient, ButtonMonochromeGradient, ButtonSize, ButtonVariant } from './types'
|
||||
import type { ThemableChildrenApply } from '@/components/utils/FlowbiteThemable/components/FlowbiteThemableChild/types'
|
||||
const props = defineProps({
|
||||
color: {
|
||||
type: String as PropType<ButtonVariant>,
|
||||
@@ -85,7 +86,7 @@ const loadingSuffix = computed(() => props.loading && props.loadingPosition ===
|
||||
const { wrapperClasses, spanClasses } = useButtonClasses(toRefs(props))
|
||||
const { color: spinnerColor, size: spinnerSize } = useButtonSpinner(toRefs(props))
|
||||
|
||||
const appliableTheme = computed(() => {
|
||||
const appliableTheme = computed<ThemableChildrenApply[]>(() => {
|
||||
if(['alternative', 'light'].includes(props.color)) return []
|
||||
return ['background', 'hover', 'focus']
|
||||
})
|
||||
|
||||
58
src/components/Input/Input.vue
Normal file
58
src/components/Input/Input.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div>
|
||||
<label v-if="label" :class="labelClasses">{{ label }}</label>
|
||||
<div class="flex relative">
|
||||
<div v-if="$slots.prefix" class="w-10 flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none overflow-hidden">
|
||||
<slot name="prefix" />
|
||||
</div>
|
||||
<input
|
||||
v-bind="$attrs"
|
||||
v-model="model"
|
||||
:disabled="disabled"
|
||||
:type="type"
|
||||
|
||||
:class="[inputClasses, $slots.prefix ? 'pl-10' : '']"
|
||||
/>
|
||||
<div v-if="$slots.suffix" class="absolute right-2.5 bottom-2.5">
|
||||
<slot name="suffix" />
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="$slots.helper" class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<slot name="helper" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import type { InputSize } from '@/components/Input/types'
|
||||
import { useInputClasses } from '@/components/Input/composables/useInputClasses'
|
||||
import { toRefs } from 'vue'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<'text' | 'password'>,
|
||||
default: 'text',
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<InputSize>,
|
||||
default: 'md',
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const model = useVModel(props, 'modelValue')
|
||||
|
||||
const {inputClasses, labelClasses} = useInputClasses(toRefs(props))
|
||||
</script>
|
||||
39
src/components/Input/composables/useInputClasses.ts
Normal file
39
src/components/Input/composables/useInputClasses.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import type { InputSize } from '@/components/Input/types'
|
||||
import { simplifyTailwindClasses } from '@/utils/simplifyTailwindClasses'
|
||||
|
||||
// LABEL
|
||||
const defaultLabelClasses = 'block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300'
|
||||
|
||||
// INPUT
|
||||
const defaultInputClasses = 'bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'
|
||||
const disabledInputClasses = 'cursor-not-allowed bg-gray-100'
|
||||
const inputSizeClasses: Record<InputSize, string> = {
|
||||
lg: 'p-4',
|
||||
md: 'p-2.5 text-sm',
|
||||
sm: 'p-2 text-sm',
|
||||
}
|
||||
|
||||
export type UseInputClassesProps = {
|
||||
size: Ref<InputSize>
|
||||
disabled: Ref<boolean>
|
||||
}
|
||||
|
||||
export function useInputClasses(props: UseInputClassesProps): {
|
||||
inputClasses: Ref<string>
|
||||
labelClasses: Ref<string>
|
||||
} {
|
||||
const inputClasses = computed(() => {
|
||||
return simplifyTailwindClasses(defaultInputClasses, inputSizeClasses[props.size.value], props.disabled.value ? disabledInputClasses : '')
|
||||
})
|
||||
|
||||
const labelClasses = computed(() => {
|
||||
return defaultLabelClasses
|
||||
})
|
||||
|
||||
return {
|
||||
inputClasses,
|
||||
labelClasses,
|
||||
}
|
||||
}
|
||||
1
src/components/Input/types.ts
Normal file
1
src/components/Input/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type InputSize = 'sm' | 'md' | 'lg'
|
||||
@@ -1,8 +1,15 @@
|
||||
<template>
|
||||
<div id="tooltip-default" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-700">
|
||||
<div>
|
||||
<button data-tooltip-target="tooltip-default" type="button"
|
||||
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
|
||||
Default tooltip
|
||||
</button>
|
||||
<div id="tooltip-default" role="tooltip"
|
||||
class="inline-block absolute z-10 py-2 px-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm transition-opacity duration-300 tooltip dark:bg-gray-700">
|
||||
Tooltip content
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<component :is="tag" :class="simplifyTailwindClasses($attrs.class || [], classes)">
|
||||
<component :is="tag" :class="simplifyTailwindClasses(classAttr, classes)">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
@@ -11,10 +11,12 @@ import {
|
||||
import type {
|
||||
ThemableChildrenApply,
|
||||
} from '@/components/utils/FlowbiteThemable/components/FlowbiteThemableChild/types'
|
||||
import { toRefs } from 'vue'
|
||||
import { computed, toRefs, useAttrs } from 'vue'
|
||||
import { simplifyTailwindClasses } from '@/utils/simplifyTailwindClasses'
|
||||
import type { FlowbiteTheme } from '@/components/utils/FlowbiteThemable/types'
|
||||
|
||||
const attrs = useAttrs()
|
||||
|
||||
const props = defineProps({
|
||||
apply: {
|
||||
type: Array as PropType<ThemableChildrenApply[]>,
|
||||
@@ -31,4 +33,8 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const { classes } = useFlowbiteThemableChildClasses(toRefs(props))
|
||||
|
||||
const classAttr = computed<string>(() => {
|
||||
return attrs.class as string || '' // TODO:
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -29,4 +29,6 @@ export { default as Toast } from './components/Toast/Toast.vue'
|
||||
export { default as ToastProvider } from './components/Toast/components/ToastProvider/ToastProvider.vue'
|
||||
export { default as Tooltip } from './components/Tooltip/Tooltip.vue'
|
||||
|
||||
export { default as Input } from './components/Input/Input.vue'
|
||||
|
||||
export * from './composables'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist_types",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
|
||||
Reference in New Issue
Block a user