Skip to content

Commit

Permalink
ui-lib(new-component): added text input component in the c-lib
Browse files Browse the repository at this point in the history
  • Loading branch information
CorentinTh committed May 14, 2023
1 parent 401f13f commit aad8d84
Show file tree
Hide file tree
Showing 14 changed files with 428 additions and 21 deletions.
5 changes: 5 additions & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ declare module '@vue/runtime-core' {
'CCard.demo': typeof import('./src/ui/c-card/c-card.demo.vue')['default']
ChmodCalculator: typeof import('./src/tools/chmod-calculator/chmod-calculator.vue')['default']
Chronometer: typeof import('./src/tools/chronometer/chronometer.vue')['default']
CInputText: typeof import('./src/ui/c-input-text/c-input-text.vue')['default']
'CInputText.demo': typeof import('./src/ui/c-input-text/c-input-text.demo.vue')['default']
'CInputText.theme': typeof import('./src/ui/c-input-text/c-input-text.theme.vue')['default']
CLink: typeof import('./src/ui/c-link/c-link.vue')['default']
'CLink.demo': typeof import('./src/ui/c-link/c-link.demo.vue')['default']
CollapsibleToolMenu: typeof import('./src/components/CollapsibleToolMenu.vue')['default']
ColorConverter: typeof import('./src/tools/color-converter/color-converter.vue')['default']
ColoredCard: typeof import('./src/components/ColoredCard.vue')['default']
copy: typeof import('./src/ui/c-input-text/c-input-text copy.vue')['default']
CopyableIpLike: typeof import('./src/tools/ipv4-subnet-calculator/copyable-ip-like.vue')['default']
CrontabGenerator: typeof import('./src/tools/crontab-generator/crontab-generator.vue')['default']
DateTimeConverter: typeof import('./src/tools/date-time-converter/date-time-converter.vue')['default']
Expand All @@ -52,6 +56,7 @@ 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']
IconMdiClose: typeof import('~icons/mdi/close')['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']
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"yaml": "^2.2.1"
},
"devDependencies": {
"@iconify-json/mdi": "^1.1.50",
"@playwright/test": "^1.32.3",
"@rushstack/eslint-patch": "^1.2.0",
"@types/bcryptjs": "^2.4.2",
Expand All @@ -98,8 +99,10 @@
"@unocss/eslint-config": "^0.50.8",
"@vitejs/plugin-vue": "^2.3.4",
"@vitejs/plugin-vue-jsx": "^1.3.10",
"@vue/compiler-sfc": "^3.2.47",
"@vue/eslint-config-prettier": "^7.1.0",
"@vue/eslint-config-typescript": "^10.0.0",
"@vue/runtime-core": "^3.2.47",
"@vue/test-utils": "^2.3.2",
"@vue/tsconfig": "^0.1.3",
"c8": "^7.13.0",
Expand All @@ -116,6 +119,7 @@
"typescript": "~4.5.5",
"unocss": "^0.50.8",
"unplugin-auto-import": "^0.15.2",
"unplugin-icons": "^0.16.1",
"unplugin-vue-components": "^0.24.1",
"vite": "^2.9.15",
"vite-plugin-md": "^0.12.4",
Expand Down
47 changes: 47 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions src/composable/validation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { get, type MaybeRef } from '@vueuse/core';
import _ from 'lodash';
import { reactive, watch, type Ref } from 'vue';

Expand Down Expand Up @@ -31,7 +32,7 @@ export function useValidation<T>({
watch: watchRefs = [],
}: {
source: Ref<T>;
rules: UseValidationRule<T>[];
rules: MaybeRef<UseValidationRule<T>[]>;
watch?: Ref<unknown>[];
}) {
const state = reactive<{
Expand All @@ -55,7 +56,7 @@ export function useValidation<T>({
state.message = '';
state.status = undefined;

for (const rule of rules) {
for (const rule of get(rules)) {
if (isFalsyOrHasThrown(() => rule.validator(source.value))) {
state.message = rule.message;
state.status = 'error';
Expand Down
39 changes: 39 additions & 0 deletions src/ui/c-input-text/c-input-text.demo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<template>
<h2>Default</h2>

<c-input-text value="qsd" />

<h2>With placeholder</h2>

<c-input-text placeholder="Placeholder" />

<h2>With label</h2>

<c-input-text label="Label" mb-2 />
<c-input-text label="Label" mb-2 label-position="left" />
<c-input-text label="Label" mb-2 label-position="left" label-width="100px" />
<c-input-text label="Label" mb-2 label-position="left" label-width="100px" label-align="right" />

<h2>Readonly</h2>

<c-input-text value="value" readonly />

<h2>Disabled</h2>

<c-input-text value="value" disabled />

<h2>Validation</h2>

<c-input-text
v-model:value="value"
:validation-rules="[{ message: 'Length must be > 10', validator: (value) => value.length > 10 }]"
/>

<h2>Clearable</h2>

<c-input-text v-model:value="value" clearable />
</template>

<script lang="ts" setup>
const value = ref('value');
</script>
87 changes: 87 additions & 0 deletions src/ui/c-input-text/c-input-text.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { describe, expect, it, beforeEach } from 'vitest';
import { shallowMount } from '@vue/test-utils';
import { setActivePinia, createPinia } from 'pinia';
import _ from 'lodash';
import CInputText from './c-input-text.vue';

describe('CInputText', () => {
beforeEach(() => {
setActivePinia(createPinia());
});

it('Renders a label', () => {
const wrapper = shallowMount(CInputText, {
props: {
label: 'Label',
},
});

expect(wrapper.get('.label').text()).to.equal('Label');
});

it('Renders a placeholder', () => {
const wrapper = shallowMount(CInputText, {
props: {
placeholder: 'Placeholder',
},
});

expect(wrapper.get('.input').attributes('placeholder')).to.equal('Placeholder');
});

it('Renders a value', () => {
const wrapper = shallowMount(CInputText, {
props: {
value: 'Value',
},
});

expect(wrapper.vm.value).to.equal('Value');
});

it('Renders a provided id', () => {
const wrapper = shallowMount(CInputText, {
props: {
id: 'id',
},
});

expect(wrapper.get('.input').attributes('id')).to.equal('id');
});

it('updates value on input', async () => {
const wrapper = shallowMount(CInputText);

await wrapper.get('input').setValue('Hello');

expect(_.get(wrapper.emitted(), 'update:value.0.0')).to.equal('Hello');
});

it('cannot be edited when disabled', async () => {
const wrapper = shallowMount(CInputText, {
props: {
disabled: true,
},
});

await wrapper.get('input').setValue('Hello');

expect(_.get(wrapper.emitted(), 'update:value')).toBeUndefined();
});

it('renders a feedback message for invalid rules', async () => {
const wrapper = shallowMount(CInputText, {
props: { rules: [{ validator: () => false, message: 'Message' }] },
});

expect(wrapper.get('.feedback').text()).to.equal('Message');
});

it('feedback does not render for valid rules', async () => {
const wrapper = shallowMount(CInputText, {
props: { rules: [{ validator: () => true, message: 'Message' }] },
});

expect(wrapper.find('.feedback').exists()).to.equal(false);
});
});
20 changes: 20 additions & 0 deletions src/ui/c-input-text/c-input-text.theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineThemes } from '../theme/theme.models';

export const { useTheme } = defineThemes({
dark: {
backgroundColor: '#333333',
borderColor: '#333333',

focus: {
backgroundColor: '#1ea54c1a',
},
},
light: {
backgroundColor: '#ffffff',
borderColor: '#e0e0e69e',

focus: {
backgroundColor: '#ffffff',
},
},
});
Loading

0 comments on commit aad8d84

Please sign in to comment.