big update

This commit is contained in:
AntoXa PRO 2023-09-20 14:09:22 +03:00
parent f58a5106eb
commit 56c40021ba
35 changed files with 1049 additions and 324 deletions

14
index.html Normal file
View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -1,23 +1,25 @@
{ {
"name": "axp-ui", "name": "axp-ui",
"descriiption": "My helper ui lib", "descriiption": "My helper ui lib",
"version": "1.6.3", "version": "1.7.0",
"homepage": "https://antoxahub.ru/antoxa/axp-ui", "homepage": "https://antoxahub.ru/antoxa/axp-ui",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://antoxahub.ru/antoxa/axp-ui.git" "url": "https://antoxahub.ru/antoxa/axp-ui.git"
}, },
"module": "./dist/index.js", "module": "./dist/index.mjs",
"main": "./dist/index.umd.js",
"types": "./dist/index.d.ts",
"exports": { "exports": {
".": { ".": {
"import": "./dist/index.js" "import": "./dist/index.mjs",
"requier": "./dist/index.umd.js"
}, },
"./styles/": [ "./tailwind.config": "./tailwind.config.ts",
"./src/css/" "./style.css": "./dist/style.css",
], "./style/*": "./src/style/*",
"./tailwind.config": "./tailwind.config.ts" "./tsconfig.json": "./tsconfig.json"
}, },
"types": "./dist/index.d.ts",
"files": [ "files": [
"dist", "dist",
"tsconfig.json", "tsconfig.json",
@ -25,23 +27,23 @@
"src/css" "src/css"
], ],
"scripts": { "scripts": {
"build": "rollup -c --configPlugin rollup-plugin-typescript2", "build": "vite build",
"dev": "vite",
"prepare": "npm run build" "prepare": "npm run build"
}, },
"dependencies": { "peerDependencies": {
"axp-ts": "^1.9.6", "axp-ts": "^1.9.10",
"vue": "^3.3.4" "vue": "^3.3.4"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.4.14", "@vitejs/plugin-vue": "^4.3.4",
"postcss": "^8.4.27", "autoprefixer": "^10.4.15",
"postcss": "^8.4.29",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"rollup": "^3.27.0", "sass": "^1.66.1",
"rollup-plugin-sass": "^1.12.20",
"rollup-plugin-typescript2": "^0.35.0",
"rollup-plugin-vue": "^6.0.0",
"sass": "^1.64.2",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tslib": "^2.6.1" "typescript": "^5.2.2",
"vite": "^4.4.9",
"vite-plugin-dts": "^3.5.3"
} }
} }

View File

@ -1,14 +0,0 @@
import { defineConfig } from 'rollup'
import typescript from 'rollup-plugin-typescript2'
import vue from 'rollup-plugin-vue'
import sass from 'rollup-plugin-sass'
export default defineConfig({
input: 'src/index.ts',
output: {
format: 'esm',
dir: 'dist'
},
plugins: [typescript(), vue(), sass()],
external: ['vue', 'axp-ts'],
})

342
src/App.vue Normal file
View File

@ -0,0 +1,342 @@
<script setup lang="ts">
import { ref } from 'vue'
import { cFieldsSchema } from 'axp-ts'
import { colors, icons } from '.'
import {
UiBtn,
UiIcon,
UiIconVisibility,
UiAlert,
UiForm,
UiFieldText,
UiFieldNumber,
UiFieldPassword,
UiFieldPhone,
UiFieldDate,
UiFieldTextArea,
UiFieldSelect,
UiFieldSelectGender,
UiFieldCheckbox,
UiFieldFile,
UiTable,
UiCard,
UiPickerDays
} from './components'
// Test fields.
const fieldTextValue = ref('Text field')
const fieldTextError = ref('Error message')
const fieldNumberValue = ref(new Date().getDay())
// Test phone number.
const testPhoneNumber = ref(996919353)
const testPhoneNumberError = ref('')
const testPhoneNumberSchema = cFieldsSchema.shape.phone
const testHandler = () => {
try {
testPhoneNumberError.value = ''
testPhoneNumberSchema.parse(testPhoneNumber.value)
} catch (e: any) {
testPhoneNumberError.value = 'Number phone not valid'
}
}
// Test checkbox.
const testCheckboxValue = ref(true)
const testCheckboxError = ref('Error checkbox')
// Days.
const days = ref([1, 3, 5])
// File.
const testFileValue = ref<FileList | undefined>()
</script>
<template>
<div class="layout">
<header class="header">
<div class="container">
<h1 class="page-title">AXP-UI My helper ui lib</h1>
</div>
</header>
<main class="main">
<div class="container">
<div class="components">
<h2 class="title">Components UI:</h2>
<div class="item btns">
<div class="item-title">Buttons</div>
<div class="item-content flex">
<ui-btn
v-for="item in colors"
class="mr-2 mb-2"
:color="item"
:label="item"
/>
<ui-btn label="Disabled" class="mr-2 mb-2" disabled />
<ui-btn label="Icon" icon="edit" />
</div>
</div>
<div class="item fields">
<div class="item-title">Input fields</div>
<div class="item-content">
<ui-field-text
label="Text"
v-model="fieldTextValue"
:description="'Description field: ' + fieldTextValue"
/>
<ui-field-text label="Placeholder" placeholder="Search..." />
<ui-field-text
model-value="Disabled text field"
disabled
/>
<ui-field-text label="Error" v-model:error="fieldTextError" />
<ui-field-number
label="Номер"
v-model="fieldNumberValue"
:description="'Value field: ' + fieldNumberValue"
v-model:error="fieldTextError"
/>
<ui-field-password label="Пароль" v-model:error="fieldTextError" />
<ui-field-phone
label="Телефон"
v-model="testPhoneNumber"
v-model:error="testPhoneNumberError"
:description="'Value phone number ' + testPhoneNumber"
/>
<ui-btn
label="Valid phone"
color="primary"
@click="testHandler"
class="mb-4"
/>
<ui-field-date
label="Дата"
:modelValue="new Date()"
v-model:error="fieldTextError"
/>
<ui-field-text-area
label="Текстовая область"
v-model="fieldTextValue"
:description="'Text area value: ' + fieldTextValue"
/>
</div>
</div>
<div class="item fields">
<div class="item-title">Выподающие списки</div>
<div class="item-content">
<ui-field-select-gender label="Выбор пола" />
<ui-field-select
label="Заблокирован"
:modelValue="1"
:options="[
{ text: 'Выбор 1', value: 1 },
{ text: 'Выбор 2', value: 2 }
]"
disabled
/>
</div>
</div>
<div class="item checkbox">
<div class="item-title">Чекбоксы</div>
<div class="item-content">
<ui-field-checkbox
label="Выбрать 1"
v-model="testCheckboxValue"
:description="'Value checkbox: ' + testCheckboxValue"
/>
<ui-field-checkbox
label="Выбрать 2"
:modelValue="true"
v-model:error="testCheckboxError"
/>
<ui-field-checkbox label="Выбрать 3" disabled />
</div>
</div>
<div class="item icons">
<div class="item-title">Иконки</div>
<div class="item-content">
<div class="grid grid-cols-4 lg:grid-cols-8 gap-4">
<div
class="flex flex-col items-center"
v-for="item of Object.keys(icons)"
>
<ui-icon :name="item" class="mb-2" />
<div class="mb-4">{{ item }}</div>
</div>
</div>
</div>
</div>
<div class="item alerts">
<div class="item-title">Уведомления</div>
<div class="item-content">
<ui-alert
v-for="item in colors"
:color="item"
:value="'Текстовое уведомление - ' + item"
close
/>
</div>
</div>
<div class="item tables">
<div class="item-title">Таблицы</div>
<div class="item-content">
<ui-table>
<thead>
<th class="w-0"></th>
<th class="w-0">Видимость</th>
<th>Название столбца</th>
</thead>
<tbody>
<tr>
<td>
<ui-btn icon="edit" />
</td>
<td>
<ui-icon-visibility
class="m-auto"
:modelValue="false"
color
/>
</td>
<td>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Quis delectus magnam hic! Corporis blanditiis sequi quae
autem quaerat soluta cupiditate ipsa ducimus quidem ipsum
dicta, reiciendis excepturi vero tempora in.
</td>
</tr>
<tr>
<td>
<ui-btn icon="edit" />
</td>
<td>
<ui-icon-visibility
class="m-auto"
:modelValue="true"
color
/>
</td>
<td>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Recusandae asperiores pariatur quasi ad a sunt minus quia
omnis! Doloremque sapiente repellat, ea voluptatibus
dolor inventore itaque quidem temporibus a asperiores.
</td>
</tr>
<tr>
<td>
<ui-btn icon="edit" />
</td>
<td>
<ui-icon-visibility
class="m-auto"
:modelValue="false"
color
/>
</td>
<td>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Rem facere ratione commodi odit accusantium iusto, harum
asperiores explicabo veniam ipsam placeat recusandae quod
quisquam! Pariatur est veniam possimus. Mollitia, culpa.
</td>
</tr>
<tr>
<td>
<ui-btn icon="edit" />
</td>
<td>
<ui-icon-visibility
class="m-auto"
:modelValue="true"
color
/>
</td>
<td>
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
Necessitatibus esse autem natus, cum consequuntur
aspernatur rerum aliquam voluptatum laboriosam harum
fuga, deserunt ad corporis ipsum quod nulla atque.
Laborum, beatae.
</td>
</tr>
</tbody>
</ui-table>
</div>
</div>
<div class="item complex">
<div class="item-title">Сложные элементы</div>
<div class="item-content">
<div class="grid grid-cols-2 gap-4">
<ui-card title="Карточка">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Similique incidunt sunt fugiat, ratione eveniet consequatur
soluta cum sit voluptatem expedita adipisci qui,
dignissimos quae? Sed, atque omnis! Dolor, iure molestias!
</p>
<ui-picker-days v-model="days" />
<code class="block mt-4">Дни недели: {{ days }}</code>
<template v-slot:actions>
<ui-btn label="Отмена" />
<ui-btn label="Кнопка" color="accent" />
</template>
</ui-card>
<ui-card title="Картачка с формой">
<ui-form>
<ui-field-text label="Текстовое поле" />
<ui-field-date label="Дата" />
<ui-field-file
v-model="testFileValue"
:description="'Count files: ' + testFileValue?.length"
multiple
/>
</ui-form>
</ui-card>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</template>
<style lang="sass">
@import './style/tailwind.sass'
@import './style/common.sass'
@import './style/theme.sass'
.header
@apply py-8 border-b
.page-title
@apply text-primary
.main
@apply py-4
.components
.title
@apply mb-6
.item
@apply mb-8
&-title
@apply text-lg font-bold mb-4
@apply text-primary
&.fields
.ui-field:not(:last-child)
@apply mb-4
</style>

View File

@ -1,14 +1,25 @@
<script setup lang="ts"> <script lang="ts">
import type { TColor } from '../colors' import type { TColor } from '../colors'
// Props. export type TUiAlertProps = {
const props = defineProps<{
color?: TColor color?: TColor
value?: string value?: string
close?: boolean close?: boolean
}>() }
const emit = defineEmits<{ (e: 'close'): void }>() export type TUiAlertEmits = {
(e: 'close'): void
}
</script>
<script setup lang="ts">
import UiIcon from './Icon.vue'
// Props.
const props = defineProps<TUiAlertProps>()
// Emit.
const emit = defineEmits<TUiAlertEmits>()
</script> </script>
<template> <template>

View File

@ -1,26 +1,52 @@
<script setup lang="ts"> <script lang="ts">
import type { TColor } from '../colors' import type { TColor } from '../colors'
import type { TUiIcon } from '../icons' import type { TUiIcon } from '../icons'
import UiIcon from './Icon.vue' export type TUiBtnProps = {
// Props.
const props = defineProps<{
label?: string label?: string
type?: 'button' | 'submit' type?: 'button' | 'submit'
color?: TColor color?: TColor
icon?: TUiIcon icon?: TUiIcon
to?: string to?: string
}>() }
export type TUiBtnEmits = {
(e: 'click', v: PointerEvent): void
}
</script>
<script setup lang="ts">
import UiIcon from './Icon.vue'
import { computed } from 'vue'
// Props.
const props = defineProps<TUiBtnProps>()
// Emits.
const emit = defineEmits<TUiBtnEmits>()
// Handlers.
const clickHandler = (e: PointerEvent) => emit('click', e)
// Computed.
const cssClass = computed(() => {
let obj: any = {
'ui-btn': true,
'ui-btn-icon': props.icon
}
if (props.color) obj[props.color] = true
return obj
})
</script> </script>
<template> <template>
<component <component
class="ui-btn"
:is="props.to ? 'router-link' : 'button'" :is="props.to ? 'router-link' : 'button'"
:to="props.to" :to="props.to"
:class="props.color"
:type="props.type" :type="props.type"
@click="clickHandler"
:class="cssClass"
> >
<ui-icon v-if="props.icon" :name="props.icon" /> <ui-icon v-if="props.icon" :name="props.icon" />
<div v-if="props.label" class="label">{{ props.label }}</div> <div v-if="props.label" class="label">{{ props.label }}</div>

View File

@ -1,7 +1,11 @@
<script setup lang="ts"> <script lang="ts">
const props = defineProps<{ export type TUiCardProps = {
title?: string title?: string
}>() }
</script>
<script setup lang="ts">
const props = defineProps<TUiCardProps>()
</script> </script>
<template> <template>

View File

@ -1,32 +1,43 @@
<script lang="ts">
export type TUiDialogProps = {
title?: string
open?: boolean
close?: boolean
}
export type TUiDialogEmits = {
(e: 'update:open', v: boolean): void
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import UiIcon from './Icon.vue' import UiIcon from './Icon.vue'
const props = defineProps({ // Props.
open: { type: Boolean, default: false }, const props = defineProps<TUiDialogProps>()
close: { type: Boolean, default: false },
title: { type: String }
})
const emit = defineEmits(['update:open']) // Emits.
const emit = defineEmits<TUiDialogEmits>()
// Init data.
const dialogState = ref(props.open) const dialogState = ref(props.open)
// Methods.
const closeDialog = () => { const closeDialog = () => {
dialogState.value = false dialogState.value = false
emit('update:open', false) emit('update:open', false)
} }
// Handlers.
const clickWrapperHandler = (e: MouseEvent) => { const clickWrapperHandler = (e: MouseEvent) => {
if (e.target instanceof Element) { if (e.target instanceof Element) {
if (e.target.classList.contains('ui-dialog')) closeDialog() if (e.target.classList.contains('ui-dialog')) closeDialog()
} }
} }
watch( // Etc.
() => props.open, watch(() => props.open, val => (dialogState.value = val))
val => (dialogState.value = val)
)
</script> </script>
<template> <template>
@ -34,7 +45,7 @@ watch(
<div class="ui-dialog-window"> <div class="ui-dialog-window">
<div class="ui-dialog-window-header" v-if="props.title"> <div class="ui-dialog-window-header" v-if="props.title">
<h4>{{ title }}</h4> <h4>{{ title }}</h4>
<ui-icon name="close" @click="closeDialog" /> <ui-icon name="close" @click="closeDialog" class="ui-icon-close" />
</div> </div>
<div v-if="$slots.default" class="ui-dialog-window-content"> <div v-if="$slots.default" class="ui-dialog-window-content">
<slot name="default" /> <slot name="default" />

View File

@ -1,95 +0,0 @@
<script setup lang="ts">
const props = defineProps<{
modelValue?: any
label?: string
type?: 'text' | 'number' | 'date' | 'password' | 'checkbox'
error?: string
readonly?: boolean
disabled?: boolean
placeholder?: string
tag?: 'input' | 'textarea' | 'select'
checked?: boolean
options?: { text: string, value: any }[]
multiple?: boolean
}>()
// Emits.
const emit = defineEmits<{
(e: 'input', v: any): void
(e: 'update:modelValue', v: any): void
(e: 'update:error'): void
}>()
// Handlers.
const inputHandler = (val: any) => {
emit('input', val)
emit('update:error')
if (val?.target?.value) {
let value: any = val.target.value
try {
switch (props.type) {
case 'number':
value = Number(value)
break
case 'date':
value = new Date(value)
break
case 'text':
case 'password':
case 'checkbox':
value = String(value)
break
}
} catch (ex) {}
emit('update:modelValue', value)
} else {
emit('update:modelValue', undefined)
}
}
</script>
<template>
<div
:class="{ 'ui-field': true, error: props.error, disabled: props.disabled }"
>
<div v-if="props.label" class="label">{{ props.label }}</div>
<input
v-if="!tag || tag === 'input'"
class="input"
:type="props.type"
:value="props.modelValue"
:readonly="props.readonly"
:disabled="props.disabled"
:checked="checked"
:placeholder="props.placeholder"
@input="inputHandler"
/>
<textarea
v-if="tag === 'textarea'"
class="input"
:value="props.modelValue"
:readonly="props.readonly"
:disabled="props.disabled"
:placeholder="props.placeholder"
@input="inputHandler"
/>
<select
v-if="tag === 'select'"
class="input"
:value="props.modelValue"
:readonly="props.readonly"
:disabled="props.disabled"
:placeholder="props.placeholder"
:multiple="props.multiple"
@input="inputHandler"
>
<option v-for="item in props.options" :value="item.value">
{{ item.text }}
</option>
</select>
<div v-if="props.error" class="message">{{ props.error }}</div>
</div>
</template>

View File

@ -1,31 +1,51 @@
<script lang="ts">
import type { TUiFiledWrapperProps } from './FieldWrapper.vue'
export type TUiFieldCheckboxProps = TUiFiledWrapperProps & {
modelValue?: boolean
}
export type TUiFieldCheckboxEmits = {
(e: 'input', v: Event): void
(e: 'update:model-value', v?: boolean): void
(e: 'update:error', v?: string): void
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import UiField from './Field.vue' import UiFieldWrapper from './FieldWrapper.vue'
import { computed } from 'vue'
// Props. // Props.
const props = defineProps<{ const props = defineProps<TUiFieldCheckboxProps>()
modelValue?: boolean
disabled?: boolean
readonly?: boolean
}>()
// Emits. // Emits.
const emit = defineEmits<{ (e: 'update:modelValue', v?: boolean): void }>() const emit = defineEmits<TUiFieldCheckboxEmits>()
// Value.
const value = computed(() => props.modelValue)
// Handlers. // Handlers.
const updateHandler = () => emit('update:modelValue', !props.modelValue) const inputHandler = (val: Event) => {
emit('input', val)
emit('update:error')
if (val.target instanceof HTMLInputElement) {
emit('update:model-value', !props.modelValue)
}
}
</script> </script>
<template> <template>
<ui-field <ui-field-wrapper
type="checkbox" :label="props.label"
class="ui-field-checkbox" :error="props.error"
:checked="value"
:disabled="props.disabled" :disabled="props.disabled"
:readonly="props.readonly" :readonly="props.readonly"
@input="updateHandler" :description="props.description"
class="ui-field-checkbox"
>
<input
type="checkbox"
:checked="props.modelValue"
:disabled="props.disabled || props.readonly"
@input="inputHandler"
/> />
</ui-field-wrapper>
</template> </template>

View File

@ -1,32 +1,39 @@
<script lang="ts">
import type { TUiFieldInputProps, TUiFieldInputEmits } from './FieldInput.vue'
export type TUiFieldDateProps = TUiFieldInputProps<Date>
export type TUiFieldDateEmits = TUiFieldInputEmits
</script>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { cFieldsSchema, getDateCommonFormat } from 'axp-ts' import { cFieldsSchema, getDateCommonFormat } from 'axp-ts'
import UiFieldInput from './FieldInput.vue'
import UiField from './Field.vue' // Props.
const props = defineProps<TUiFieldDateProps>()
const props = defineProps<{ // Emits.
modelValue?: Date const emit = defineEmits<TUiFieldDateEmits>()
}>()
const emit = defineEmits<{ (e: 'update:modelValue', v?: Date): void }>()
// Etc.
const valueStr = computed({ const valueStr = computed({
get: () => { get: () => {
try { try {
const value = cFieldsSchema.shape.date.parse(props.modelValue) const value = cFieldsSchema.shape.date.parse(props.modelValue)
const format = getDateCommonFormat(value) return getDateCommonFormat(value)
return format
} catch (e) { } } catch (e) { }
}, },
set: (val) => { set: val => {
emit('update:error')
try { try {
const value = cFieldsSchema.shape.date.parse(val) const value = cFieldsSchema.shape.date.parse(val)
emit('update:modelValue', value) emit('update:model-value', value)
} catch (e) { } } catch (e) { }
} }
}) })
</script> </script>
<template> <template>
<ui-field type="date" class="ui-field-date" v-model="valueStr" /> <ui-field-input :="{ ...props, ...$attrs }" v-model="valueStr" type="date" />
</template> </template>

View File

@ -0,0 +1,57 @@
<script lang="ts">
import type { TUiFiledWrapperProps } from './FieldWrapper.vue'
type TModelValue = FileList
export type TUiFieldFileProps = TUiFiledWrapperProps & {
modelValue?: TModelValue
multiple?: boolean
accept?: string
}
export type TUiFieldFileEmits = {
(e: 'input', v?: Event): void
(e: 'update:model-value', v?: TModelValue): void
(e: 'update:error', v?: string): void
}
</script>
<script setup lang="ts">
import UiFieldWrapper from './FieldWrapper.vue'
// Props.
const props = defineProps<TUiFieldFileProps>()
// Emits.
const emit = defineEmits<TUiFieldFileEmits>()
// Handlers.
const inputHandler = (e: Event) => {
emit('input', e)
emit('update:error')
if (e.target instanceof HTMLInputElement && e.target.files) {
emit('update:model-value', e.target.files)
} else {
emit('update:model-value')
}
}
</script>
<template>
<ui-field-wrapper
:label="props.label"
:error="props.error"
:disabled="props.disabled"
:readonly="props.readonly"
:description="props.description"
>
<input
type="file"
:multiple="props.multiple"
:disabled="props.disabled || props.readonly"
:accept="props.accept"
@input="inputHandler"
/>
</ui-field-wrapper>
</template>

View File

@ -0,0 +1,64 @@
<script lang="ts">
import type { TUiFiledWrapperProps } from './FieldWrapper.vue'
export type TUiFieldInputProps<T extends string | number | Date> =
TUiFiledWrapperProps & {
type?: 'text' | 'number' | 'password' | 'date' | 'checkbox'
modelValue?: T
placeholder?: string
}
export type TUiFieldInputEmits = {
(e: 'input', v: Event): void
(e: 'update:model-value', v?: string | number | Date): void
(e: 'update:error', v?: string): void
}
</script>
<script setup lang="ts">
import UiFieldWrapper from './FieldWrapper.vue'
// Props.
const props = defineProps<TUiFieldInputProps<any>>()
// Emits.
const emit = defineEmits<TUiFieldInputEmits>()
// Handlers.
const inputHandler = (val: Event) => {
emit('input', val)
emit('update:error')
if (val.target instanceof HTMLInputElement) {
const { value } = val.target
switch (props.type) {
case 'number':
emit('update:model-value', Number(value))
break
default:
emit('update:model-value', String(value))
}
} else {
emit('update:model-value', undefined)
}
}
</script>
<template>
<ui-field-wrapper
:label="props.label"
:error="props.error"
:disabled="props.disabled"
:readonly="props.readonly"
:description="props.description"
>
<input
:type="props.type"
:value="props.modelValue"
:placeholder="props.placeholder"
:disabled="props.disabled"
:readonly="props.readonly"
@input="inputHandler"
/>
</ui-field-wrapper>
</template>

View File

@ -1,7 +1,16 @@
<script lang="ts">
import type { TUiFieldInputProps } from './FieldInput.vue'
export type TUiFieldNumberProps = TUiFieldInputProps<number>
</script>
<script setup lang="ts"> <script setup lang="ts">
import UiField from './Field.vue' import UiFieldInput from './FieldInput.vue'
// Props.
const props = defineProps<TUiFieldNumberProps>()
</script> </script>
<template> <template>
<ui-field type="number" class="ui-field-number" /> <ui-field-input :="{ ...props, ...$attrs }" type="number" />
</template> </template>

View File

@ -1,7 +1,16 @@
<script lang="ts">
import type { TUiFieldInputProps } from './FieldInput.vue'
export type TFieldPasswordProps = TUiFieldInputProps<string>
</script>
<script setup lang="ts"> <script setup lang="ts">
import UiField from './Field.vue' import UiFieldInput from './FieldInput.vue'
// Props.
const props = defineProps<TFieldPasswordProps>()
</script> </script>
<template> <template>
<ui-field type="password" class="ui-field-password" /> <ui-field-input :="{ ...props, ...$attrs }" type="password" />
</template> </template>

View File

@ -1,22 +1,32 @@
<script lang="ts">
import type { TUiFieldInputProps, TUiFieldInputEmits } from './FieldInput.vue'
export type TUiFieldPhoneProps = TUiFieldInputProps<number>
export type TUiFieldPhoneEmits = TUiFieldInputEmits
</script>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { getPhoneNumberFormat, getPhoneNumberValue } from 'axp-ts' import { getPhoneNumberFormat, getPhoneNumberValue } from 'axp-ts'
import UiField from './Field.vue' import UiFieldInput from './FieldInput.vue'
// Props. // Props.
const props = defineProps<{ modelValue?: number }>() const props = defineProps<TUiFieldPhoneProps>()
// Emits. // Emits.
const emit = defineEmits<{ (e: 'update:modelValue', v?: number): void }>() const emit = defineEmits<TUiFieldPhoneEmits>()
// Value string. // Value string.
const valueStr = computed({ const valueStr = computed({
get: () => getPhoneNumberFormat(props.modelValue), get: () => getPhoneNumberFormat(props.modelValue),
set: val => emit('update:modelValue', getPhoneNumberValue(val)) set: val => {
emit('update:error')
emit('update:model-value', getPhoneNumberValue(val))
}
}) })
</script> </script>
<template> <template>
<ui-field v-model="valueStr" class="ui-field-phone" /> <ui-field-input :="{ ...props, ...$attrs }" v-model="valueStr" />
</template> </template>

View File

@ -1,27 +1,59 @@
<script setup lang="ts"> <script lang="ts">
import { computed } from 'vue' import type { TUiFiledWrapperProps } from './FieldWrapper.vue'
import UiField from './Field.vue'
const props = defineProps<{ export type TUiFieldSelectProps = TUiFiledWrapperProps & {
modelValue?: string | string[] | number | number[], modelValue?: string | string[] | number | number[]
options?: { text: string; value: string | number }[]
multiple?: boolean multiple?: boolean
options: { text: string, value: any }[] }
}>()
const emit = defineEmits<{ (e: 'update:modelValue', v?: any): void }>() export type TUiFieldSelectEmits = {
(e: 'input', v: Event): void
(e: 'update:model-value', v?: string | string[] | number | number[]): void
(e: 'update:error', v?: string): void
}
</script>
const displayValue = computed({ <script setup lang="ts">
get: () => props.modelValue, import UiFieldWrapper from './FieldWrapper.vue'
set: (val) => emit('update:modelValue', val)
}) // Props.
const props = defineProps<TUiFieldSelectProps>()
// Emits.
const emit = defineEmits<TUiFieldSelectEmits>()
// Handlers.
const inputHandler = (val: Event) => {
emit('input', val)
emit('update:error')
if (val.target instanceof HTMLSelectElement) {
emit('update:model-value', val.target.value)
} else {
emit('update:model-value')
}
}
</script> </script>
<template> <template>
<ui-field <ui-field-wrapper
tag="select" :label="props.label"
:error="props.error"
:disabled="props.disabled"
:readonly="props.readonly"
:description="props.description"
class="ui-field-select" class="ui-field-select"
:options="props.options" >
<select
:value="props.modelValue"
:multiple="props.multiple" :multiple="props.multiple"
v-model="displayValue" :disabled="props.disabled || props.readonly"
/> @input="inputHandler"
>
<option v-for="item in props.options" :value="item.value">
{{ item.text }}
</option>
</select>
</ui-field-wrapper>
</template> </template>

View File

@ -15,5 +15,5 @@ const options: { text: string, value: TGender }[] = [
</script> </script>
<template> <template>
<ui-field-select class="ui-field-select-gender" :options="options" /> <ui-field-select :options="options" />
</template> </template>

View File

@ -1,7 +1,16 @@
<script lang="ts">
import type { TUiFieldInputProps } from './FieldInput.vue'
export type TUiFieldText = TUiFieldInputProps<string>
</script>
<script setup lang="ts"> <script setup lang="ts">
import UiField from './Field.vue' import UiFieldInput from './FieldInput.vue'
// Props.
const props = defineProps<TUiFieldText>()
</script> </script>
<template> <template>
<ui-field class="ui-field-text" /> <ui-field-input :="{ ...props, ...$attrs }" type="text" />
</template> </template>

View File

@ -1,7 +1,50 @@
<script lang="ts">
import type { TUiFiledWrapperProps } from './FieldWrapper.vue'
export type TUiFieldTextAreaProps = TUiFiledWrapperProps & {
modelValue?: string
}
export type TUiFieldTextAreaEmits = {
(e: 'input', v: Event): void
(e: 'update:model-value', v?: string | number | Date): void
(e: 'update:error', v?: string): void
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import UiField from './Field.vue' import UiFieldWrapper from './FieldWrapper.vue'
// Props.
const props = defineProps<TUiFieldTextAreaProps>()
// Emits.
const emit = defineEmits<TUiFieldTextAreaEmits>()
// Handlers.
const inputHandler = (val: Event) => {
emit('input', val)
emit('update:error')
if (val.target instanceof HTMLTextAreaElement) {
emit('update:model-value', val.target.value)
}
}
</script> </script>
<template> <template>
<ui-field tag="textarea" class="ui-field-textarea" /> <ui-field-wrapper
:label="props.label"
:error="props.error"
:disabled="props.disabled"
:readonly="props.readonly"
:description="props.description"
class="ui-field-textarea"
>
<textarea
:value="props.modelValue"
:disabled="props.disabled || props.readonly"
@input="inputHandler"
/>
</ui-field-wrapper>
</template> </template>

View File

@ -0,0 +1,36 @@
<script lang="ts">
export type TUiFiledWrapperProps = {
label?: string
error?: string
disabled?: boolean
readonly?: boolean
description?: string
}
</script>
<script setup lang="ts">
// Props.
const props = defineProps<TUiFiledWrapperProps>()
</script>
<template>
<div
:class="{
'ui-field': true,
error: props.error,
disabled: props.disabled,
readonly: props.readonly
}"
>
<div class="ui-field-wrap">
<div v-if="props.label" class="label">{{ props.label }}</div>
<div class="input">
<slot />
</div>
</div>
<div v-if="props.description" class="description">
{{ props.description }}
</div>
<div v-if="props.error" class="message">{{ props.error }}</div>
</div>
</template>

View File

@ -1,16 +1,8 @@
<script setup lang="ts"> <script lang="ts">
import type { Ref } from 'vue' import type { TNotificationItem, IFormModel } from 'axp-ts'
import type { TNotificationItem, BaseFormModel } from 'axp-ts'
import { ref, computed, watch } from 'vue' export type TUiFormProps<T = any> = {
import { colors } from '../colors' modelValue?: IFormModel<T>
import UiBtn from './Btn.vue'
import UiAlert from './Alert.vue'
// Props.
const props = defineProps<{
modelValue?: BaseFormModel<any>
title?: string title?: string
noTitle?: boolean noTitle?: boolean
messages?: TNotificationItem[] messages?: TNotificationItem[]
@ -18,16 +10,30 @@ const props = defineProps<{
disabled?: boolean disabled?: boolean
load?: boolean load?: boolean
showAll?: boolean showAll?: boolean
fn?: (obj?: any) => Promise<any> fn?: (obj?: T) => Promise<T>
}>() }
// Emits. export type TUiFormEmits<T = any> = {
const emit = defineEmits<{ (e: 'submit', v?: IFormModel<T>): void
(e: 'submit', v?: BaseFormModel<any>): void (e: 'failedValid', v?: IFormModel<T>): void
(e: 'failedValid', v?: BaseFormModel<any>): void
(e: 'update:load', v: boolean): void (e: 'update:load', v: boolean): void
(e: 'fnCompleted', v?: any): void (e: 'fnCompleted', v?: any): void
}>() }
</script>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import UiBtn from './Btn.vue'
import UiAlert from './Alert.vue'
import { colors } from '../colors'
// Props.
const props = defineProps<TUiFormProps>()
// Emits.
const emit = defineEmits<TUiFormEmits>()
// Controls. // Controls.
const ctrls = computed(() => { const ctrls = computed(() => {
@ -35,7 +41,6 @@ const ctrls = computed(() => {
if (props.showAll) { if (props.showAll) {
return props.modelValue.ctrls return props.modelValue.ctrls
} else { } else {
// @ts-ignore
return props.modelValue.ctrls.filter(e => !e.hidden) return props.modelValue.ctrls.filter(e => !e.hidden)
} }
} }
@ -76,7 +81,6 @@ const submitHandler = async () => {
if (err.code && err.text) if (err.code && err.text)
if ( if (
props.modelValue && props.modelValue &&
// @ts-ignore
props.modelValue.ctrls.find(e => e.key === err.code) props.modelValue.ctrls.find(e => e.key === err.code)
) { ) {
props.modelValue.setValidError(err.code, err.text) props.modelValue.setValidError(err.code, err.text)
@ -118,6 +122,9 @@ const getColorMessage = (item: TNotificationItem) => {
/> />
</div> </div>
<div class="ui-form-body"> <div class="ui-form-body">
<slot v-if="$slots.pre" name="pre" />
<slot v-if="$slots.default" name="default" />
<template v-else>
<component <component
v-if="props.modelValue" v-if="props.modelValue"
v-for="ctrl in ctrls" v-for="ctrl in ctrls"
@ -127,9 +134,11 @@ const getColorMessage = (item: TNotificationItem) => {
v-model:error="props.modelValue._errors[ctrl.key]" v-model:error="props.modelValue._errors[ctrl.key]"
:readonly="ctrl.readonly" :readonly="ctrl.readonly"
:disabled="load || props.load || props.disabled || ctrl.disabled" :disabled="load || props.load || props.disabled || ctrl.disabled"
:class="'ui-field-' + ctrl.key" :description="ctrl.description"
:class="'ui-field-key-' + ctrl.key"
/> />
<slot v-if="$slots.default" name="default" /> </template>
<slot v-if="$slots.post" name="post" />
</div> </div>
<div v-if="!props.noActions" class="ui-form-actions"> <div v-if="!props.noActions" class="ui-form-actions">
<slot v-if="$slots.actions" name="actions" /> <slot v-if="$slots.actions" name="actions" />

View File

@ -1,19 +1,21 @@
<script setup lang="ts"> <script lang="ts">
import type { TUiIcon } from '../icons' import type { TUiIcon } from '../icons'
export type TUiIconProps = {
name: TUiIcon
fill?: boolean
}
</script>
<script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { icons } from '../icons' import { icons } from '../icons'
// Props. // Props.
const props = defineProps<{ const props = defineProps<TUiIconProps>()
name: TUiIcon
fill?: boolean
}>()
const iconName = computed(() => { // Etc.
// @ts-ignore const icon = computed(() => icons[props.name])
return icons[props.name]
})
</script> </script>
<template> <template>
@ -23,7 +25,7 @@ const iconName = computed(() => {
:fill="fill ? 'currentColor' : 'none'" :fill="fill ? 'currentColor' : 'none'"
stroke="currentColor" stroke="currentColor"
stroke-width="1.2" stroke-width="1.2"
v-html="iconName" v-html="icon"
/> />
</span> </span>
</template> </template>

View File

@ -1,15 +1,23 @@
<script lang="ts">
export type TUiIconVisibilityProps = {
modelValue?: boolean,
color?: boolean,
cursor?: boolean
}
export type TUiIconVisibilityEmits = {
(e: 'update:modelValue', v: boolean): void
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import UiIcon from './Icon.vue' import UiIcon from './Icon.vue'
// Props. // Props.
const props = defineProps<{ const props = defineProps<TUiIconVisibilityProps>()
modelValue?: boolean,
color?: boolean,
cursor?: boolean
}>()
// Emits. // Emits.
const emit = defineEmits<{ (e: 'update:modelValue', v: boolean): void }>() const emit = defineEmits<TUiIconVisibilityEmits>()
</script> </script>
<template> <template>

View File

@ -1,17 +1,25 @@
<script setup lang="ts"> <script lang="ts">
import UiFieldCheckbox from './FieldCheckbox.vue' export type TUiPickerDaysProps = {
// Props.
const props = defineProps<{
modelValue?: number[] modelValue?: number[]
label?: string label?: string
error?: string error?: string
readonly?: boolean readonly?: boolean
disabled?: boolean disabled?: boolean
}>() }
export type TUiPickerDaysEmits = {
(e: 'update:modelValue', v: number[]): void
}
</script>
<script setup lang="ts">
import UiFieldCheckbox from './FieldCheckbox.vue'
// Props.
const props = defineProps<TUiPickerDaysProps>()
// Emits. // Emits.
const emit = defineEmits<{ (e: 'update:modelValue', v: number[]): void }>() const emit = defineEmits<TUiPickerDaysEmits>()
// Init data. // Init data.
const days = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'] const days = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс']

View File

@ -1,17 +1,23 @@
<script lang="ts"> <script lang="ts">
export type TTheme = 'light' | 'dark' export type TUiThemeName = 'light' | 'dark'
export type TUiToggleThemeProps = {
modelValue?: TUiThemeName
}
export type TUiToggleThemeEmits = {
(e: 'update:modelValue', v: TUiThemeName): void
}
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import UiIcon from './Icon.vue' import UiIcon from './Icon.vue'
// Props. // Props.
const props = defineProps<{ const props = defineProps<TUiToggleThemeProps>()
modelValue?: TTheme
}>()
// Emits. // Emits.
const emit = defineEmits<{ (e: 'update:modelValue', v: TTheme): void }>() const emit = defineEmits<TUiToggleThemeEmits>()
// Handlers. // Handlers.
const clickHandler = ({}: PointerEvent) => const clickHandler = ({}: PointerEvent) =>

View File

@ -1,20 +1,65 @@
export { default as UiBtn } from './Btn.vue'
export { default as UiAlert } from './Alert.vue' export { default as UiAlert } from './Alert.vue'
export { default as UiField } from './Field.vue' export * from './Alert.vue'
export { default as UiFieldText } from './FieldText.vue'
export { default as UiFieldTextArea } from './FieldTextArea.vue' export { default as UiBtn } from './Btn.vue'
export { default as UiFieldPassword } from './FieldPassword.vue' export * from './Btn.vue'
export { default as UiFieldNumber } from './FieldNumber.vue'
export { default as UiFieldPhone } from './FieldPhone.vue'
export { default as UiFieldDate } from './FieldDate.vue'
export { default as UiFieldCheckbox } from './FieldCheckbox.vue'
export { default as UiFieldSelect } from './FieldSelect.vue'
export { default as UiFieldSelectGender } from './FieldSelectGender.vue'
export { default as UiPickerDays } from './PickerDays.vue'
export { default as UiIcon } from './Icon.vue'
export { default as UiIconVisibility } from './IconVisibility.vue'
export { default as UiForm } from './Form.vue'
export { default as UiCard } from './Card.vue' export { default as UiCard } from './Card.vue'
export { default as UiTable } from './Table.vue' export * from './Card.vue'
export { default as UiDialog } from './Dialog.vue' export { default as UiDialog } from './Dialog.vue'
export * from './Dialog.vue'
export { default as UiIcon } from './Icon.vue'
export * from './Icon.vue'
export { default as UiIconVisibility } from './IconVisibility.vue'
export * from './IconVisibility.vue'
export { default as UiTable } from './Table.vue'
export * from './Table.vue'
export { default as UiToggleTheme } from './ToggleTheme.vue' export { default as UiToggleTheme } from './ToggleTheme.vue'
export * from './ToggleTheme.vue'
export { default as UiFieldWrapper } from './FieldWrapper.vue'
export * from './FieldWrapper.vue'
export { default as UiFieldInput } from './FieldInput.vue'
export * from './FieldInput.vue'
export { default as UiFieldText } from './FieldText.vue'
export * from './FieldText.vue'
export { default as UiFieldNumber } from './FieldNumber.vue'
export * from './FieldNumber.vue'
export { default as UiFieldPassword } from './FieldPassword.vue'
export * from './FieldPassword.vue'
export { default as UiFieldPhone } from './FieldPhone.vue'
export * from './FieldPhone.vue'
export { default as UiFieldDate } from './FieldDate.vue'
export * from './FieldDate.vue'
export { default as UiFieldTextArea } from './FieldTextArea.vue'
export * from './FieldTextArea.vue'
export { default as UiFieldFile } from './FieldFile.vue'
export * from './FieldFile.vue'
export { default as UiFieldCheckbox } from './FieldCheckbox.vue'
export * from './FieldCheckbox.vue'
export { default as UiFieldSelect } from './FieldSelect.vue'
export * from './FieldSelect.vue'
export { default as UiFieldSelectGender } from './FieldSelectGender.vue'
export * from './FieldSelectGender.vue'
export { default as UiPickerDays } from './PickerDays.vue'
export * from './PickerDays.vue'
export { default as UiForm } from './Form.vue'
export * from './Form.vue'

View File

@ -83,4 +83,4 @@ export const icons = {
` `
} }
export type TUiIcon = keyof typeof icons export type TIcon = keyof typeof icons

View File

@ -1,3 +1,7 @@
export * from './components' export * from './components'
export * from './icons' export * from './icons'
export * from './colors' export * from './colors'
import './style/tailwind.sass'
import './style/common.sass'
import './style/theme.sass'

3
src/main.ts Normal file
View File

@ -0,0 +1,3 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

View File

@ -27,28 +27,31 @@
// Ui. // Ui.
.ui .ui
&-btn,
&-field .input
@apply outline-none
@apply h-[38px] text-base
&-btn &-btn
@apply px-4 border @apply h-[38px] text-base
@apply flex justify-center items-center @apply flex justify-center items-center
@apply px-2 border
> *:not(:last-child) > *:not(:last-child)
@apply mr-1 @apply mr-2
> .ui-icon:first-child .label
@apply ml-[-5px] @apply text-base font-normal
&-field &-field
&-wrap
.label .label
@apply mb-1 @apply mb-1
.input .input
@apply block w-full border px-2 py-2 @apply h-[38px] text-base
@apply block w-full border
> *
@apply w-full h-full px-2 py-2 outline-none
.description,
.message .message
@apply mt-1 text-sm @apply w-full pl-1 mt-1 text-sm
&-checkbox &-checkbox
@apply mb-4
.ui-field-wrap
@apply flex items-center justify-start @apply flex items-center justify-start
.label .label
@apply mb-0 @apply mb-0
@ -59,7 +62,10 @@
&-textarea &-textarea
.input .input
@apply min-h-[80px] @apply h-auto
> *
@apply min-h-[94px]
@apply leading-5
&-alert &-alert
@apply relative border @apply relative border
@ -134,8 +140,8 @@
@apply flex items-center justify-between py-2 px-3 @apply flex items-center justify-between py-2 px-3
> .title > .title
@apply text-lg font-normal @apply text-lg font-normal
.ui-icon .ui-icon-close
@apply w-[30px] h-[30px] p-0 border-0 @apply w-[30px] h-[30px] p-0 border-0 cursor-pointer
&:hover &:hover
@apply text-dark @apply text-dark
&-content &-content

3
src/style/tailwind.sass Normal file
View File

@ -0,0 +1,3 @@
@tailwind base
@tailwind components
@tailwind utilities

View File

@ -33,16 +33,19 @@
@apply text-white bg-light border-light @apply text-white bg-light border-light
&-field &-field
.input .input > *
@apply bg-white dark:bg-white/10 text-dark dark:text-white @apply bg-white dark:bg-white/10 text-dark dark:text-white
.description
@apply text-dark/50
&.error &.error
.label, .label,
.input, .input,
.message .message
@apply text-error border-error @apply text-error border-error
&:disabled &.disabled
@apply text-dark/60
.input .input
@apply bg-light/10 text-dark/60 @apply bg-light/10
&-btn &-btn
@apply outline-none @apply outline-none
&:hover:not(:active) &:hover:not(:active)
@ -51,5 +54,6 @@
&-btn, &-btn,
&-alert, &-alert,
&-field .input, &-field .input,
&-field .input > *,
&-card &-card
@apply rounded @apply rounded

View File

@ -1,7 +1,7 @@
import type { Config } from 'tailwindcss' import type { Config } from 'tailwindcss'
const config: Config = { const config: Config = {
content: ['./src/components/**/*.vue'], content: ['./src/App.vue', './src/components/**/*.vue'],
darkMode: 'class', darkMode: 'class',
theme: { theme: {
extend: { extend: {
@ -12,7 +12,7 @@ const config: Config = {
dark: '#363636', dark: '#363636',
light: '#f1f1f1', light: '#f1f1f1',
info: '#2196f3', info: '#00b8ff',
warning: '#ff9800', warning: '#ff9800',
success: '#2dd633', success: '#2dd633',
error: '#f00000' error: '#f00000'

30
vite.config.ts Normal file
View File

@ -0,0 +1,30 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import dts from 'vite-plugin-dts'
import postcss from 'postcss'
import autoprefixer from 'autoprefixer'
import tailwindcss from 'tailwindcss'
export default defineConfig({
plugins: [vue(), dts()],
css: {
postcss: postcss([autoprefixer(), tailwindcss()])
},
build: {
lib: {
entry: 'src/index.ts',
name: 'axp-ui',
fileName: 'index'
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue'
}
}
}
}
})