ui-lib(new-component): added text input component in the c-lib
This commit is contained in:
		
							parent
							
								
									cb1665acf2
								
							
						
					
					
						commit
						a64ebd43d5
					
				
							
								
								
									
										5
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -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'] | ||||
| @ -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'] | ||||
|  | ||||
| @ -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", | ||||
| @ -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", | ||||
| @ -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", | ||||
|  | ||||
							
								
								
									
										47
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										47
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @ -135,6 +135,9 @@ dependencies: | ||||
|     version: 2.2.1 | ||||
| 
 | ||||
| devDependencies: | ||||
|   '@iconify-json/mdi': | ||||
|     specifier: ^1.1.50 | ||||
|     version: 1.1.50 | ||||
|   '@playwright/test': | ||||
|     specifier: ^1.32.3 | ||||
|     version: 1.32.3 | ||||
| @ -192,12 +195,18 @@ devDependencies: | ||||
|   '@vitejs/plugin-vue-jsx': | ||||
|     specifier: ^1.3.10 | ||||
|     version: 1.3.10 | ||||
|   '@vue/compiler-sfc': | ||||
|     specifier: ^3.2.47 | ||||
|     version: 3.2.47 | ||||
|   '@vue/eslint-config-prettier': | ||||
|     specifier: ^7.1.0 | ||||
|     version: 7.1.0(eslint@8.38.0)(prettier@2.8.7) | ||||
|   '@vue/eslint-config-typescript': | ||||
|     specifier: ^10.0.0 | ||||
|     version: 10.0.0(eslint-plugin-vue@8.7.1)(eslint@8.38.0)(typescript@4.5.5) | ||||
|   '@vue/runtime-core': | ||||
|     specifier: ^3.2.47 | ||||
|     version: 3.2.47 | ||||
|   '@vue/test-utils': | ||||
|     specifier: ^2.3.2 | ||||
|     version: 2.3.2(vue@3.2.47) | ||||
| @ -246,6 +255,9 @@ devDependencies: | ||||
|   unplugin-auto-import: | ||||
|     specifier: ^0.15.2 | ||||
|     version: 0.15.2(@vueuse/core@8.9.4)(rollup@2.79.1) | ||||
|   unplugin-icons: | ||||
|     specifier: ^0.16.1 | ||||
|     version: 0.16.1(@vue/compiler-sfc@3.2.47) | ||||
|   unplugin-vue-components: | ||||
|     specifier: ^0.24.1 | ||||
|     version: 0.24.1(rollup@2.79.1)(vue@3.2.47) | ||||
| @ -1612,6 +1624,12 @@ packages: | ||||
|     resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} | ||||
|     dev: true | ||||
| 
 | ||||
|   /@iconify-json/mdi@1.1.50: | ||||
|     resolution: {integrity: sha512-SgbT5w5eHCdOG74ZWPz7HlTGk6VsifIJhNi6lAsxj/5Nlqt6Cz4LlQmSa9eecU9p075Jub2aAx/o7YI+GCahRQ==} | ||||
|     dependencies: | ||||
|       '@iconify/types': 2.0.0 | ||||
|     dev: true | ||||
| 
 | ||||
|   /@iconify/types@2.0.0: | ||||
|     resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} | ||||
|     dev: true | ||||
| @ -7561,6 +7579,35 @@ packages: | ||||
|       - rollup | ||||
|     dev: true | ||||
| 
 | ||||
|   /unplugin-icons@0.16.1(@vue/compiler-sfc@3.2.47): | ||||
|     resolution: {integrity: sha512-qTunFUkpAyDnwzwV7YV1ZgCWRYfLuURcCurhhXOWMy2ipY88qx1pADvral2hJu4Xymh0X0t3Zcll3BIru2AVLQ==} | ||||
|     peerDependencies: | ||||
|       '@svgr/core': '>=7.0.0' | ||||
|       '@vue/compiler-sfc': ^3.0.2 || ^2.7.0 | ||||
|       vue-template-compiler: ^2.6.12 | ||||
|       vue-template-es2015-compiler: ^1.9.0 | ||||
|     peerDependenciesMeta: | ||||
|       '@svgr/core': | ||||
|         optional: true | ||||
|       '@vue/compiler-sfc': | ||||
|         optional: true | ||||
|       vue-template-compiler: | ||||
|         optional: true | ||||
|       vue-template-es2015-compiler: | ||||
|         optional: true | ||||
|     dependencies: | ||||
|       '@antfu/install-pkg': 0.1.1 | ||||
|       '@antfu/utils': 0.7.2 | ||||
|       '@iconify/utils': 2.1.5 | ||||
|       '@vue/compiler-sfc': 3.2.47 | ||||
|       debug: 4.3.4 | ||||
|       kolorist: 1.7.0 | ||||
|       local-pkg: 0.4.3 | ||||
|       unplugin: 1.3.1 | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|     dev: true | ||||
| 
 | ||||
|   /unplugin-vue-components@0.24.1(rollup@2.79.1)(vue@3.2.47): | ||||
|     resolution: {integrity: sha512-T3A8HkZoIE1Cja95xNqolwza0yD5IVlgZZ1PVAGvVCx8xthmjsv38xWRCtHtwl+rvZyL9uif42SRkDGw9aCfMA==} | ||||
|     engines: {node: '>=14'} | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import { get, type MaybeRef } from '@vueuse/core'; | ||||
| import _ from 'lodash'; | ||||
| import { reactive, watch, type Ref } from 'vue'; | ||||
| 
 | ||||
| @ -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<{ | ||||
| @ -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'; | ||||
|  | ||||
							
								
								
									
										39
									
								
								src/ui/c-input-text/c-input-text.demo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/ui/c-input-text/c-input-text.demo.vue
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								src/ui/c-input-text/c-input-text.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/ui/c-input-text/c-input-text.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								src/ui/c-input-text/c-input-text.theme.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/ui/c-input-text/c-input-text.theme.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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', | ||||
|     }, | ||||
|   }, | ||||
| }); | ||||
							
								
								
									
										198
									
								
								src/ui/c-input-text/c-input-text.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/ui/c-input-text/c-input-text.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,198 @@ | ||||
| <template> | ||||
|   <div class="c-input-text" :class="{ disabled, error: !validation.isValid, 'label-left': labelPosition === 'left' }"> | ||||
|     <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> | ||||
| 
 | ||||
|     <span v-if="!validation.isValid" class="feedback"> {{ validation.message }} </span> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { generateRandomId } from '@/utils/random'; | ||||
| import { useValidation, type UseValidationRule } from '@/composable/validation'; | ||||
| import { useTheme } from './c-input-text.theme'; | ||||
| import { useAppTheme } from '../theme/themes'; | ||||
| 
 | ||||
| const props = withDefaults( | ||||
|   defineProps<{ | ||||
|     value?: string; | ||||
|     id?: string; | ||||
|     placeholder?: string; | ||||
|     label?: string; | ||||
|     readonly?: boolean; | ||||
|     disabled?: boolean; | ||||
|     validationRules?: UseValidationRule<string>[]; | ||||
|     labelPosition?: 'top' | 'left'; | ||||
|     labelWidth?: string; | ||||
|     labelAlign?: 'left' | 'right'; | ||||
|     clearable?: boolean; | ||||
|     testId?: string; | ||||
|     autocapitalize?: 'none' | 'sentences' | 'words' | 'characters' | 'on' | 'off' | string; | ||||
|     autocomplete?: 'on' | 'off' | string; | ||||
|     autocorrect?: 'on' | 'off' | string; | ||||
|     spellcheck?: 'true' | 'false' | boolean; | ||||
|     rawText?: boolean; | ||||
|   }>(), | ||||
|   { | ||||
|     value: '', | ||||
|     id: generateRandomId, | ||||
|     placeholder: 'Input text', | ||||
|     label: undefined, | ||||
|     readonly: false, | ||||
|     disabled: false, | ||||
|     validationRules: () => [], | ||||
|     labelPosition: 'top', | ||||
|     labelWidth: 'auto', | ||||
|     labelAlign: 'left', | ||||
|     clearable: false, | ||||
|     testId: undefined, | ||||
|     autocapitalize: undefined, | ||||
|     autocomplete: undefined, | ||||
|     autocorrect: undefined, | ||||
|     spellcheck: undefined, | ||||
|     rawText: false, | ||||
|   }, | ||||
| ); | ||||
| const emit = defineEmits(['update:value']); | ||||
| const value = useVModel(props, 'value', emit); | ||||
| 
 | ||||
| const { id, placeholder, label, validationRules, labelPosition, labelWidth, labelAlign } = toRefs(props); | ||||
| 
 | ||||
| const validation = useValidation({ | ||||
|   rules: validationRules, | ||||
|   source: value, | ||||
| }); | ||||
| 
 | ||||
| const theme = useTheme(); | ||||
| const appTheme = useAppTheme(); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="less" scoped> | ||||
| .c-input-text { | ||||
|   display: inline-flex; | ||||
|   flex-direction: column; | ||||
|   width: 100%; | ||||
| 
 | ||||
|   &.label-left { | ||||
|     flex-direction: row; | ||||
|     align-items: baseline; | ||||
|   } | ||||
| 
 | ||||
|   &.error { | ||||
|     & > .input { | ||||
|       border-color: v-bind('appTheme.error.color'); | ||||
|       &:hover, | ||||
|       &:focus { | ||||
|         border-color: v-bind('appTheme.error.color'); | ||||
|       } | ||||
| 
 | ||||
|       &:focus { | ||||
|         background-color: v-bind('appTheme.error.color + 22'); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     & > .feedback { | ||||
|       color: v-bind('appTheme.error.color'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   & > .label { | ||||
|     margin-bottom: 5px; | ||||
|     flex: 0 0 v-bind('labelWidth'); | ||||
|     text-align: v-bind('labelAlign'); | ||||
|     padding-right: 10px; | ||||
|   } | ||||
| 
 | ||||
|   .input-wrapper { | ||||
|     flex: 1 1 0; | ||||
|     min-width: 0; | ||||
| 
 | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|     align-items: center; | ||||
|     background-color: v-bind('theme.backgroundColor'); | ||||
|     border: 1px solid v-bind('theme.borderColor'); | ||||
|     border-radius: 4px; | ||||
|     padding: 0 4px 0 12px; | ||||
| 
 | ||||
|     & > .input { | ||||
|       flex: 1 1 0; | ||||
|       min-width: 0; | ||||
| 
 | ||||
|       padding: 8px 0; | ||||
|       outline: none; | ||||
|       transition: border-color 0.2s ease-in-out; | ||||
|       background-color: transparent; | ||||
|       background-image: none; | ||||
|       -webkit-box-shadow: none; | ||||
|       -moz-box-shadow: none; | ||||
|       box-shadow: none; | ||||
|       background-color: transparent; | ||||
|       border: none; | ||||
|       color: v-bind('appTheme.text.baseColor'); | ||||
| 
 | ||||
|       &::placeholder { | ||||
|         color: v-bind('appTheme.text.mutedColor'); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &:hover, | ||||
|     &:focus { | ||||
|       border-color: v-bind('appTheme.primary.color'); | ||||
|     } | ||||
| 
 | ||||
|     &:focus { | ||||
|       background-color: v-bind('theme.focus.backgroundColor'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.error .input-wrapper { | ||||
|     border-color: v-bind('appTheme.error.color'); | ||||
| 
 | ||||
|     &:hover, | ||||
|     &:focus { | ||||
|       border-color: v-bind('appTheme.error.color'); | ||||
|     } | ||||
| 
 | ||||
|     &:focus { | ||||
|       background-color: v-bind('appTheme.error.color + 22'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &.disabled .input-wrapper { | ||||
|     opacity: 0.5; | ||||
| 
 | ||||
|     &:hover, | ||||
|     &:focus { | ||||
|       border-color: v-bind('theme.borderColor'); | ||||
|     } | ||||
| 
 | ||||
|     & > .input { | ||||
|       cursor: not-allowed; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @ -18,6 +18,8 @@ | ||||
|       </div> | ||||
| 
 | ||||
|       <div flex-1 pl-4> | ||||
|         <h1>{{ componentName }}</h1> | ||||
| 
 | ||||
|         <router-view /> | ||||
|       </div> | ||||
|     </div> | ||||
| @ -25,9 +27,12 @@ | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import _ from 'lodash'; | ||||
| import { demoRoutes } from './demo.routes'; | ||||
| 
 | ||||
| const route = useRoute(); | ||||
| 
 | ||||
| const componentName = computed(() => _.startCase(String(route.name).replace(/^c-/, ''))); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="less" scoped></style> | ||||
|  | ||||
| @ -6,8 +6,6 @@ export const demoRoutes = Object.keys(demoPages).map((path) => { | ||||
|   const [, , fileName] = path.split('/'); | ||||
|   const name = fileName.split('.').shift(); | ||||
| 
 | ||||
|   console.log(path); | ||||
| 
 | ||||
|   return { | ||||
|     path: name, | ||||
|     name, | ||||
|  | ||||
| @ -18,4 +18,14 @@ const shuffleArray = <T>(array: T[]): T[] => shuffleArrayMutate([...array]); | ||||
| 
 | ||||
| const shuffleString = (str: string, delimiter = ''): string => shuffleArrayMutate(str.split(delimiter)).join(delimiter); | ||||
| 
 | ||||
| export { randFromArray, randIntFromInterval, random, shuffleArray, shuffleArrayMutate, shuffleString }; | ||||
| const generateRandomId = () => `id-${random().toString(36).substring(2, 12)}`; | ||||
| 
 | ||||
| export { | ||||
|   randFromArray, | ||||
|   randIntFromInterval, | ||||
|   random, | ||||
|   shuffleArray, | ||||
|   shuffleArrayMutate, | ||||
|   shuffleString, | ||||
|   generateRandomId, | ||||
| }; | ||||
|  | ||||
| @ -9,6 +9,6 @@ | ||||
|     "paths": { | ||||
|       "@/*": ["./src/*"] | ||||
|     }, | ||||
|     "types": ["naive-ui/volar"] | ||||
|     "types": ["naive-ui/volar", "unplugin-icons/types/vue"] | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -10,6 +10,9 @@ import AutoImport from 'unplugin-auto-import/vite'; | ||||
| import Components from 'unplugin-vue-components/vite'; | ||||
| import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'; | ||||
| import Unocss from 'unocss/vite'; | ||||
| import { configDefaults } from 'vitest/config'; | ||||
| import Icons from 'unplugin-icons/vite'; | ||||
| import IconsResolver from 'unplugin-icons/resolver'; | ||||
| 
 | ||||
| // https://vitejs.dev/config/
 | ||||
| export default defineConfig({ | ||||
| @ -28,7 +31,7 @@ export default defineConfig({ | ||||
|         enabled: true, | ||||
|       }, | ||||
|     }), | ||||
| 
 | ||||
|     Icons({ compiler: 'vue3' }), | ||||
|     vue({ | ||||
|       include: [/\.vue$/, /\.md$/], | ||||
|     }), | ||||
| @ -76,7 +79,7 @@ export default defineConfig({ | ||||
|       dirs: ['src/'], | ||||
|       extensions: ['vue', 'md'], | ||||
|       include: [/\.vue$/, /\.vue\?vue/, /\.md$/], | ||||
|       resolvers: [NaiveUiResolver()], | ||||
|       resolvers: [NaiveUiResolver(), IconsResolver({ prefix: 'icon' })], | ||||
|     }), | ||||
|     Unocss(), | ||||
|   ], | ||||
| @ -88,4 +91,7 @@ export default defineConfig({ | ||||
|   define: { | ||||
|     'import.meta.env.PACKAGE_VERSION': JSON.stringify(process.env.npm_package_version), | ||||
|   }, | ||||
|   test: { | ||||
|     exclude: [...configDefaults.exclude, '**/*.e2e.spec.ts'], | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| @ -1,13 +0,0 @@ | ||||
| import { configDefaults, defineConfig } from 'vitest/config'; | ||||
| import path from 'path'; | ||||
| 
 | ||||
| export default defineConfig({ | ||||
|   resolve: { | ||||
|     alias: { | ||||
|       '@': path.resolve(__dirname, './src'), | ||||
|     }, | ||||
|   }, | ||||
|   test: { | ||||
|     exclude: [...configDefaults.exclude, '**/*.e2e.spec.ts'], | ||||
|   }, | ||||
| }); | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user