Skip to content

Commit e17b522

Browse files
committed
FwbInput component refactored to enchance its flexibility
1 parent b505a93 commit e17b522

File tree

5 files changed

+121
-95
lines changed

5 files changed

+121
-95
lines changed

docs/components/input/examples/FwbInputExampleBlockClasses.vue renamed to docs/components/input/examples/FwbInputExampleStyling.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
<div class="vp-raw">
33
<fwb-input
44
v-model="name"
5+
class="p-2 border border-black rounded-none italic text-a"
6+
input-class="p-0 text-center text-gray-700 dark:text-gray-200"
7+
label-class="text-center text-gray-200 dark:text-gray-400 p-2 m-0"
58
label="First name"
69
placeholder="enter your first name"
7-
class="bg-green-200 dark:bg-green-700"
8-
block-classes="border-2 border-green-500 p-2 rounded-lg"
10+
wrapper-class=" background-gray-100 dark:bg-gray-800"
911
/>
1012
</div>
1113
</template>
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
<template>
22
<div class="vp-raw">
33
<fwb-input
4-
v-model="email"
4+
v-model="name"
5+
label="Your name"
6+
placeholder="Success input"
57
required
6-
placeholder="enter your email address"
7-
label="Email"
88
validation-status="success"
9-
/>
9+
>
10+
<template #validationMessage>
11+
<span class="font-medium">Well done!</span> Some success message.
12+
</template>
13+
</fwb-input>
1014
<hr class="mt-4 border-0">
1115
<fwb-input
12-
v-model="email"
16+
v-model="name"
17+
label="Your name"
18+
placeholder="Error input"
1319
required
14-
placeholder="enter your email address"
15-
label="Email"
1620
validation-status="error"
1721
>
1822
<template #validationMessage>
19-
Please enter a valid email address
23+
<span class="font-medium">Oh, snapp!</span> Some error message.
2024
</template>
2125
</fwb-input>
2226
</div>
@@ -27,5 +31,5 @@ import { ref } from 'vue'
2731
2832
import { FwbInput } from '../../../../src/index'
2933
30-
const email = ref('')
34+
const name = ref('')
3135
</script>

src/components/FwbInput/FwbInput.vue

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
<template>
2-
<div :class="blockClasses">
2+
<div :class="wrapperClass">
33
<label
44
v-if="label"
5-
:class="labelClasses"
5+
:class="labelClass"
66
>{{ label }}</label>
7-
<div
8-
class="relative flex items-center"
9-
:class="[inputBlockClasses]"
10-
>
7+
<div :class="inputWrapperClass">
118
<div
129
v-if="$slots.prefix"
1310
class="ms-2 flex shrink-0 items-center"
@@ -17,11 +14,11 @@
1714
<input
1815
v-bind="$attrs"
1916
v-model="model"
17+
:autocomplete="autocomplete"
18+
:class="inputClass"
2019
:disabled="disabled"
21-
:type="type"
2220
:required="required"
23-
:autocomplete="autocomplete"
24-
:class="[inputClasses]"
21+
:type="type"
2522
>
2623
<div
2724
v-if="$slots.suffix"
@@ -32,69 +29,70 @@
3229
</div>
3330
<p
3431
v-if="$slots.validationMessage"
35-
:class="validationWrapperClasses"
32+
:class="validationMessageClass"
3633
>
3734
<slot name="validationMessage" />
3835
</p>
3936
<p
4037
v-if="$slots.helper"
41-
class="mt-2 text-sm text-gray-500 dark:text-gray-400"
38+
:class="helperMessageClass"
39+
class=""
4240
>
4341
<slot name="helper" />
4442
</p>
4543
</div>
4644
</template>
4745

4846
<script lang="ts" setup>
49-
import { useVModel } from '@vueuse/core'
50-
import { twMerge } from 'tailwind-merge'
51-
import { computed, toRefs } from 'vue'
47+
48+
import { toRefs } from 'vue'
5249
5350
import { useInputClasses } from './composables/useInputClasses'
54-
import {
55-
type CommonAutoFill,
56-
type InputSize,
57-
type InputType,
58-
type ValidationStatus,
59-
validationStatusMap,
60-
} from './types'
51+
52+
import type { CommonAutoFill, InputSize, InputType, ValidationStatus } from './types'
6153
6254
interface InputProps {
55+
autocomplete?: CommonAutoFill
56+
class?: string | Record<string, boolean>
6357
disabled?: boolean
58+
inputClass?: string | Record<string, boolean>
6459
label?: string
60+
labelClass?: string | Record<string, boolean>
6561
modelValue?: string | number
6662
required?: boolean
6763
size?: InputSize
6864
type?: InputType
69-
autocomplete?: CommonAutoFill
7065
validationStatus?: ValidationStatus
71-
blockClasses?: string | string[] | Record<string, unknown>
66+
wrapperClass?: string | Record<string, boolean>
7267
}
7368
7469
defineOptions({
7570
inheritAttrs: false,
7671
})
7772
7873
const props = withDefaults(defineProps<InputProps>(), {
74+
autocomplete: 'off',
75+
class: '',
7976
disabled: false,
77+
inputClass: '',
8078
label: '',
79+
labelClass: '',
8180
modelValue: '',
8281
required: false,
8382
size: 'md',
8483
type: 'text',
85-
autocomplete: 'off',
8684
validationStatus: undefined,
87-
blockClasses: undefined,
85+
wrapperClass: '',
8886
})
8987
90-
const model = useVModel(props, 'modelValue')
91-
92-
const { inputClasses, inputBlockClasses, labelClasses } = useInputClasses(toRefs(props))
93-
94-
const validationWrapperClasses = computed(() => twMerge(
95-
'mt-2 text-sm',
96-
props.validationStatus === validationStatusMap.Success ? 'text-green-600 dark:text-green-500' : '',
97-
props.validationStatus === validationStatusMap.Error ? 'text-red-600 dark:text-red-500' : '',
88+
const model = defineModel({ type: String })
9889
99-
))
90+
const {
91+
wrapperClass,
92+
helperMessageClass,
93+
validationMessageClass,
94+
labelClass,
95+
inputWrapperClass,
96+
inputClass,
97+
} = useInputClasses(toRefs(props))
10098
</script>
Lines changed: 71 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,98 @@
1-
import { twMerge } from 'tailwind-merge'
21
import { computed, type Ref } from 'vue'
32

4-
import {
5-
type InputSize,
6-
type ValidationStatus,
7-
validationStatusMap,
8-
} from '../types'
3+
import { type InputSize, type ValidationStatus, validationStatusMap } from '../types'
94

10-
// LABEL
11-
const baseLabelClasses = 'block mb-2 text-sm font-medium'
5+
import { useMergeClasses } from '@/composables/useMergeClasses'
126

13-
// INPUT
14-
const defaultInputClasses = 'block flex-grow w-full p-0 bg-transparent text-inherit ring-offset-0 ring-0 border-0 focus:ring-offset-0 focus:ring-0 focus:border-0'
7+
const defaultWrapperClasses = ''
8+
const defaultLabelClasses = 'block mb-2 text-sm font-medium'
9+
const defaultInputWrapperClasses = 'relative flex items-center has-[input:focus]:ring-offset-0 has-[input:focus]:ring-1 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg has-[input:focus]:ring-blue-500 has-[input:focus]:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:has-[input:focus]:ring-blue-500 dark:has-[input:focus]:border-blue-500'
10+
const defaultInputClasses = 'block flex-grow w-full p-0 bg-transparent text-inherit ring-offset-0 ring-0 border-0 focus:ring-offset-0 focus:ring-0 focus:border-0 dark:placeholder-gray-400'
11+
const defaultHelperClasses = 'mt-2 text-sm text-gray-500 dark:text-gray-400'
1512

16-
// BLOCK
17-
const defaultBlockClasses = 'has-[input:focus]:ring-offset-0 has-[input:focus]:ring-1 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg has-[input:focus]:ring-blue-500 has-[input:focus]:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:has-[input:focus]:ring-blue-500 dark:has-[input:focus]:border-blue-500'
13+
const disabledInputWrapperClasses = 'bg-gray-100'
14+
const disabledInputClasses = 'cursor-not-allowed'
1815

19-
const disabledInputClasses = 'cursor-not-allowed bg-gray-100'
2016
const inputSizeClasses: Record<InputSize, string> = {
21-
lg: 'p-4',
22-
md: 'p-2.5 text-sm',
2317
sm: 'p-2 text-sm',
18+
md: 'p-2.5 text-sm',
19+
lg: 'p-4',
2420
}
2521

26-
const successInputClasses = 'bg-green-50 border-green-500 dark:border-green-500 text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 has-[input:focus]:ring-green-500 has-[input:focus]:border-green-500'
27-
const errorInputClasses = 'bg-red-50 border-red-500 text-red-900 placeholder-red-700 has-[input:focus]:ring-red-500 has-[input:focus]:border-red-500 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500'
22+
const errorInputWrapperClasses = 'bg-red-50 border-red-500 text-red-900 placeholder-red-700 has-[input:focus]:ring-red-500 has-[input:focus]:border-red-500 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500'
23+
const errorTextClasses = 'text-red-700 dark:text-red-500'
24+
const successInputWrapperClasses = 'bg-green-50 border-green-500 dark:border-green-500 text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 has-[input:focus]:ring-green-500 has-[input:focus]:border-green-500 '
25+
const successTextClasses = 'text-green-700 dark:text-green-500'
26+
const errorInputClasses = 'text-red-900 placeholder-red-700 dark:placeholder-red-500'
27+
const successInputClasses = 'text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500'
2828

2929
export type UseInputClassesProps = {
30-
size: Ref<InputSize>
30+
class: Ref<string | Record<string, boolean>>
3131
disabled: Ref<boolean>
32+
inputClass: Ref<string | Record<string, boolean>>
33+
labelClass: Ref<string | Record<string, boolean>>
34+
size: Ref<InputSize>
3235
validationStatus: Ref<ValidationStatus | undefined>
36+
wrapperClass: Ref<string | Record<string, boolean>>
3337
}
3438

3539
export function useInputClasses (props: UseInputClassesProps): {
36-
inputBlockClasses: Ref<string>
37-
inputClasses: Ref<string>
38-
labelClasses: Ref<string>
40+
helperMessageClass: Ref<string>
41+
inputClass: Ref<string>
42+
inputWrapperClass: Ref<string>
43+
labelClass: Ref<string>
44+
validationMessageClass: Ref<string>
45+
wrapperClass: Ref<string>
3946
} {
40-
const inputBlockClasses = computed(() => {
41-
const vs = props.validationStatus.value
47+
const wrapperClass = computed(() => useMergeClasses([
48+
defaultWrapperClasses,
49+
props.wrapperClass.value,
50+
]))
4251

43-
const classByStatus = vs === validationStatusMap.Success
44-
? successInputClasses
45-
: vs === validationStatusMap.Error
46-
? errorInputClasses
47-
: ''
52+
const labelClass = computed(() => useMergeClasses([
53+
defaultLabelClasses,
54+
props.labelClass.value,
55+
props.validationStatus.value === validationStatusMap.Success
56+
? successTextClasses
57+
: props.validationStatus.value === validationStatusMap.Error ? errorTextClasses : '',
58+
]))
4859

49-
return twMerge(
50-
defaultBlockClasses,
51-
classByStatus,
52-
props.disabled.value ? disabledInputClasses : '',
53-
)
54-
})
60+
const inputWrapperClass = computed(() => useMergeClasses([
61+
defaultInputWrapperClasses,
62+
props.class.value,
63+
props.validationStatus.value === validationStatusMap.Success
64+
? successInputWrapperClasses
65+
: props.validationStatus.value === validationStatusMap.Error ? errorInputWrapperClasses : '',
66+
props.disabled.value ? disabledInputWrapperClasses : '',
67+
]))
5568

56-
const inputClasses = computed(() => {
57-
return twMerge(defaultInputClasses, inputSizeClasses[props.size.value])
58-
})
69+
const inputClass = computed(() => useMergeClasses([
70+
defaultInputClasses,
71+
inputSizeClasses[props.size.value],
72+
props.validationStatus.value === validationStatusMap.Success
73+
? successInputClasses
74+
: props.validationStatus.value === validationStatusMap.Error ? errorInputClasses : '',
75+
props.inputClass.value,
76+
props.disabled.value ? disabledInputClasses : '',
77+
]))
5978

60-
const labelClasses = computed(() => {
61-
const vs = props.validationStatus.value
62-
const classByStatus = vs === validationStatusMap.Success
63-
? 'text-green-700 dark:text-green-500'
64-
: vs === validationStatusMap.Error
65-
? 'text-red-700 dark:text-red-500'
66-
: 'text-gray-900 dark:text-white'
79+
const validationMessageClass = computed(() => useMergeClasses([
80+
defaultHelperClasses,
81+
props.validationStatus.value === validationStatusMap.Success
82+
? successTextClasses
83+
: props.validationStatus.value === validationStatusMap.Error ? errorTextClasses : '',
84+
]))
6785

68-
return twMerge(baseLabelClasses, classByStatus)
69-
})
86+
const helperMessageClass = computed(() => useMergeClasses([
87+
defaultHelperClasses,
88+
]))
7089

7190
return {
72-
inputBlockClasses,
73-
inputClasses,
74-
labelClasses,
91+
helperMessageClass,
92+
inputClass,
93+
inputWrapperClass,
94+
labelClass,
95+
validationMessageClass,
96+
wrapperClass,
7597
}
7698
}

src/components/FwbInput/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ export type InputType = 'button' | 'checkbox' | 'color' | 'date' | 'datetime-loc
66
export type CommonAutoFill = 'on' | 'off' | 'email' | 'tel' | 'name' | 'username' | 'current-password' | 'country' | 'postal-code' | 'language' | 'bday'
77

88
export const validationStatusMap = {
9-
Success: 'success',
109
Error: 'error',
10+
Success: 'success',
1111
} as const
1212

1313
export type ValidationStatus = typeof validationStatusMap[keyof typeof validationStatusMap]

0 commit comments

Comments
 (0)