aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Corentin Thomasset <corentin.thomasset74@gmail.com> 2023-05-14 21:26:18 +0200
committerGravatar Corentin THOMASSET <corentin.thomasset74@gmail.com> 2023-05-14 22:30:23 +0200
commit77f2efc0b92847c3b1198446f4b520ecb2263164 (patch)
tree942ea5ffd922dbe7a9913e235a4fae17b117c925
parentaad8d84e13ce31c1b7c1cbb930fb8bd4c0abe13a (diff)
downloadit-tools-77f2efc0b92847c3b1198446f4b520ecb2263164.tar.gz
it-tools-77f2efc0b92847c3b1198446f4b520ecb2263164.tar.zst
it-tools-77f2efc0b92847c3b1198446f4b520ecb2263164.zip
refactor(ui): replaced some n-input with c-input-text
-rw-r--r--auto-imports.d.ts72
-rw-r--r--components.d.ts5
-rw-r--r--src/components/FormatTransformer.vue4
-rw-r--r--src/components/InputCopyable.vue15
-rw-r--r--src/tools/base64-string-converter/base64-string-converter.vue2
-rw-r--r--src/tools/basic-auth-generator/basic-auth-generator.vue22
-rw-r--r--src/tools/bcrypt/bcrypt.vue49
-rw-r--r--src/tools/benchmark-builder/benchmark-builder.vue18
-rw-r--r--src/tools/bip39-generator/bip39-generator.vue13
-rw-r--r--src/tools/case-converter/case-converter.vue12
-rw-r--r--src/tools/chronometer/chronometer.vue4
-rw-r--r--src/tools/color-converter/color-converter.vue34
-rw-r--r--src/tools/crontab-generator/crontab-generator.vue49
-rw-r--r--src/tools/date-time-converter/date-time-converter.vue27
-rw-r--r--src/tools/encryption/encryption.vue18
-rw-r--r--src/tools/integer-base-converter/integer-base-converter.vue93
-rw-r--r--src/tools/ipv4-address-converter/ipv4-address-converter.vue25
-rw-r--r--src/tools/ipv4-range-expander/ipv4-range-expander.vue4
-rw-r--r--src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue26
-rw-r--r--src/tools/ipv6-ula-generator/ipv6-ula-generator.vue50
-rw-r--r--src/tools/json-diff/json-diff.vue8
-rw-r--r--src/tools/list-converter/list-converter.e2e.spec.ts4
-rw-r--r--src/tools/list-converter/list-converter.vue56
-rw-r--r--src/tools/mac-address-lookup/mac-address-lookup.vue2
-rw-r--r--src/tools/meta-tag-generator/meta-tag-generator.vue2
-rw-r--r--src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue126
-rw-r--r--src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue11
-rw-r--r--src/tools/roman-numeral-converter/roman-numeral-converter.vue11
-rw-r--r--src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue2
-rw-r--r--src/tools/svg-placeholder-generator/svg-placeholder-generator.vue12
-rw-r--r--src/tools/text-to-nato-alphabet/text-to-nato-alphabet.vue10
-rw-r--r--src/tools/url-parser/url-parser.vue70
-rw-r--r--src/ui/c-button/c-button.demo.vue14
-rw-r--r--src/ui/c-button/c-button.theme.ts16
-rw-r--r--src/ui/c-button/c-button.vue2
-rw-r--r--src/ui/c-input-text/c-input-text.demo.vue45
-rw-r--r--src/ui/c-input-text/c-input-text.test.ts79
-rw-r--r--src/ui/c-input-text/c-input-text.vue180
-rw-r--r--src/ui/theme/themes.ts2
39 files changed, 742 insertions, 452 deletions
diff --git a/auto-imports.d.ts b/auto-imports.d.ts
index 2850890..9dccb44 100644
--- a/auto-imports.d.ts
+++ b/auto-imports.d.ts
@@ -19,7 +19,9 @@ declare global {
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
+ const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
+ const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
const customRef: typeof import('vue')['customRef']
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
@@ -39,9 +41,6 @@ declare global {
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
- const logicAnd: typeof import('@vueuse/core')['logicAnd']
- const logicNot: typeof import('@vueuse/core')['logicNot']
- const logicOr: typeof import('@vueuse/core')['logicOr']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
@@ -92,8 +91,9 @@ declare global {
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
const toRaw: typeof import('vue')['toRaw']
const toReactive: typeof import('@vueuse/core')['toReactive']
- const toRef: typeof import('vue')['toRef']
+ const toRef: typeof import('@vueuse/core')['toRef']
const toRefs: typeof import('vue')['toRefs']
+ const toValue: typeof import('@vueuse/core')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
@@ -104,6 +104,19 @@ declare global {
const unrefElement: typeof import('@vueuse/core')['unrefElement']
const until: typeof import('@vueuse/core')['until']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
+ const useAnimate: typeof import('@vueuse/core')['useAnimate']
+ const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
+ const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
+ const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
+ const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
+ const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
+ const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
+ const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
+ const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
+ const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
+ const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
+ const useArraySome: typeof import('@vueuse/core')['useArraySome']
+ const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
@@ -114,8 +127,8 @@ declare global {
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
const useCached: typeof import('@vueuse/core')['useCached']
- const useClamp: typeof import('@vueuse/core')['useClamp']
const useClipboard: typeof import('@vueuse/core')['useClipboard']
+ const useCloned: typeof import('@vueuse/core')['useCloned']
const useColorMode: typeof import('@vueuse/core')['useColorMode']
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
const useCounter: typeof import('@vueuse/core')['useCounter']
@@ -189,12 +202,18 @@ declare global {
const useOnline: typeof import('@vueuse/core')['useOnline']
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
const useParallax: typeof import('@vueuse/core')['useParallax']
+ const useParentElement: typeof import('@vueuse/core')['useParentElement']
+ const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointer: typeof import('@vueuse/core')['usePointer']
+ const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
+ const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
+ const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
+ const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
@@ -208,14 +227,17 @@ declare global {
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useShare: typeof import('@vueuse/core')['useShare']
const useSlots: typeof import('vue')['useSlots']
+ const useSorted: typeof import('@vueuse/core')['useSorted']
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
const useStepper: typeof import('@vueuse/core')['useStepper']
const useStorage: typeof import('@vueuse/core')['useStorage']
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
+ const useSupported: typeof import('@vueuse/core')['useSupported']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
+ const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
const useThrottle: typeof import('@vueuse/core')['useThrottle']
@@ -227,6 +249,8 @@ declare global {
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
const useTitle: typeof import('@vueuse/core')['useTitle']
+ const useToNumber: typeof import('@vueuse/core')['useToNumber']
+ const useToString: typeof import('@vueuse/core')['useToString']
const useToggle: typeof import('@vueuse/core')['useToggle']
const useTransition: typeof import('@vueuse/core')['useTransition']
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
@@ -247,8 +271,10 @@ declare global {
const watchArray: typeof import('@vueuse/core')['watchArray']
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
+ const watchDeep: typeof import('@vueuse/core')['watchDeep']
const watchEffect: typeof import('vue')['watchEffect']
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
+ const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
const watchOnce: typeof import('@vueuse/core')['watchOnce']
const watchPausable: typeof import('@vueuse/core')['watchPausable']
const watchPostEffect: typeof import('vue')['watchPostEffect']
@@ -282,7 +308,9 @@ declare module 'vue' {
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
+ readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
+ readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
@@ -302,9 +330,6 @@ declare module 'vue' {
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
- readonly logicAnd: UnwrapRef<typeof import('@vueuse/core')['logicAnd']>
- readonly logicNot: UnwrapRef<typeof import('@vueuse/core')['logicNot']>
- readonly logicOr: UnwrapRef<typeof import('@vueuse/core')['logicOr']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
@@ -355,8 +380,9 @@ declare module 'vue' {
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
- readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
+ readonly toRef: UnwrapRef<typeof import('@vueuse/core')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
+ readonly toValue: UnwrapRef<typeof import('@vueuse/core')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
@@ -367,6 +393,19 @@ declare module 'vue' {
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
+ readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
+ readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
+ readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
+ readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
+ readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
+ readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
+ readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
+ readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
+ readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
+ readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
+ readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
+ readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
+ readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
@@ -377,8 +416,8 @@ declare module 'vue' {
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
- readonly useClamp: UnwrapRef<typeof import('@vueuse/core')['useClamp']>
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
+ readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
@@ -452,12 +491,18 @@ declare module 'vue' {
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
+ readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
+ readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
+ readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
+ readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
+ readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
+ readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
@@ -471,14 +516,17 @@ declare module 'vue' {
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
+ readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
+ readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
+ readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
@@ -490,6 +538,8 @@ declare module 'vue' {
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
+ readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
+ readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
@@ -510,8 +560,10 @@ declare module 'vue' {
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
+ readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
+ readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
diff --git a/components.d.ts b/components.d.ts
index 65c82ba..40b805a 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -56,7 +56,12 @@ declare module '@vue/runtime-core' {
HtmlEntities: typeof import('./src/tools/html-entities/html-entities.vue')['default']
HtmlWysiwygEditor: typeof import('./src/tools/html-wysiwyg-editor/html-wysiwyg-editor.vue')['default']
HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default']
+ IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default']
+ IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
+ IconMdiEye: typeof import('~icons/mdi/eye')['default']
+ IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default']
+ IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
InputCopyable: typeof import('./src/components/InputCopyable.vue')['default']
IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default']
Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default']
diff --git a/src/components/FormatTransformer.vue b/src/components/FormatTransformer.vue
index dea5d56..d187632 100644
--- a/src/components/FormatTransformer.vue
+++ b/src/components/FormatTransformer.vue
@@ -1,5 +1,5 @@
<template>
- <n-form-item :label="inputLabel" v-bind="validationAttrs">
+ <n-form-item :label="inputLabel" v-bind="validationAttrs as any">
<n-input
ref="inputElement"
v-model:value="input"
@@ -10,7 +10,7 @@
autocorrect="off"
autocapitalize="off"
spellcheck="false"
- :input-props="{ 'data-test-id': 'input' }"
+ :input-props="{ 'data-test-id': 'input' } as any"
/>
</n-form-item>
<n-form-item :label="outputLabel">
diff --git a/src/components/InputCopyable.vue b/src/components/InputCopyable.vue
index eaf29c8..cf7a42a 100644
--- a/src/components/InputCopyable.vue
+++ b/src/components/InputCopyable.vue
@@ -1,21 +1,20 @@
<template>
- <n-input v-model:value="value">
+ <c-input-text v-model:value="value">
<template #suffix>
<n-tooltip trigger="hover">
<template #trigger>
- <c-button circle variant="text" @click="onCopyClicked">
- <n-icon :component="ContentCopyFilled" />
+ <c-button circle variant="text" size="small" @click="onCopyClicked">
+ <icon-mdi-content-copy />
</c-button>
</template>
{{ tooltipText }}
</n-tooltip>
</template>
- </n-input>
+ </c-input-text>
</template>
<script setup lang="ts">
import { useVModel, useClipboard } from '@vueuse/core';
-import { ContentCopyFilled } from '@vicons/material';
import { ref } from 'vue';
const props = defineProps<{ value: string }>();
@@ -35,9 +34,3 @@ function onCopyClicked() {
}, 2000);
}
</script>
-
-<style scoped>
-::v-deep(.n-input-wrapper) {
- padding-right: 5px;
-}
-</style>
diff --git a/src/tools/base64-string-converter/base64-string-converter.vue b/src/tools/base64-string-converter/base64-string-converter.vue
index be4a635..e97c884 100644
--- a/src/tools/base64-string-converter/base64-string-converter.vue
+++ b/src/tools/base64-string-converter/base64-string-converter.vue
@@ -20,7 +20,7 @@
</c-card>
<c-card title="Base64 to string">
- <n-form-item label="Base64 string to decode" v-bind="b64Validation.attrs">
+ <n-form-item label="Base64 string to decode" v-bind="b64Validation.attrs as any">
<n-input v-model:value="base64Input" type="textarea" placeholder="Your base64 string..." rows="5" />
</n-form-item>
diff --git a/src/tools/basic-auth-generator/basic-auth-generator.vue b/src/tools/basic-auth-generator/basic-auth-generator.vue
index 3cab019..6764748 100644
--- a/src/tools/basic-auth-generator/basic-auth-generator.vue
+++ b/src/tools/basic-auth-generator/basic-auth-generator.vue
@@ -1,17 +1,15 @@
<template>
<div>
- <n-form-item label="Username">
- <n-input v-model:value="username" placeholder="Your username..." clearable />
- </n-form-item>
- <n-form-item label="Password">
- <n-input
- v-model:value="password"
- placeholder="Your password..."
- type="password"
- show-password-on="click"
- clearable
- />
- </n-form-item>
+ <c-input-text v-model:value="username" label="Username" placeholder="Your username..." clearable raw-text mb-5 />
+ <c-input-text
+ v-model:value="password"
+ label="Password"
+ placeholder="Your password..."
+ clearable
+ raw-text
+ mb-2
+ type="password"
+ />
<c-card>
<n-statistic label="Authorization header:" class="header">
diff --git a/src/tools/bcrypt/bcrypt.vue b/src/tools/bcrypt/bcrypt.vue
index a091fb7..c0c86f6 100644
--- a/src/tools/bcrypt/bcrypt.vue
+++ b/src/tools/bcrypt/bcrypt.vue
@@ -1,21 +1,20 @@
<template>
<c-card title="Hash">
- <n-form label-width="120">
- <n-form-item label="Your string: " label-placement="left">
- <n-input
- v-model:value="input"
- placeholder="Your string to bcrypt..."
- autocomplete="off"
- autocorrect="off"
- autocapitalize="off"
- spellcheck="false"
- />
- </n-form-item>
- <n-form-item label="Salt count: " label-placement="left">
- <n-input-number v-model:value="saltCount" placeholder="Salt rounds..." :max="10" :min="0" w-full />
- </n-form-item>
- <n-input :value="hashed" readonly style="text-align: center" />
- </n-form>
+ <c-input-text
+ v-model:value="input"
+ placeholder="Your string to bcrypt..."
+ raw-text
+ label="Your string: "
+ label-position="left"
+ label-width="120px"
+ mb-2
+ />
+ <n-form-item label="Salt count: " label-placement="left" label-width="120">
+ <n-input-number v-model:value="saltCount" placeholder="Salt rounds..." :max="10" :min="0" w-full />
+ </n-form-item>
+
+ <c-input-text :value="hashed" readonly text-center />
+
<n-space justify="center" mt-5>
<c-button @click="copy"> Copy hash </c-button>
</n-space>
@@ -24,24 +23,10 @@
<c-card title="Compare string with hash">
<n-form label-width="120">
<n-form-item label="Your string: " label-placement="left">
- <n-input
- v-model:value="compareString"
- placeholder="Your string to compare..."
- autocomplete="off"
- autocorrect="off"
- autocapitalize="off"
- spellcheck="false"
- />
+ <c-input-text v-model:value="compareString" placeholder="Your string to compare..." raw-text />
</n-form-item>
<n-form-item label="Your hash: " label-placement="left">
- <n-input
- v-model:value="compareHash"
- placeholder="Your hahs to compare..."
- autocomplete="off"
- autocorrect="off"
- autocapitalize="off"
- spellcheck="false"
- />
+ <c-input-text v-model:value="compareHash" placeholder="Your hahs to compare..." raw-text />
</n-form-item>
<n-form-item label="Do they match ? " label-placement="left" :show-feedback="false">
<div class="compare-result" :class="{ positive: compareMatch }">
diff --git a/src/tools/benchmark-builder/benchmark-builder.vue b/src/tools/benchmark-builder/benchmark-builder.vue
index 9943e06..3a6182f 100644
--- a/src/tools/benchmark-builder/benchmark-builder.vue
+++ b/src/tools/benchmark-builder/benchmark-builder.vue
@@ -1,11 +1,15 @@
<template>
<n-scrollbar style="flex: 1" x-scrollable>
- <n-space :wrap="false" style="flex: 1" justify="center" :size="0" mb-5>
+ <n-space :wrap="false" style="flex: 1" justify="center" :size="12" mb-5>
<div v-for="(suite, index) of suites" :key="index">
- <c-card style="width: 292px; margin: 0 8px 5px">
- <n-form-item label="Suite name:" :show-feedback="false" label-placement="left">
- <n-input v-model:value="suite.title" placeholder="Suite name..." />
- </n-form-item>
+ <c-card style="width: 294px">
+ <c-input-text
+ v-model:value="suite.title"
+ label-position="left"
+ label="Suite name"
+ placeholder="Suite name..."
+ clearable
+ />
<n-divider></n-divider>
<n-form-item label="Suite values" :show-feedback="false">
@@ -33,9 +37,7 @@
<div style="flex: 0 0 100%">
<div style="max-width: 600px; margin: 0 auto">
<n-space justify="center">
- <n-form-item label="Unit:" label-placement="left">
- <n-input v-model:value="unit" placeholder="Unit (eg: ms)" />
- </n-form-item>
+ <c-input-text v-model:value="unit" placeholder="Unit (eg: ms)" label="Unit" label-position="left" mb-4 />
<c-button
@click="
diff --git a/src/tools/bip39-generator/bip39-generator.vue b/src/tools/bip39-generator/bip39-generator.vue
index 418ae0e..1aaa6e9 100644
--- a/src/tools/bip39-generator/bip39-generator.vue
+++ b/src/tools/bip39-generator/bip39-generator.vue
@@ -16,7 +16,8 @@
:validation-status="entropyValidation.status"
>
<n-input-group>
- <n-input v-model:value="entropy" placeholder="Your string..." />
+ <c-input-text v-model:value="entropy" placeholder="Your string..." />
+
<c-button @click="refreshEntropy">
<n-icon size="22">
<Refresh />
@@ -37,15 +38,7 @@
:validation-status="mnemonicValidation.status"
>
<n-input-group>
- <n-input
- v-model:value="passphrase"
- style="text-align: center; flex: 1"
- placeholder="Your mnemonic..."
- autocomplete="off"
- autocorrect="off"
- autocapitalize="off"
- spellcheck="false"
- />
+ <c-input-text v-model:value="passphrase" placeholder="Your mnemonic..." raw-text />
<c-button @click="copyPassphrase">
<n-icon size="22" :component="Copy" />
diff --git a/src/tools/case-converter/case-converter.vue b/src/tools/case-converter/case-converter.vue
index 854eb91..2ad3656 100644
--- a/src/tools/case-converter/case-converter.vue
+++ b/src/tools/case-converter/case-converter.vue
@@ -1,9 +1,15 @@
<template>
<c-card>
<n-form label-width="120" label-placement="left" :show-feedback="false">
- <n-form-item label="Your string:">
- <n-input v-model:value="input" />
- </n-form-item>
+ <c-input-text
+ v-model:value="input"
+ label="Your string"
+ label-position="left"
+ label-width="120px"
+ label-align="right"
+ placeholder="Your string..."
+ raw-text
+ />
<n-divider />
diff --git a/src/tools/chronometer/chronometer.vue b/src/tools/chronometer/chronometer.vue
index d3cd425..0c8eb09 100644
--- a/src/tools/chronometer/chronometer.vue
+++ b/src/tools/chronometer/chronometer.vue
@@ -4,8 +4,8 @@
<div class="duration">{{ formatMs(counter) }}</div>
</c-card>
<n-space justify="center" mt-5>
- <c-button v-if="!isRunning" secondary type="primary" @click="resume">Start</c-button>
- <c-button v-else secondary type="warning" @click="pause">Stop</c-button>
+ <c-button v-if="!isRunning" type="primary" @click="resume">Start</c-button>
+ <c-button v-else type="warning" @click="pause">Stop</c-button>
<c-button @click="counter = 0">Reset</c-button>
</n-space>
diff --git a/src/tools/color-converter/color-converter.vue b/src/tools/color-converter/color-converter.vue
index fff7607..bfd4912 100644
--- a/src/tools/color-converter/color-converter.vue
+++ b/src/tools/color-converter/color-converter.vue
@@ -9,25 +9,25 @@
/>
</n-form-item>
<n-form-item label="color name:">
- <input-copyable v-model:value="name" :on-input="(v: string) => onInputUpdated(v, 'name')" />
+ <input-copyable v-model:value="name" @update:value="(v: string) => onInputUpdated(v, 'name')" />
</n-form-item>
<n-form-item label="hex:">
- <input-copyable v-model:value="hex" :on-input="(v: string) => onInputUpdated(v, 'hex')" />
+ <input-copyable v-model:value="hex" @update:value="(v: string) => onInputUpdated(v, 'hex')" />
</n-form-item>
<n-form-item label="rgb:">
- <input-copyable v-model:value="rgb" :on-input="(v: string) => onInputUpdated(v, 'rgb')" />
+ <input-copyable v-model:value="rgb" @update:value="(v: string) => onInputUpdated(v, 'rgb')" />
</n-form-item>
<n-form-item label="hsl:">
- <input-copyable v-model:value="hsl" :on-input="(v: string) => onInputUpdated(v, 'hsl')" />
+ <input-copyable v-model:value="hsl" @update:value="(v: string) => onInputUpdated(v, 'hsl')" />
</n-form-item>
<n-form-item label="hwb:">
- <input-copyable v-model:value="hwb" :on-input="(v: string) => onInputUpdated(v, 'hwb')" />
+ <input-copyable v-model:value="hwb" @update:value="(v: string) => onInputUpdated(v, 'hwb')" />
</n-form-item>
<n-form-item label="lch:">
- <input-copyable v-model:value="lch" :on-input="(v: string) => onInputUpdated(v, 'lch')" />
+ <input-copyable v-model:value="lch" @update:value="(v: string) => onInputUpdated(v, 'lch')" />
</n-form-item>
<n-form-item label="cmyk:">
- <input-copyable v-model:value="cmyk" :on-input="(v: string) => onInputUpdated(v, 'cmyk')" />
+ <input-copyable v-model:value="cmyk" @update:value="(v: string) => onInputUpdated(v, 'cmyk')" />
</n-form-item>
</n-form>
</c-card>
@@ -54,15 +54,19 @@ const cmyk = ref('');
const lch = ref('');
function onInputUpdated(value: string, omit: string) {
- const color = colord(value);
+ try {
+ const color = colord(value);
- if (omit !== 'name') name.value = color.toName({ closest: true }) ?? '';
- if (omit !== 'hex') hex.value = color.toHex();
- if (omit !== 'rgb') rgb.value = color.toRgbString();
- if (omit !== 'hsl') hsl.value = color.toHslString();
- if (omit !== 'hwb') hwb.value = color.toHwbString();
- if (omit !== 'cmyk') cmyk.value = color.toCmykString();
- if (omit !== 'lch') lch.value = color.toLchString();
+ if (omit !== 'name') name.value = color.toName({ closest: true }) ?? '';
+ if (omit !== 'hex') hex.value = color.toHex();
+ if (omit !== 'rgb') rgb.value = color.toRgbString();
+ if (omit !== 'hsl') hsl.value = color.toHslString();
+ if (omit !== 'hwb') hwb.value = color.toHwbString();
+ if (omit !== 'cmyk') cmyk.value = color.toCmykString();
+ if (omit !== 'lch') lch.value = color.toLchString();
+ } catch {
+ //
+ }
}
onInputUpdated(hex.value, 'hex');
diff --git a/src/tools/crontab-generator/crontab-generator.vue b/src/tools/crontab-generator/crontab-generator.vue
index 978943d..6f8572d 100644
--- a/src/tools/crontab-generator/crontab-generator.vue
+++ b/src/tools/crontab-generator/crontab-generator.vue
@@ -1,13 +1,15 @@
<template>
<c-card>
- <n-form-item
- class="cron"
- :show-label="false"
- :feedback="cronValidation.message"
- :validation-status="cronValidation.status"
- >
- <n-input v-model:value="cron" size="large" placeholder="* * * * *" />
- </n-form-item>
+ <div mx-auto max-w-sm>
+ <c-input-text
+ v-model:value="cron"
+ size="large"
+ placeholder="* * * * *"
+ :validation-rules="cronValidationRules"
+ mb-3
+ />
+ </div>
+
<div class="cron-string">
{{ cronString }}
</div>
@@ -86,7 +88,6 @@
import cronstrue from 'cronstrue';
import { isValidCron } from 'cron-validator';
import { computed, reactive, ref } from 'vue';
-import { useValidation } from '@/composable/validation';
import { useStyleStore } from '@/stores/style.store';
function isCronValid(v: string) {
@@ -185,30 +186,20 @@ const cronString = computed(() => {
return ' ';
});
-const cronValidation = useValidation({
- source: cron,
- rules: [
- {
- validator: (value) => isCronValid(value),
- message: 'This cron is invalid',
- },
- ],
-});
+const cronValidationRules = [
+ {
+ validator: (value: string) => isCronValid(value),
+ message: 'This cron is invalid',
+ },
+];
</script>
<style lang="less" scoped>
-.cron {
+::v-deep(input) {
+ font-size: 30px;
+ font-family: monospace;
+ padding: 5px;
text-align: center;
-
- margin: auto;
- max-width: 400px;
- display: block;
-
- .n-input {
- font-size: 30px;
- font-family: monospace;
- padding: 5px;
- }
}
.cron-string {
diff --git a/src/tools/date-time-converter/date-time-converter.vue b/src/tools/date-time-converter/date-time-converter.vue
index 0d174fd..276c829 100644
--- a/src/tools/date-time-converter/date-time-converter.vue
+++ b/src/tools/date-time-converter/date-time-converter.vue
@@ -1,6 +1,6 @@
<template>
<div>
- <n-form-item :show-label="false" v-bind="validation.attrs">
+ <n-form-item :show-label="false" v-bind="validation.attrs as any">
<n-input-group>
<n-input
v-model:value="inputDate"
@@ -8,7 +8,7 @@
:on-input="onDateInputChanged"
placeholder="Put you date string here..."
clearable
- :input-props="{ 'data-test-id': 'date-time-converter-input' }"
+ :input-props="{ 'data-test-id': 'date-time-converter-input' } as any"
/>
<n-select
@@ -20,16 +20,19 @@
</n-input-group>
</n-form-item>
<n-divider style="margin-top: 0" />
- <div v-for="{ name, fromDate } in formats" :key="name" mt-1>
- <n-input-group>
- <n-input-group-label style="flex: 0 0 170px"> {{ name }}: </n-input-group-label>
- <input-copyable
- :value="formatDateUsingFormatter(fromDate, normalizedDate)"
- placeholder="Invalid date..."
- :input-props="{ 'data-test-id': name }"
- />
- </n-input-group>
- </div>
+ <input-copyable
+ v-for="{ name, fromDate } in formats"
+ :key="name"
+ :label="name"
+ label-width="150px"
+ label-position="left"
+ label-align="right"
+ :value="formatDateUsingFormatter(fromDate, normalizedDate)"
+ placeholder="Invalid date..."
+ :test-id="name"
+ readonly
+ mt-2
+ />
</div>
</template>
diff --git a/src/tools/encryption/encryption.vue b/src/tools/encryption/encryption.vue
index 881dfa2..d4f6e34 100644
--- a/src/tools/encryption/encryption.vue
+++ b/src/tools/encryption/encryption.vue
@@ -7,12 +7,15 @@
type="textarea"
placeholder="The string to cypher"
:autosize="{ minRows: 4 }"
+ autocomplete="off"
+ autocorrect="off"
+ autocapitalize="off"
+ spellcheck="false"
/>
</n-form-item>
<n-space vertical>
- <n-form-item label="Your secret key:" :show-feedback="false">
- <n-input v-model:value="cypherSecret" />
- </n-form-item>
+ <c-input-text v-model:value="cypherSecret" label="Your secret key:" clearable raw-text />
+
<n-form-item label="Encryption algorithm:" :show-feedback="false">
<n-select
v-model:value="cypherAlgo"
@@ -43,12 +46,15 @@
type="textarea"
placeholder="The string to cypher"
:autosize="{ minRows: 4 }"
+ autocomplete="off"
+ autocorrect="off"
+ autocapitalize="off"
+ spellcheck="false"
/>
</n-form-item>
<n-space vertical>
- <n-form-item label="Your secret key:" :show-feedback="false">
- <n-input v-model:value="decryptSecret" />
- </n-form-item>
+ <c-input-text v-model:value="decryptSecret" label="Your secret key:" clearable raw-text />
+
<n-form-item label="Encryption algorithm:" :show-feedback="false">
<n-select
v-model:value="decryptAlgo"
diff --git a/src/tools/integer-base-converter/integer-base-converter.vue b/src/tools/integer-base-converter/integer-base-converter.vue
index c480f4a..058d831 100644
--- a/src/tools/integer-base-converter/integer-base-converter.vue
+++ b/src/tools/integer-base-converter/integer-base-converter.vue
@@ -22,59 +22,54 @@
<n-alert v-if="error" style="margin-top: 25px" type="error">{{ error }}</n-alert>
<n-divider />
- <n-input-group>
- <n-input-group-label style="flex: 0 0 170px"> Binary (2): </n-input-group-label>
- <input-copyable
- :value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 2 })"
- readonly
- placeholder="Binary version will be here..."
- />
- </n-input-group>
+ <input-copyable
+ label="Binary (2)"
+ v-bind="inputProps"
+ :value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 2 })"
+ placeholder="Binary version will be here..."
+ />
- <n-input-group>
- <n-input-group-label style="flex: 0 0 170px"> Octal (8): </n-input-group-label>
- <input-copyable
- :value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 8 })"
- readonly
- placeholder="Octal version will be here..."
- />
- </n-input-group>
+ <input-copyable
+ label="Octal (8)"
+ v-bind="inputProps"
+ :value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 8 })"
+ placeholder="Octal version will be here..."
+ />
- <n-input-group>
- <n-input-group-label style="flex: 0 0 170px"> Decimal (10): </n-input-group-label>
- <input-copyable
- :value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 10 })"
- readonly
- placeholder="Decimal version will be here..."
- />
- </n-input-group>
+ <input-copyable
+ label="Decimal (10)"
+ v-bind="inputProps"
+ :value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 10 })"
+ placeholder="Decimal version will be here..."
+ />
- <n-input-group>
- <n-input-group-label style="flex: 0 0 170px"> Hexadecimal (16): </n-input-group-label>
- <input-copyable
- :value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 16 })"
- readonly
- placeholder="Decimal version will be here..."
- />
- </n-input-group>
+ <input-copyable
+ label="Hexadecimal (16)"
+ v-bind="inputProps"
+ :value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 16 })"
+ placeholder="Hexadecimal version will be here..."
+ />
+
+ <input-copyable
+ label="Base64 (64)"
+ v-bind="inputProps"
+ :value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 64 })"
+ placeholder="Base64 version will be here..."
+ />
+
+ <div flex items-baseline>
+ <n-input-group style="width: 160px; margin-right: 10px">
+ <n-input-group-label> Custom: </n-input-group-label>
+ <n-input-number v-model:value="outputBase" max="64" min="2" />
+ </n-input-group>
- <n-input-group>
- <n-input-group-label style="flex: 0 0 170px"> Base64 (64): </n-input-group-label>
- <input-copyable
- :value="errorlessConvert({ value: input, fromBase: inputBase, toBase: 64 })"
- readonly
- placeholder="Base64 version will be here..."
- />
- </n-input-group>
- <n-input-group>
- <n-input-group-label style="flex: 0 0 85px"> Custom: </n-input-group-label>
- <n-input-number v-model:value="outputBase" style="flex: 0 0 86px" max="64" min="2" />
<input-copyable
+ flex-1
+ v-bind="inputProps"
:value="errorlessConvert({ value: input, fromBase: inputBase, toBase: outputBase })"
- readonly
:placeholder="`Base ${outputBase} will be here...`"
/>
- </n-input-group>
+ </div>
</c-card>
</div>
</template>
@@ -88,6 +83,14 @@ import InputCopyable from '../../components/InputCopyable.vue';
const styleStore = useStyleStore();
+const inputProps = {
+ labelPosition: 'left',
+ labelWidth: '170px',
+ labelAlign: 'right',
+ readonly: true,
+ 'mb-2': '',
+} as const;
+
const input = ref('42');
const inputBase = ref(10);
const outputBase = ref(42);
diff --git a/src/tools/ipv4-address-converter/ipv4-address-converter.vue b/src/tools/ipv4-address-converter/ipv4-address-converter.vue
index 7474608..419b6be 100644
--- a/src/tools/ipv4-address-converter/ipv4-address-converter.vue
+++ b/src/tools/ipv4-address-converter/ipv4-address-converter.vue
@@ -1,23 +1,20 @@
<template>
<div>
- <n-form-item label="An ipv4 address:" v-bind="validationAttrs">
- <n-input v-model:value="rawIpAddress" placeholder="An ipv4 address..." />
- </n-form-item>
+ <c-input-text v-model:value="rawIpAddress" label="The ipv4 address:" placeholder="The ipv4 address..." readonly />
- <n-divider style="margin-top: 0" mt-0 />
+ <n-divider />
- <n-form-item
+ <input-copyable
v-for="{ label, value } of convertedSections"
:key="label"
:label="label"
- label-placement="left"
- label-width="100"
- >
- <input-copyable
- :value="validationAttrs.validationStatus === 'error' ? '' : value"
- placeholder="Set a correct ipv4 address"
- />
- </n-form-item>
+ label-position="left"
+ label-width="100px"
+ label-align="right"
+ mb-2
+ :value="validationAttrs.validationStatus === 'error' ? '' : value"
+ placeholder="Set a correct ipv4 address"
+ />
</div>
</template>
@@ -33,7 +30,7 @@ const convertedSections = computed(() => {
return [
{
- label: 'Decimal : ',
+ label: 'Decimal: ',
value: String(ipInDecimal),
},
{
diff --git a/src/tools/ipv4-range-expander/ipv4-range-expander.vue b/src/tools/ipv4-range-expander/ipv4-range-expander.vue
index 05b15b2..0d9f067 100644
--- a/src/tools/ipv4-range-expander/ipv4-range-expander.vue
+++ b/src/tools/ipv4-range-expander/ipv4-range-expander.vue
@@ -3,10 +3,10 @@
<n-space item-style="flex:1 1 0">
<div>
<n-space item-style="flex:1 1 0">
- <n-form-item label="Start address" v-bind="startIpValidation.attrs">
+ <n-form-item label="Start address" v-bind="startIpValidation.attrs as any">
<n-input v-model:value="rawStartAddress" placeholder="Start IPv4 address..." />
</n-form-item>
- <n-form-item label="End address" v-bind="endIpValidation.attrs">
+ <n-form-item label="End address" v-bind="endIpValidation.attrs as any">
<n-input v-model:value="rawEndAddress" placeholder="End IPv4 address..." />
</n-form-item>
</n-space>
diff --git a/src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue b/src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue
index 6e3d2eb..1767fc9 100644
--- a/src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue
+++ b/src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue
@@ -1,8 +1,12 @@
<template>
<div>
- <n-form-item label="An IPv4 address with or without mask" v-bind="validationAttrs">
- <n-input v-model:value="ip" />
- </n-form-item>
+ <c-input-text
+ v-model:value="ip"
+ label="An IPv4 address with or without mask"
+ placeholder="The ipv4 address..."
+ :validation-rules="ipValidationRules"
+ mb-4
+ />
<div v-if="networkInfo">
<n-table>
@@ -37,7 +41,6 @@
import { computed } from 'vue';
import { Netmask } from 'netmask';
import { withDefaultOnError } from '@/utils/defaults';
-import { useValidation } from '@/composable/validation';
import { isNotThrowing } from '@/utils/boolean';
import { useStorage } from '@vueuse/core';
import { ArrowLeft, ArrowRight } from '@vicons/tabler';
@@ -50,15 +53,12 @@ const getNetworkInfo = (address: string) => new Netmask(address.trim());
const networkInfo = computed(() => withDefaultOnError(() => getNetworkInfo(ip.value), undefined));
-const { attrs: validationAttrs } = useValidation({
- source: ip,
- rules: [
- {
- message: 'We cannot parse this address, check the format',
- validator: (value) => isNotThrowing(() => getNetworkInfo(value.trim())),
- },
- ],
-});
+const ipValidationRules = [
+ {
+ message: 'We cannot parse this address, check the format',
+ validator: (value: string) => isNotThrowing(() => getNetworkInfo(value.trim())),
+ },
+];
const sections: {
label: string;
diff --git a/src/tools/ipv6-ula-generator/ipv6-ula-generator.vue b/src/tools/ipv6-ula-generator/ipv6-ula-generator.vue
index ee74d4c..8f1b130 100644
--- a/src/tools/ipv6-ula-generator/ipv6-ula-generator.vue
+++ b/src/tools/ipv6-ula-generator/ipv6-ula-generator.vue
@@ -1,30 +1,32 @@
<template>
<div>
- <n-space vertical :size="50">
- <n-alert title="Info" type="info">
- This tool uses the first method suggested by IETF using the current timestamp plus the mac address, sha1 hashed,
- and the lower 40 bits to generate your random ULA.
- </n-alert>
+ <n-alert title="Info" type="info">
+ This tool uses the first method suggested by IETF using the current timestamp plus the mac address, sha1 hashed,
+ and the lower 40 bits to generate your random ULA.
+ </n-alert>
- <n-form-item label="MAC address:" v-bind="validationAttrs">
- <n-input
- v-model:value="macAddress"
- size="large"
- placeholder="Type a MAC address"
- clearable
- autocomplete="off"
- autocorrect="off"
- autocapitalize="off"
- spellcheck="false"
- />
- </n-form-item>
- </n-space>
+ <c-input-text
+ v-model:value="macAddress"
+ placeholder="Type a MAC address"
+ clearable
+ label="MAC address:"
+ raw-text
+ my-8
+ :validation="addressValidation"
+ />
- <div v-if="validationAttrs.validationStatus !== 'error'">
- <n-input-group v-for="{ label, value } in calculatedSections" :key="label" style="margin: 5px 0">
- <n-input-group-label style="flex: 0 0 160px"> {{ label }} </n-input-group-label>
- <input-copyable :value="value" readonly />
- </n-input-group>
+ <div v-if="addressValidation.isValid">
+ <input-copyable
+ v-for="{ label, value } in calculatedSections"
+ :key="label"
+ :value="value"
+ :label="label"
+ label-width="160px"
+ label-align="right"
+ label-position="left"
+ readonly
+ mb-2
+ />
</div>
</div>
</template>
@@ -59,7 +61,7 @@ const calculatedSections = computed(() => {
];
});
-const { attrs: validationAttrs } = macAddressValidation(macAddress);
+const addressValidation = macAddressValidation(macAddress);
</script>
<style lang="less" scoped></style>
diff --git a/src/tools/json-diff/json-diff.vue b/src/tools/json-diff/json-diff.vue
index df106c6..0db06cc 100644
--- a/src/tools/json-diff/json-diff.vue
+++ b/src/tools/json-diff/json-diff.vue
@@ -1,5 +1,5 @@
<template>
- <n-form-item label="Your first json" v-bind="leftJsonValidation.attrs">
+ <n-form-item label="Your first json" v-bind="leftJsonValidation.attrs as any">
<n-input
v-model:value="rawLeftJson"
placeholder="Paste your first json here..."
@@ -9,10 +9,10 @@
autocorrect="off"
autocapitalize="off"
spellcheck="false"
- :input-props="{ 'data-test-id': 'leftJson' }"
+ :input-props="{ 'data-test-id': 'leftJson' } as any"
/>
</n-form-item>
- <n-form-item label="Your json to compare" v-bind="rightJsonValidation.attrs">
+ <n-form-item label="Your json to compare" v-bind="rightJsonValidation.attrs as any">
<n-input
v-model:value="rawRightJson"
placeholder="Paste your json to compare here..."
@@ -22,7 +22,7 @@
autocorrect="off"
autocapitalize="off"
spellcheck="false"
- :input-props="{ 'data-test-id': 'rightJson' }"
+ :input-props="{ 'data-test-id': 'rightJson' } as any"
/>
</n-form-item>
diff --git a/src/tools/list-converter/list-converter.e2e.spec.ts b/src/tools/list-converter/list-converter.e2e.spec.ts
index 1634a11..a5ac8c6 100644
--- a/src/tools/list-converter/list-converter.e2e.spec.ts
+++ b/src/tools/list-converter/list-converter.e2e.spec.ts
@@ -30,8 +30,8 @@ test.describe('Tool - List converter', () => {
3
5`);
await page.getByTestId('removeDuplicates').check();
- await page.getByTestId('itemPrefix').locator('input').fill("'");
- await page.getByTestId('itemSuffix').locator('input').fill("'");
+ await page.getByTestId('itemPrefix').fill("'");
+ await page.getByTestId('itemSuffix').fill("'");
const result = await page.getByTestId('area-content').innerText();
expect(result.trim()).toEqual("'1', '2', '4', '3', '5'");
diff --git a/src/tools/list-converter/list-converter.vue b/src/tools/list-converter/list-converter.vue
index ae0b50e..f8da070 100644
--- a/src/tools/list-converter/list-converter.vue
+++ b/src/tools/list-converter/list-converter.vue
@@ -36,37 +36,39 @@
/>
</n-form-item>
- <n-form-item label="Separator" label-placement="left" label-width="120" :show-feedback="false" mb-2>
- <n-input v-model:value="conversionConfig.separator" placeholder="," />
- </n-form-item>
+ <c-input-text
+ v-model:value="conversionConfig.separator"
+ label="Separator"
+ label-position="left"
+ label-width="120px"
+ label-align="right"
+ mb-2
+ placeholder=","
+ />
<n-form-item label="Wrap item" label-placement="left" label-width="120" :show-feedback="false" mb-2>
- <n-input-group>
- <n-input
- v-model:value="conversionConfig.itemPrefix"
- placeholder="Item prefix"
- data-test-id="itemPrefix"
- />
- <n-input
- v-model:value="conversionConfig.itemSuffix"
- placeholder="Item suffix"
- data-test-id="itemSuffix"
- />
- </n-input-group>
+ <c-input-text
+ v-model:value="conversionConfig.itemPrefix"
+ placeholder="Item prefix"
+ test-id="itemPrefix"
+ />
+ <c-input-text
+ v-model:value="conversionConfig.itemSuffix"
+ placeholder="Item suffix"
+ test-id="itemSuffix"
+ />
</n-form-item>
<n-form-item label="Wrap list" label-placement="left" label-width="120" :show-feedback="false" mb-2>
- <n-input-group>
- <n-input
- v-model:value="conversionConfig.listPrefix"
- placeholder="List prefix"
- data-test-id="listPrefix"
- />
- <n-input
- v-model:value="conversionConfig.listSuffix"
- placeholder="List suffix"
- data-test-id="listSuffix"
- />
- </n-input-group>
+ <c-input-text
+ v-model:value="conversionConfig.listPrefix"
+ placeholder="List prefix"
+ test-id="listPrefix"
+ />
+ <c-input-text
+ v-model:value="conversionConfig.listSuffix"
+ placeholder="List suffix"
+ test-id="listSuffix"
+ />
</n-form-item>
</div>
</div>
diff --git a/src/tools/mac-address-lookup/mac-address-lookup.vue b/src/tools/mac-address-lookup/mac-address-lookup.vue
index c157dda..4ff4c2e 100644
--- a/src/tools/mac-address-lookup/mac-address-lookup.vue
+++ b/src/tools/mac-address-lookup/mac-address-lookup.vue
@@ -1,6 +1,6 @@
<template>
<div>
- <n-form-item label="MAC address:" v-bind="validationAttrs">
+ <n-form-item label="MAC address:" v-bind="validationAttrs as any">
<n-input
v-model:value="macAddress"
size="large"
diff --git a/src/tools/meta-tag-generator/meta-tag-generator.vue b/src/tools/meta-tag-generator/meta-tag-generator.vue
index ee5d8e7..73fad8b 100644
--- a/src/tools/meta-tag-generator/meta-tag-generator.vue
+++ b/src/tools/meta-tag-generator/meta-tag-generator.vue
@@ -5,7 +5,7 @@
<n-input-group v-for="{ key, type, label, placeholder, ...element } of elements" :key="key">
<n-input-group-label style="flex: 0 0 110px">{{ label }}</n-input-group-label>
- <n-input v-if="type === 'input'" v-model:value="metadata[key]" :placeholder="placeholder" />
+ <c-input-text v-if="type === 'input'" v-model:value="metadata[key]" :placeholder="placeholder" clearable />
<n-dynamic-input
v-else-if="type === 'input-multiple'"
v-model:value="metadata[key]"
diff --git a/src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue b/src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue
index fd9f173..73fbd25 100644
--- a/src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue
+++ b/src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue
@@ -1,19 +1,23 @@
<template>
<div style="max-width: 350px">
- <n-form-item label="Secret" v-bind="secretValidationAttrs">
- <n-input v-model:value="secret" placeholder="Paste your TOTP secret...">
- <template #suffix>
- <n-tooltip trigger="hover">
- <template #trigger>
- <c-button circle variant="text" @click="refreshSecret">
- <n-icon :component="Refresh" />
- </c-button>
- </template>
- Generate secret token
- </n-tooltip>
- </template>
- </n-input>
- </n-form-item>
+ <c-input-text
+ v-model:value="secret"
+ label="Secret"
+ placeholder="Paste your TOTP secret..."
+ mb-5
+ :validation-rules="secretValidationRules"
+ >
+ <template #suffix>
+ <n-tooltip trigger="hover">
+ <template #trigger>
+ <c-button circle variant="text" size="small" @click="refreshSecret">
+ <icon-mdi-refresh />
+ </c-button>
+ </template>
+ Generate secret token
+ </n-tooltip>
+ </template>
+ </c-input-text>
<div>
<token-display :tokens="tokens" style="margin-top: 2px" />
@@ -27,49 +31,52 @@
</n-space>
</div>
<div style="max-width: 350px">
- <n-form-item label="Secret in hexadecimal">
- <input-copyable :value="base32toHex(secret)" readonly placeholder="Secret in hex will be displayed here" />
- </n-form-item>
-
- <n-form-item label="Epoch">
- <input-copyable
- :value="Math.floor(now / 1000).toString()"
- readonly
- placeholder="Epoch in sec will be displayed here"
- />
- </n-form-item>
- <n-form-item label="Iteration" :show-feedback="false">
- <n-input-group>
- <n-input-group-label style="width: 110px">Count:</n-input-group-label>
- <input-copyable
- :value="String(getCounterFromTime({ now, timeStep: 30 }))"
- readonly
- placeholder="Iteration count will be displayed here"
- />
- </n-input-group>
- </n-form-item>
-
- <n-form-item label="Iteration" :show-label="false" style="margin-top: 5px">
- <n-input-group>
- <n-input-group-label style="width: 110px">Padded hex:</n-input-group-label>
- <input-copyable
- :value="getCounterFromTime({ now, timeStep: 30 }).toString(16).padStart(16, '0')"
- readonly
- placeholder="Iteration count in hex will be displayed here"
- />
- </n-input-group>
- </n-form-item>
+ <input-copyable
+ label="Secret in hexadecimal"
+ :value="base32toHex(secret)"
+ readonly
+ placeholder="Secret in hex will be displayed here"
+ mb-5
+ />
+
+ <input-copyable
+ label="Epoch"
+ :value="Math.floor(now / 1000).toString()"
+ readonly
+ mb-5
+ placeholder="Epoch in sec will be displayed here"
+ />
+
+ <p>Iteration</p>
+
+ <input-copyable
+ :value="String(getCounterFromTime({ now, timeStep: 30 }))"
+ readonly
+ label="Count:"
+ label-position="left"
+ label-width="90px"
+ label-align="right"
+ placeholder="Iteration count will be displayed here"
+ />
+
+ <input-copyable
+ :value="getCounterFromTime({ now, timeStep: 30 }).toString(16).padStart(16, '0')"
+ readonly
+ placeholder="Iteration count in hex will be displayed here"
+ label-position="left"
+ label-width="90px"
+ label-align="right"
+ label="Padded hex:"
+ />
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
-import { Refresh } from '@vicons/tabler';
import { useTimestamp } from '@vueuse/core';
import { useThemeVars } from 'naive-ui';
import { useStyleStore } from '@/stores/style.store';
import InputCopyable from '@/components/InputCopyable.vue';
-import { useValidation } from '@/composable/validation';
import { computedRefreshable } from '@/composable/computedRefreshable';
import { generateTOTP, buildKeyUri, generateSecret, base32toHex, getCounterFromTime } from './otp.service';
import { useQRCode } from '../qr-code-generator/useQRCode';
@@ -106,19 +113,16 @@ const { qrcode } = useQRCode({
options: { width: 210 },
});
-const { attrs: secretValidationAttrs } = useValidation({
- source: secret,
- rules: [
- {
- message: 'Secret should be a base32 string',
- validator: (value) => value.toUpperCase().match(/^[A-Z234567]+$/),
- },
- {
- message: 'Please set a secret',
- validator: (value) => value !== '',
- },
- ],
-});
+const secretValidationRules = [
+ {
+ message: 'Secret should be a base32 string',
+ validator: (value: string) => value.toUpperCase().match(/^[A-Z234567]+$/),
+ },
+ {
+ message: 'Please set a secret',
+ validator: (value: string) => value !== '',
+ },
+];
</script>
<style lang="less" scoped>
diff --git a/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue b/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue
index dad4ddc..1f31085 100644
--- a/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue
+++ b/src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue
@@ -3,9 +3,14 @@
<n-form-item label="Default country code:">
<n-select v-model:value="defaultCountryCode" :options="countriesOptions" />
</n-form-item>
- <n-form-item label="Phone number:" v-bind="validation.attrs">
- <n-input v-model:value="rawPhone" placeholder="Enter a phone number" />
- </n-form-item>
+
+ <c-input-text
+ v-model:value="rawPhone"
+ placeholder="Enter a phone number"
+ label="Phone number:"
+ :validation="validation"
+ mb-5
+ />
<n-table v-if="parsedDetails">
<tbody>
diff --git a/src/tools/roman-numeral-converter/roman-numeral-converter.vue b/src/tools/roman-numeral-converter/roman-numeral-converter.vue
index d21728e..cc5ce93 100644
--- a/src/tools/roman-numeral-converter/roman-numeral-converter.vue
+++ b/src/tools/roman-numeral-converter/roman-numeral-converter.vue
@@ -2,7 +2,7 @@
<div>
<c-card title="Arabic to roman">
<n-space align="center" justify="space-between">
- <n-form-item v-bind="validationNumeral">
+ <n-form-item v-bind="validationNumeral as any">
<n-input-number v-model:value="inputNumeral" :min="1" style="width: 200px" :show-button="false" />
</n-form-item>
<div class="result">
@@ -15,13 +15,12 @@
</c-card>
<c-card title="Roman to arabic" mt-5>
<n-space align="center" justify="space-between">
- <n-form-item v-bind="validationRoman">
- <n-input v-model:value="inputRoman" style="width: 200px" />
- </n-form-item>
+ <c-input-text v-model:value="inputRoman" style="width: 200px" :validation="validationRoman" />
+
<div class="result">
{{ outputNumeral }}
</div>
- <c-button :disabled="validationRoman.validationStatus === 'error'" @click="copyArabic"> Copy </c-button>
+ <c-button :disabled="!validationRoman.isValid" @click="copyArabic"> Copy </c-button>
</n-space>
</c-card>
</div>
@@ -55,7 +54,7 @@ const { attrs: validationNumeral } = useValidation({
const inputRoman = ref('XLII');
const outputNumeral = computed(() => romanToArabic(inputRoman.value));
-const { attrs: validationRoman } = useValidation({
+const validationRoman = useValidation({
source: inputRoman,
rules: [
{
diff --git a/src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue b/src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue
index 0c5034c..a2202c9 100644
--- a/src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue
+++ b/src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue
@@ -1,7 +1,7 @@
<template>
<div style="flex: 0 0 100%">
<n-space item-style="flex: 1 1 0" style="margin: 0 auto; max-width: 600px" justify="center">
- <n-form-item label="Bits :" v-bind="bitsValidationAttrs" label-placement="left" label-width="100">
+ <n-form-item label="Bits :" v-bind="bitsValidationAttrs as any" label-placement="left" label-width="100">
<n-input-number v-model:value="bits" min="256" max="16384" step="8" />
</n-form-item>
diff --git a/src/tools/svg-placeholder-generator/svg-placeholder-generator.vue b/src/tools/svg-placeholder-generator/svg-placeholder-generator.vue
index 404e552..dacc700 100644
--- a/src/tools/svg-placeholder-generator/svg-placeholder-generator.vue
+++ b/src/tools/svg-placeholder-generator/svg-placeholder-generator.vue
@@ -21,9 +21,15 @@
<n-form-item label="Font size">
<n-input-number v-model:value="fontSize" placeholder="Font size..." min="1" />
</n-form-item>
- <n-form-item label="Custom text">
- <n-input v-model:value="customText" :placeholder="`Default is ${width}x${height}`" />
- </n-form-item>
+
+ <c-input-text
+ v-model:value="customText"
+ label="Custom text"
+ :placeholder="`Default is ${width}x${height}`"
+ label-position="left"
+ label-width="100px"
+ label-align="right"
+ />
</n-space>
<n-form-item label="Use exact size" label-placement="left">
<n-switch v-model:value="useExactSize" />
diff --git a/src/tools/text-to-nato-alphabet/text-to-nato-alphabet.vue b/src/tools/text-to-nato-alphabet/text-to-nato-alphabet.vue
index aa551f5..4b12adb 100644
--- a/src/tools/text-to-nato-alphabet/text-to-nato-alphabet.vue
+++ b/src/tools/text-to-nato-alphabet/text-to-nato-alphabet.vue
@@ -1,8 +1,12 @@
<template>
<div>
- <n-form-item label="Your text to convert to NATO phonetic alphabet">
- <n-input v-model:value="input" placeholder="Put your text here..." clearable />
- </n-form-item>
+ <c-input-text
+ v-model:value="input"
+ label="Your text to convert to NATO phonetic alphabet"
+ placeholder="Put your text here..."
+ clearable
+ mb-5
+ />
<n-space v-if="natoText" vertical>
<n-text>Your text in NATO phonetic alphabet</n-text>
diff --git a/src/tools/url-parser/url-parser.vue b/src/tools/url-parser/url-parser.vue
index ac80f61..04ab939 100644
--- a/src/tools/url-parser/url-parser.vue
+++ b/src/tools/url-parser/url-parser.vue
@@ -1,51 +1,59 @@
<template>
<c-card>
- <n-form-item label="Your url to parse:" :feedback="validation.message" :validation-status="validation.status">
- <n-input v-model:value="urlToParse" placeholder="Your url to parse..." />
- </n-form-item>
+ <c-input-text
+ v-model:value="urlToParse"
+ label="Your url to parse:"
+ placeholder="Your url to parse..."
+ raw-text
+ :validation-rules="urlValidationRules"
+ />
- <n-divider style="margin-top: 0" />
+ <n-divider />
- <n-form>
- <n-input-group v-for="{ title, key } in properties" :key="key">
- <n-input-group-label style="flex: 0 0 120px"> {{ title }}: </n-input-group-label>
- <input-copyable :value="(urlParsed?.[key] as string) ?? ''" readonly placeholder=" " />
- </n-input-group>
+ <input-copyable
+ v-for="{ title, key } in properties"
+ :key="key"
+ :label="title"
+ :value="(urlParsed?.[key] as string) ?? ''"
+ readonly
+ label-position="left"
+ label-width="110px"
+ mb-2
+ placeholder=" "
+ />
- <n-input-group
- v-for="[k, v] in Object.entries(Object.fromEntries(urlParsed?.searchParams.entries() ?? []))"
- :key="k"
- >
- <n-input-group-label style="flex: 0 0 120px">
- <n-icon :component="SubdirectoryArrowRightRound" />
- </n-input-group-label>
- <input-copyable :value="k" readonly />
- <input-copyable :value="v" readonly />
- </n-input-group>
- </n-form>
+ <div
+ v-for="[k, v] in Object.entries(Object.fromEntries(urlParsed?.searchParams.entries() ?? []))"
+ :key="k"
+ mb-2
+ w-full
+ flex
+ >
+ <div style="flex: 1 0 110px">
+ <icon-mdi-arrow-right-bottom />
+ </div>
+
+ <input-copyable :value="k" readonly />
+ <input-copyable :value="v" readonly />
+ </div>
</c-card>
</template>
<script setup lang="ts">
-import { useValidation } from '@/composable/validation';
import { isNotThrowing } from '@/utils/boolean';
import { withDefaultOnError } from '@/utils/defaults';
-import { SubdirectoryArrowRightRound } from '@vicons/material';
import { computed, ref } from 'vue';
import InputCopyable from '../../components/InputCopyable.vue';
const urlToParse = ref('https://me:pwd@it-tools.tech:3000/url-parser?key1=value&key2=value2#the-hash');
const urlParsed = computed(() => withDefaultOnError(() => new URL(urlToParse.value), undefined));
-const validation = useValidation({
- source: urlToParse,
- rules: [
- {
- validator: (value) => isNotThrowing(() => new URL(value)),
- message: 'Invalid url',
- },
- ],
-});
+const urlValidationRules = [
+ {
+ validator: (value: string) => isNotThrowing(() => new URL(value)),
+ message: 'Invalid url',
+ },
+];
const properties: { title: string; key: keyof URL }[] = [
{ title: 'Protocol', key: 'protocol' },
diff --git a/src/ui/c-button/c-button.demo.vue b/src/ui/c-button/c-button.demo.vue
index 4e9aa1a..ce339f5 100644
--- a/src/ui/c-button/c-button.demo.vue
+++ b/src/ui/c-button/c-button.demo.vue
@@ -25,6 +25,18 @@
>
A
</c-button>
+
+ <c-button
+ v-for="buttonType of buttonTypes"
+ :key="buttonType"
+ :variant="buttonVariant"
+ :type="buttonType"
+ :size="buttonSize"
+ circle
+ mx-1
+ >
+ <icon-mdi-content-copy />
+ </c-button>
</div>
</div>
</template>
@@ -33,7 +45,7 @@
import _ from 'lodash';
const buttonVariants = ['basic', 'text'] as const;
-const buttonTypes = ['default', 'primary'] as const;
+const buttonTypes = ['default', 'primary', 'warning'] as const;
const buttonSizes = ['small', 'medium', 'large'] as const;
</script>
diff --git a/src/ui/c-button/c-button.theme.ts b/src/ui/c-button/c-button.theme.ts
index e96ac56..5b4c26f 100644
--- a/src/ui/c-button/c-button.theme.ts
+++ b/src/ui/c-button/c-button.theme.ts
@@ -56,10 +56,10 @@ const createTheme = ({ style }: { style: 'light' | 'dark' }) => {
pressedBackground: darken(theme.primary.colorFaded, 30),
}),
warning: createState({
- textColor: theme.text.baseColor,
- backgroundColor: theme.warning.color,
- hoverBackground: theme.warning.colorHover,
- pressedBackground: theme.warning.colorPressed,
+ textColor: theme.warning.color,
+ backgroundColor: theme.warning.colorFaded,
+ hoverBackground: lighten(theme.warning.colorFaded, 30),
+ pressedBackground: darken(theme.warning.colorFaded, 30),
}),
},
text: {
@@ -76,10 +76,10 @@ const createTheme = ({ style }: { style: 'light' | 'dark' }) => {
pressedBackground: darken(theme.primary.colorFaded, 30),
}),
warning: createState({
- textColor: theme.text.baseColor,
- backgroundColor: theme.warning.color,
- hoverBackground: theme.warning.colorHover,
- pressedBackground: theme.warning.colorPressed,
+ textColor: darken(theme.warning.color, 20),
+ backgroundColor: 'transparent',
+ hoverBackground: theme.warning.colorFaded,
+ pressedBackground: darken(theme.warning.colorFaded, 30),
}),
},
};
diff --git a/src/ui/c-button/c-button.vue b/src/ui/c-button/c-button.vue
index c17b078..121a1e9 100644
--- a/src/ui/c-button/c-button.vue
+++ b/src/ui/c-button/c-button.vue
@@ -18,7 +18,7 @@ import { useAppTheme } from '../theme/themes';
const props = withDefaults(
defineProps<{
- type?: 'default' | 'primary';
+ type?: 'default' | 'primary' | 'warning';
variant?: 'basic' | 'text';
disabled?: boolean;
round?: boolean;
diff --git a/src/ui/c-input-text/c-input-text.demo.vue b/src/ui/c-input-text/c-input-text.demo.vue
index 2363219..5a5fa99 100644
--- a/src/ui/c-input-text/c-input-text.demo.vue
+++ b/src/ui/c-input-text/c-input-text.demo.vue
@@ -2,6 +2,9 @@
<h2>Default</h2>
<c-input-text value="qsd" />
+ <c-input-text
+ value="Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum, est modi iusto repellendus fuga accusantium atque at magnam aliquam eum explicabo vero quia, nobis quasi quis! Earum amet quam a?"
+ />
<h2>With placeholder</h2>
@@ -24,16 +27,50 @@
<h2>Validation</h2>
- <c-input-text
- v-model:value="value"
- :validation-rules="[{ message: 'Length must be > 10', validator: (value) => value.length > 10 }]"
- />
+ <c-input-text v-model:value="value" :validation-rules="validationRules" mb-2 />
+ <c-input-text v-model:value="value" :validation-rules="validationRules" mb-2 label-position="left" label="Yo " />
+ <c-input-text v-model:value="value" :validation="validation" />
+ <c-input-text v-model:value="value" :validation="validation" multiline rows="3" />
<h2>Clearable</h2>
<c-input-text v-model:value="value" clearable />
+
+ <h2>Type password</h2>
+
+ <c-input-text value="value" type="password" />
+
+ <h2>Multiline</h2>
+
+ <c-input-text value="value" multiline label="Label" mb-2 rows="1" />
+ <c-input-text value="value" multiline label="Label" mb-2 />
+ <c-input-text
+ value="Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum, est modi iusto repellendus fuga accusantium atque at magnam aliquam eum explicabo vero quia, nobis quasi quis! Earum amet quam a?"
+ multiline
+ mb-2
+ />
+
+ <c-input-text v-model:value="valueLong" multiline autosize mb-2 rows="5" />
+
+ <c-input-text
+ value="Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum, est modi iusto repellendus fuga accusantium atque at magnam aliquam eum explicabo vero quia, nobis quasi quis! Earum amet quam a?"
+ multiline
+ clearable
+ />
</template>
<script lang="ts" setup>
+import { useValidation } from '@/composable/validation';
+
const value = ref('value');
+const valueLong = ref(
+ 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum, est modi iusto repellendus fuga accusantium atque at magnam aliquam eum explicabo vero quia, nobis quasi quis! Earum amet quam a?',
+);
+
+const validationRules = [{ message: 'Length must be > 10', validator: (value: string) => value.length > 10 }];
+
+const validation = useValidation({
+ source: value,
+ rules: validationRules,
+});
</script>
diff --git a/src/ui/c-input-text/c-input-text.test.ts b/src/ui/c-input-text/c-input-text.test.ts
index 56b5855..69f4046 100644
--- a/src/ui/c-input-text/c-input-text.test.ts
+++ b/src/ui/c-input-text/c-input-text.test.ts
@@ -1,7 +1,8 @@
import { describe, expect, it, beforeEach } from 'vitest';
-import { shallowMount } from '@vue/test-utils';
+import { shallowMount, mount } from '@vue/test-utils';
import { setActivePinia, createPinia } from 'pinia';
import _ from 'lodash';
+import { useValidation } from '@/composable/validation';
import CInputText from './c-input-text.vue';
describe('CInputText', () => {
@@ -71,10 +72,28 @@ describe('CInputText', () => {
it('renders a feedback message for invalid rules', async () => {
const wrapper = shallowMount(CInputText, {
- props: { rules: [{ validator: () => false, message: 'Message' }] },
+ props: { validationRules: [{ validator: () => false, message: 'Message' }] },
});
- expect(wrapper.get('.feedback').text()).to.equal('Message');
+ const feedback = wrapper.find('.feedback');
+ expect(feedback.exists()).to.equal(true);
+ expect(feedback.text()).to.equal('Message');
+ });
+
+ it('if the value become valid according to rules, the feedback disappear', async () => {
+ const wrapper = shallowMount(CInputText, {
+ props: {
+ validationRules: [{ validator: (value: string) => value === 'Hello', message: 'Value should be Hello' }],
+ },
+ });
+
+ const feedback = wrapper.find('.feedback');
+ expect(feedback.exists()).to.equal(true);
+ expect(feedback.text()).to.equal('Value should be Hello');
+
+ await wrapper.setProps({ value: 'Hello' });
+
+ expect(wrapper.find('.feedback').exists()).to.equal(false);
});
it('feedback does not render for valid rules', async () => {
@@ -84,4 +103,58 @@ describe('CInputText', () => {
expect(wrapper.find('.feedback').exists()).to.equal(false);
});
+
+ it('renders a feedback message for invalid custom validation wrapper', async () => {
+ const wrapper = shallowMount(CInputText, {
+ props: {
+ validation: useValidation({ source: ref(), rules: [{ validator: () => false, message: 'Message' }] }),
+ },
+ });
+
+ const feedback = wrapper.find('.feedback');
+ expect(feedback.exists()).to.equal(true);
+ expect(feedback.text()).to.equal('Message');
+ });
+
+ it('feedback does not render for valid custom validation wrapper', async () => {
+ const wrapper = shallowMount(CInputText, {
+ props: {
+ validation: useValidation({ source: ref(), rules: [{ validator: () => true, message: 'Message' }] }),
+ },
+ });
+ expect(wrapper.find('.feedback').exists()).to.equal(false);
+ });
+
+ it('if the value become valid according to the custom validation wrapper, the feedback disappear', async () => {
+ const source = ref('');
+
+ const wrapper = shallowMount(CInputText, {
+ props: {
+ validation: useValidation({
+ source,
+ rules: [{ validator: (value: string) => value === 'Hello', message: 'Value should be Hello' }],
+ }),
+ },
+ });
+
+ const feedback = wrapper.find('.feedback');
+ expect(feedback.exists()).to.equal(true);
+ expect(feedback.text()).to.equal('Value should be Hello');
+
+ source.value = 'Hello';
+
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.find('.feedback').exists()).to.equal(false);
+ });
+
+ it('[prop:testId] renders a test id on the input', async () => {
+ const wrapper = mount(CInputText, {
+ props: {
+ testId: 'TEST',
+ },
+ });
+
+ expect(wrapper.get('input').attributes('data-test-id')).to.equal('TEST');
+ });
});
diff --git a/src/ui/c-input-text/c-input-text.vue b/src/ui/c-input-text/c-input-text.vue
index a40d26a..51c2805 100644
--- a/src/ui/c-input-text/c-input-text.vue
+++ b/src/ui/c-input-text/c-input-text.vue
@@ -1,32 +1,60 @@
<template>
- <div class="c-input-text" :class="{ disabled, error: !validation.isValid, 'label-left': labelPosition === 'left' }">
+ <div
+ class="c-input-text"
+ :class="{ disabled, error: !validation.isValid, 'label-left': labelPosition === 'left', multiline }"
+ >
<label v-if="label" :for="id" class="label"> {{ label }} </label>
- <div class="input-wrapper">
- <slot name="prefix" />
-
- <input
- :id="id"
- v-model="value"
- type="text"
- class="input"
- :placeholder="placeholder"
- :readonly="readonly"
- :disabled="disabled"
- :data-test-id="testId"
- :autocapitalize="autocapitalize ?? (rawText ? 'off' : undefined)"
- :autocomplete="autocomplete ?? (rawText ? 'off' : undefined)"
- :autocorrect="autocorrect ?? (rawText ? 'off' : undefined)"
- :spellcheck="spellcheck ?? (rawText ? false : undefined)"
- />
-
- <c-button v-if="clearable && value" variant="text" circle size="small" @click="value = ''">
- <icon-mdi-close />
- </c-button>
- <slot name="suffix" />
- </div>
+ <div class="feedback-wrapper">
+ <div ref="inputWrapperRef" class="input-wrapper">
+ <slot name="prefix" />
+
+ <textarea
+ v-if="multiline"
+ :id="id"
+ ref="textareaRef"
+ v-model="value"
+ class="input"
+ :placeholder="placeholder"
+ :readonly="readonly"
+ :disabled="disabled"
+ :data-test-id="testId"
+ :autocapitalize="autocapitalize ?? (rawText ? 'off' : undefined)"
+ :autocomplete="autocomplete ?? (rawText ? 'off' : undefined)"
+ :autocorrect="autocorrect ?? (rawText ? 'off' : undefined)"
+ :spellcheck="spellcheck ?? (rawText ? false : undefined)"
+ :rows="rows"
+ />
+
+ <input
+ v-else
+ :id="id"
+ v-model="value"
+ :type="htmlInputType"
+ class="input"
+ size="1"
+ :placeholder="placeholder"
+ :readonly="readonly"
+ :disabled="disabled"
+ :data-test-id="testId"
+ :autocapitalize="autocapitalize ?? (rawText ? 'off' : undefined)"
+ :autocomplete="autocomplete ?? (rawText ? 'off' : undefined)"
+ :autocorrect="autocorrect ?? (rawText ? 'off' : undefined)"
+ :spellcheck="spellcheck ?? (rawText ? false : undefined)"
+ />
+
+ <c-button v-if="clearable && value" variant="text" circle size="small" @click="value = ''">
+ <icon-mdi-close />
+ </c-button>
- <span v-if="!validation.isValid" class="feedback"> {{ validation.message }} </span>
+ <c-button v-if="type === 'password'" variant="text" circle size="small" @click="showPassword = !showPassword">
+ <icon-mdi-eye v-if="!showPassword" />
+ <icon-mdi-eye-off v-if="showPassword" />
+ </c-button>
+ <slot name="suffix" />
+ </div>
+ <span v-if="!validation.isValid" class="feedback"> {{ validation.message }} </span>
+ </div>
</div>
</template>
@@ -45,6 +73,7 @@ const props = withDefaults(
readonly?: boolean;
disabled?: boolean;
validationRules?: UseValidationRule<string>[];
+ validation?: ReturnType<typeof useValidation>;
labelPosition?: 'top' | 'left';
labelWidth?: string;
labelAlign?: 'left' | 'right';
@@ -55,6 +84,10 @@ const props = withDefaults(
autocorrect?: 'on' | 'off' | string;
spellcheck?: 'true' | 'false' | boolean;
rawText?: boolean;
+ type?: 'text' | 'password';
+ multiline?: boolean;
+ rows?: number | string;
+ autosize?: boolean;
}>(),
{
value: '',
@@ -64,6 +97,7 @@ const props = withDefaults(
readonly: false,
disabled: false,
validationRules: () => [],
+ validation: undefined,
labelPosition: 'top',
labelWidth: 'auto',
labelAlign: 'left',
@@ -74,20 +108,58 @@ const props = withDefaults(
autocorrect: undefined,
spellcheck: undefined,
rawText: false,
+ type: 'text',
+ multiline: false,
+ rows: 3,
+ autosize: false,
},
);
const emit = defineEmits(['update:value']);
const value = useVModel(props, 'value', emit);
+const showPassword = ref(false);
-const { id, placeholder, label, validationRules, labelPosition, labelWidth, labelAlign } = toRefs(props);
+const { id, placeholder, label, validationRules, labelPosition, labelWidth, labelAlign, autosize } = toRefs(props);
-const validation = useValidation({
- rules: validationRules,
- source: value,
-});
+const validation =
+ props.validation ??
+ useValidation({
+ rules: validationRules,
+ source: value,
+ });
const theme = useTheme();
const appTheme = useAppTheme();
+
+const textareaRef = ref<HTMLTextAreaElement>();
+const inputWrapperRef = ref<HTMLElement>();
+
+watch(
+ value,
+ () => {
+ if (props.multiline && autosize.value) {
+ resizeTextarea();
+ }
+ },
+ { immediate: true },
+);
+
+function resizeTextarea() {
+ if (!textareaRef.value || !inputWrapperRef.value) {
+ return;
+ }
+
+ const { scrollHeight } = textareaRef.value;
+
+ inputWrapperRef.value.style.height = `${scrollHeight + 2}px`;
+}
+
+const htmlInputType = computed(() => {
+ if (props.type === 'password' && !showPassword.value) {
+ return 'password';
+ }
+
+ return 'text';
+});
</script>
<style lang="less" scoped>
@@ -114,29 +186,55 @@ const appTheme = useAppTheme();
}
}
- & > .feedback {
+ & .feedback {
color: v-bind('appTheme.error.color');
}
}
& > .label {
+ flex-shrink: 0;
margin-bottom: 5px;
flex: 0 0 v-bind('labelWidth');
text-align: v-bind('labelAlign');
- padding-right: 10px;
+ padding-right: 12px;
}
- .input-wrapper {
+ .feedback-wrapper {
flex: 1 1 0;
min-width: 0;
-
+ }
+ .input-wrapper {
display: flex;
flex-direction: row;
align-items: center;
background-color: v-bind('theme.backgroundColor');
+ color: transparent;
border: 1px solid v-bind('theme.borderColor');
border-radius: 4px;
padding: 0 4px 0 12px;
+ transition: border-color 0.2s ease-in-out;
+
+ .multiline& {
+ resize: vertical;
+ overflow: hidden;
+
+ & > textarea {
+ height: 100%;
+ resize: none;
+ word-break: break-word;
+ white-space: pre-wrap;
+ overflow-wrap: break-word;
+ border: none;
+ outline: none;
+ font-family: inherit;
+ font-size: inherit;
+ color: v-bind('appTheme.text.baseColor');
+
+ &::placeholder {
+ color: v-bind('appTheme.text.mutedColor');
+ }
+ }
+ }
& > .input {
flex: 1 1 0;
@@ -144,7 +242,6 @@ const appTheme = useAppTheme();
padding: 8px 0;
outline: none;
- transition: border-color 0.2s ease-in-out;
background-color: transparent;
background-image: none;
-webkit-box-shadow: none;
@@ -159,12 +256,13 @@ const appTheme = useAppTheme();
}
}
- &:hover,
- &:focus {
+ &:hover {
border-color: v-bind('appTheme.primary.color');
}
- &:focus {
+ &:focus-within {
+ border-color: v-bind('appTheme.primary.color');
+
background-color: v-bind('theme.focus.backgroundColor');
}
}
@@ -173,11 +271,11 @@ const appTheme = useAppTheme();
border-color: v-bind('appTheme.error.color');
&:hover,
- &:focus {
+ &:focus-within {
border-color: v-bind('appTheme.error.color');
}
- &:focus {
+ &:focus-within {
background-color: v-bind('appTheme.error.color + 22');
}
}
@@ -186,7 +284,7 @@ const appTheme = useAppTheme();
opacity: 0.5;
&:hover,
- &:focus {
+ &:focus-within {
border-color: v-bind('theme.borderColor');
}
diff --git a/src/ui/theme/themes.ts b/src/ui/theme/themes.ts
index 18b2c8d..81d7ddf 100644
--- a/src/ui/theme/themes.ts
+++ b/src/ui/theme/themes.ts
@@ -21,6 +21,7 @@ export const { themes: appThemes, useTheme: useAppTheme } = defineThemes({
color: '#f59e0b',
colorHover: '#f59e0b',
colorPressed: '#f59e0b',
+ colorFaded: '#f59e0b2f',
},
success: {
color: '#18a058',
@@ -55,6 +56,7 @@ export const { themes: appThemes, useTheme: useAppTheme } = defineThemes({
color: '#f59e0b',
colorHover: '#f59e0b',
colorPressed: '#f59e0b',
+ colorFaded: '#f59e0b2f',
},
success: {
color: '#18a058',