init
This commit is contained in:
commit
2ddefb9be3
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
dist
|
||||
node_modules
|
||||
package-lock.json
|
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"semi": false,
|
||||
"trailingComma": "none",
|
||||
"arrowParens": "avoid",
|
||||
"printWidth": 79
|
||||
}
|
48
package.json
Normal file
48
package.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "axp-ui",
|
||||
"descriiption": "My helper ui lib",
|
||||
"version": "1.5.16",
|
||||
"homepage": "https://antoxahub.ru/antoxa/axp-ui",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://antoxahub.ru/antoxa/axp-ui.git"
|
||||
},
|
||||
"module": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./styles/": [
|
||||
"./src/css/"
|
||||
],
|
||||
"./tailwind.config": "./tailwind.config.ts"
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"tsconfig.json",
|
||||
"tailwind.config.ts",
|
||||
"src/css"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rollup -c --configPlugin rollup-plugin-typescript2",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"axp-ts": "^1.9.6",
|
||||
"rollup-plugin-typescript": "^1.0.1",
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.25",
|
||||
"prettier": "^2.8.8",
|
||||
"rollup": "^3.26.2",
|
||||
"rollup-plugin-sass": "^1.12.19",
|
||||
"rollup-plugin-typescript2": "^0.34.1",
|
||||
"rollup-plugin-vue": "^6.0.0",
|
||||
"sass": "^1.63.6",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tslib": "^2.6.0"
|
||||
}
|
||||
}
|
15
rollup.config.ts
Normal file
15
rollup.config.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { defineConfig } from 'rollup'
|
||||
|
||||
import ts 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: [ts(), vue(), sass()],
|
||||
external: ['vue', 'axp-ts'],
|
||||
})
|
13
src/components/Alert.vue
Normal file
13
src/components/Alert.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
type?: string,
|
||||
value?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ui-alert" :class="props.type">
|
||||
<span v-if="props.value">{{props.value}}</span>
|
||||
<slot v-if="$slots.default" name="default" />
|
||||
</div>
|
||||
</template>
|
27
src/components/Btn.vue
Normal file
27
src/components/Btn.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import UiIcon from './Icon.vue'
|
||||
|
||||
// Props.
|
||||
const props = defineProps<{
|
||||
label?: string
|
||||
type?: 'button' | 'submit'
|
||||
color?: string
|
||||
icon?: any
|
||||
}>()
|
||||
|
||||
// Etc.
|
||||
const cssClass = computed(() => {
|
||||
let res = ''
|
||||
if (props.color) res = props.color
|
||||
return res
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="ui-btn" :class="cssClass" :type="props.type">
|
||||
<ui-icon v-if="props.icon" :name="props.icon" />
|
||||
<div v-if="props.label" class="label">{{ props.label }}</div>
|
||||
<slot name="default" />
|
||||
</button>
|
||||
</template>
|
84
src/components/Field.vue
Normal file
84
src/components/Field.vue
Normal file
@ -0,0 +1,84 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
modelValue?: any
|
||||
label?: string
|
||||
type?: 'text' | 'number' | 'date' | 'password' | 'checkbox'
|
||||
error?: string
|
||||
readonly?: boolean
|
||||
disabled?: boolean
|
||||
tag?: 'input' | 'textarea' | 'select'
|
||||
}>()
|
||||
|
||||
// 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'"
|
||||
:type="props.type"
|
||||
:value="props.modelValue"
|
||||
:reatonly="props.readonly"
|
||||
:disabled="props.disabled"
|
||||
@input="inputHandler"
|
||||
class="input"
|
||||
/>
|
||||
<textarea
|
||||
v-if="tag === 'textarea'"
|
||||
:value="props.modelValue"
|
||||
:reatonly="props.readonly"
|
||||
:disabled="props.disabled"
|
||||
@input="inputHandler"
|
||||
class="input"
|
||||
/>
|
||||
<select
|
||||
v-if="tag === 'select'"
|
||||
:value="props.modelValue"
|
||||
:reatonly="props.readonly"
|
||||
:disabled="props.disabled"
|
||||
@input="inputHandler"
|
||||
class="input"
|
||||
>
|
||||
<slot />
|
||||
</select>
|
||||
<div v-if="props.error" class="message">{{ props.error }}</div>
|
||||
</div>
|
||||
</template>
|
26
src/components/FieldCheckbox.vue
Normal file
26
src/components/FieldCheckbox.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import UiField from './Field.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
// Props.
|
||||
const props = defineProps<{
|
||||
modelValue?: boolean
|
||||
}>()
|
||||
|
||||
// Emits.
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', v?: boolean): void }>()
|
||||
|
||||
// Value.
|
||||
const valueStr = computed({
|
||||
get: () => {
|
||||
if (props.modelValue) return 'on'
|
||||
},
|
||||
set: val => {
|
||||
if (val !== undefined) emit('update:modelValue', !props.modelValue)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ui-field type="checkbox" class="ui-field-checkbox" v-model="valueStr" />
|
||||
</template>
|
32
src/components/FieldDate.vue
Normal file
32
src/components/FieldDate.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { cFieldsSchema, getDateCommonFormat } from 'axp-ts'
|
||||
|
||||
import UiField from './Field.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: Date
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', v?: Date): void }>()
|
||||
|
||||
const valueStr = computed({
|
||||
get: () => {
|
||||
try {
|
||||
const value = cFieldsSchema.shape.date.parse(props.modelValue)
|
||||
const format = getDateCommonFormat(value)
|
||||
return format
|
||||
} catch (e) {}
|
||||
},
|
||||
set: (val) => {
|
||||
try {
|
||||
const value = cFieldsSchema.shape.date.parse(val)
|
||||
emit('update:modelValue', value)
|
||||
} catch (e) {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ui-field type="date" class="ui-field-date" v-model="valueStr" />
|
||||
</template>
|
7
src/components/FieldNumber.vue
Normal file
7
src/components/FieldNumber.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import UiField from './Field.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ui-field type="number" class="ui-field-number" />
|
||||
</template>
|
7
src/components/FieldPassword.vue
Normal file
7
src/components/FieldPassword.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import UiField from './Field.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ui-field type="password" class="ui-field-password" />
|
||||
</template>
|
22
src/components/FieldPhone.vue
Normal file
22
src/components/FieldPhone.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { getPhoneNumberFormat, getPhoneNumberValue } from 'axp-ts'
|
||||
|
||||
import UiField from './Field.vue'
|
||||
|
||||
// Props.
|
||||
const props = defineProps<{ modelValue?: number }>()
|
||||
|
||||
// Emits.
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', v?: number): void }>()
|
||||
|
||||
// Value string.
|
||||
const valueStr = computed({
|
||||
get: () => getPhoneNumberFormat(props.modelValue),
|
||||
set: val => emit('update:modelValue', getPhoneNumberValue(val))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ui-field v-model="valueStr" class="ui-field-phone" />
|
||||
</template>
|
25
src/components/FieldSelect.vue
Normal file
25
src/components/FieldSelect.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import UiField from './Field.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: string | number,
|
||||
options: { value: string, text: string }[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', v?: any): void }>()
|
||||
|
||||
const displayValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ui-field tag="select" class="ui-field-select" v-model="displayValue">
|
||||
<option v-for="opt in props.options" :value="opt.value">
|
||||
{{opt.text}}
|
||||
</option>
|
||||
</ui-field>
|
||||
</template>
|
19
src/components/FieldSelectGender.vue
Normal file
19
src/components/FieldSelectGender.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { GenderEnum } from 'axp-ts'
|
||||
import UiFieldSelect from './FieldSelect.vue'
|
||||
|
||||
const options: any[] = [
|
||||
{
|
||||
text: 'Мужской',
|
||||
value: GenderEnum.man
|
||||
},
|
||||
{
|
||||
text: 'Женский',
|
||||
value: GenderEnum.woman
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ui-field-select :options="options" class="ui-field-select-gender" />
|
||||
</template>
|
7
src/components/FieldText.vue
Normal file
7
src/components/FieldText.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import UiField from './Field.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ui-field class="ui-field-text" />
|
||||
</template>
|
7
src/components/FieldTextArea.vue
Normal file
7
src/components/FieldTextArea.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import UiField from './Field.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ui-field tag="textarea" class="ui-field-textarea" />
|
||||
</template>
|
132
src/components/Form.vue
Normal file
132
src/components/Form.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
import type { TNotificationItem, BaseFormModel } from 'axp-ts'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
import UiBtn from './Btn.vue'
|
||||
|
||||
// Props.
|
||||
const props = defineProps<{
|
||||
modelValue: BaseFormModel<any>
|
||||
title?: string
|
||||
noTitle?: boolean
|
||||
messages?: TNotificationItem[]
|
||||
noActions?: boolean
|
||||
disabled?: boolean
|
||||
load?: boolean
|
||||
showAll?: boolean
|
||||
fn?: (obj?: any) => Promise<any>
|
||||
}>()
|
||||
|
||||
// Emits.
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit', v?: BaseFormModel<any>): void
|
||||
(e: 'failedValid', v?: BaseFormModel<any>): void
|
||||
(e: 'update:load', v: boolean): void
|
||||
(e: 'fnCompleted', v?: any): void
|
||||
}>()
|
||||
|
||||
// Controls.
|
||||
const ctrls = computed(() => {
|
||||
if (props.modelValue) {
|
||||
if (props.showAll) {
|
||||
return props.modelValue.ctrls
|
||||
} else {
|
||||
// @ts-ignore
|
||||
return props.modelValue.ctrls.filter(e => !e.hidden)
|
||||
}
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
// Content.
|
||||
const title = computed(() => {
|
||||
if (!props.noTitle) return props.title || props.modelValue?.title
|
||||
})
|
||||
|
||||
// Init data.
|
||||
const load = ref(false)
|
||||
watch(load, val => emit('update:load', val))
|
||||
|
||||
const messages: Ref<TNotificationItem[]> = ref(props.messages || [])
|
||||
watch(() => props.messages, (val) => messages.value = val || [])
|
||||
|
||||
// Handlers.
|
||||
const submitHandler = async () => {
|
||||
if (props.modelValue) {
|
||||
if (props.modelValue.isValid()) {
|
||||
emit('submit', props.modelValue)
|
||||
|
||||
if (props.fn) {
|
||||
load.value = true
|
||||
messages.value = []
|
||||
|
||||
try {
|
||||
const dR = await props.fn(props.modelValue.obj)
|
||||
emit('fnCompleted', dR)
|
||||
|
||||
if (dR?.errors && Array.isArray(dR.errors)) {
|
||||
for (const err of dR.errors) {
|
||||
if (err.code && err.text)
|
||||
if (
|
||||
props.modelValue &&
|
||||
// @ts-ignore
|
||||
props.modelValue.ctrls.find(e => e.key === err.code)
|
||||
) {
|
||||
props.modelValue.setValidError(err.code, err.text)
|
||||
} else {
|
||||
messages.value.push({ code: 'error', text: err.text })
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex: any) {
|
||||
messages.value.push({ code: 'error', text: ex.message })
|
||||
}
|
||||
|
||||
load.value = false
|
||||
}
|
||||
} else {
|
||||
emit('failedValid', props.modelValue)
|
||||
}
|
||||
} else {
|
||||
emit('submit')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="submitHandler" class="ui-form">
|
||||
<div v-if="title" class="ui-form-header">
|
||||
<h3 class="ui-form-title">{{ title }}</h3>
|
||||
</div>
|
||||
<div v-if="messages.length" class="ui-form-messages">
|
||||
<div v-for="message in messages" :class="'text-' + message.code">
|
||||
{{ message.text }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui-form-body">
|
||||
<component
|
||||
v-if="props.modelValue"
|
||||
v-for="ctrl in ctrls"
|
||||
:is="ctrl.component"
|
||||
:label="ctrl.label"
|
||||
v-model="props.modelValue.obj[ctrl.key]"
|
||||
v-model:error="props.modelValue._errors[ctrl.key]"
|
||||
:readonly="ctrl.readonly"
|
||||
:disabled="load || props.load || props.disabled || ctrl.disabled"
|
||||
:class="'ui-field-' + ctrl.key"
|
||||
/>
|
||||
<slot v-if="$slots.default" name="default" />
|
||||
</div>
|
||||
<div v-if="!props.noActions" class="ui-form-actions">
|
||||
<slot v-if="$slots.actions" name="actions" />
|
||||
<ui-btn
|
||||
v-else
|
||||
label="Отправить"
|
||||
type="submit"
|
||||
color="primary"
|
||||
:disabled="load || props.disabled"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
104
src/components/Icon.vue
Normal file
104
src/components/Icon.vue
Normal file
@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
export const icons = {
|
||||
account: `
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
<path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" />
|
||||
`,
|
||||
doc: `
|
||||
<path d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"/>
|
||||
`,
|
||||
vk: `
|
||||
<path d="M21.579 6.855c.14-.465 0-.806-.662-.806h-2.193c-.558 0-.813.295-.953.619 0 0-1.115 2.719-2.695 4.482-.51.513-.743.675-1.021.675-.139 0-.341-.162-.341-.627V6.855c0-.558-.161-.806-.626-.806H9.642c-.348 0-.558.258-.558.504 0 .528.79.65.871 2.138v3.228c0 .707-.127.836-.407.836-.743 0-2.551-2.729-3.624-5.853-.209-.607-.42-.852-.98-.852H2.752c-.627 0-.752.295-.752.619 0 .582.743 3.462 3.461 7.271 1.812 2.601 4.363 4.011 6.687 4.011 1.393 0 1.565-.313 1.565-.853v-1.966c0-.626.133-.752.574-.752.324 0 .882.164 2.183 1.417 1.486 1.486 1.732 2.153 2.567 2.153h2.192c.626 0 .939-.313.759-.931-.197-.615-.907-1.51-1.849-2.569-.512-.604-1.277-1.254-1.51-1.579-.325-.419-.231-.604 0-.976.001.001 2.672-3.761 2.95-5.04z"/>
|
||||
`,
|
||||
youtube: `
|
||||
<path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z" />
|
||||
<polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02" />
|
||||
`,
|
||||
video: `
|
||||
<polygon points="23 7 16 12 23 17 23 7" />
|
||||
<rect x="1" y="5" width="15" height="14" rx="2" ry="2" />
|
||||
`,
|
||||
yandex: `
|
||||
<path d="m10 24v-7.786l-5.2-13.964h2.616l3.834 10.767 4.41-13.018h2.405l-5.658 16.303v7.697z"/>
|
||||
`,
|
||||
menu: `
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<line x1="4" y1="6" x2="20" y2="6" />
|
||||
<line x1="4" y1="12" x2="20" y2="12" />
|
||||
<line x1="4" y1="18" x2="20" y2="18" />
|
||||
`,
|
||||
close: `
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
`,
|
||||
'arrow-down': `
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<polyline points="6 9 12 15 18 9" />
|
||||
`,
|
||||
car: `
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="7" cy="17" r="2" />
|
||||
<circle cx="17" cy="17" r="2" />
|
||||
<path d="M5 17h-2v-6l2-5h9l4 5h1a2 2 0 0 1 2 2v4h-2m-4 0h-6m-6 -6h15m-6 0v-5" />
|
||||
`,
|
||||
review: `
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||
`,
|
||||
phone: `
|
||||
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" />
|
||||
`,
|
||||
circle: `
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
`,
|
||||
dzen: `
|
||||
<path d="M 11.659994,0.01953125 C 5.2952996,0.20519926 0.20563242,5.2947457 0.01953125,11.659552 4.8820049,11.598767 7.6163326,11.265345 9.4411321,9.4406121 11.265931,7.6159232 11.59921,4.8819566 11.659994,0.01953125 Z m 0.719931,0 c 0.06078,4.86242535 0.393207,7.59634815 2.218006,9.42108085 1.824686,1.8245759 4.559692,2.1581249 9.4216,2.2189399 C 23.83343,5.2948697 18.744448,0.20538037 12.379925,0.01953125 Z M 0.01953125,12.37951 C 0.20581298,18.744149 5.2954188,23.833867 11.659994,24.019531 11.59921,19.156885 11.265931,16.422327 9.4411321,14.597594 7.6163326,12.772905 4.8820049,12.440295 0.01953125,12.37951 Z m 23.99999975,0 c -4.861908,0.06081 -7.596914,0.393508 -9.4216,2.218084 -1.8248,1.824733 -2.157222,4.559291 -2.218006,9.421937 6.364403,-0.185846 11.453324,-5.275505 11.639606,-11.640021 z" />
|
||||
`,
|
||||
instagram: `
|
||||
<path d="m 18.374816,4.4184098 c -0.80327,0 -1.405723,0.6025075 -1.405723,1.4058562 0,0.8033487 0.602453,1.4058562 1.405723,1.4058562 0.803271,0 1.405724,-0.6025075 1.405724,-1.4058562 0,-0.8033487 -0.602453,-1.4058562 -1.405724,-1.4058562 z" />
|
||||
<path d="m 12.049062,6.2259403 c -3.3134909,0 -5.9241249,2.7112998 -5.9241249,5.9246867 0,3.213387 2.7110426,5.924686 5.9241249,5.924686 3.213082,0 5.924119,-2.711299 5.924119,-5.924686 0,-3.2133869 -2.610629,-5.9246867 -5.924119,-5.9246867 z m 0,9.7405887 c -2.1085854,0 -3.815535,-1.707118 -3.815535,-3.815902 0,-2.108784 1.7069496,-3.8159024 3.815535,-3.8159024 2.108585,0 3.815534,1.7071184 3.815534,3.8159024 0,2.108784 -1.706949,3.815902 -3.815534,3.815902 z" />
|
||||
<path d="M 16.868685,0 H 7.3298474 C 3.2130851,0 0,3.2133868 0,7.2301222 V 16.769878 C 0,20.786613 3.2130851,24 7.2294388,24 h 9.5388362 c 4.016353,0 7.229434,-3.213387 7.229434,-7.230122 V 7.2301222 C 24.098119,3.2133868 20.885037,0 16.868685,0 Z m 4.92003,16.87029 c 0,2.7113 -2.208993,5.020926 -5.02044,5.020926 H 7.2294388 c -2.711045,0 -5.0204482,-2.209205 -5.0204482,-5.020926 V 7.3305428 c 0,-2.7112998 2.2089985,-5.0209173 5.0204482,-5.0209173 h 9.5388362 c 2.711038,0 5.02044,2.2092048 5.02044,5.0209173 z" />
|
||||
`,
|
||||
moon: `
|
||||
<path d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||
`,
|
||||
sun: `
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
<line x1="12" y1="1" x2="12" y2="3" />
|
||||
<line x1="12" y1="21" x2="12" y2="23" />
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
||||
<line x1="1" y1="12" x2="3" y2="12" />
|
||||
<line x1="21" y1="12" x2="23" y2="12" />
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
||||
`
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
// Props.
|
||||
const props = defineProps<{
|
||||
name: keyof typeof icons
|
||||
fill?: boolean
|
||||
}>()
|
||||
|
||||
const iconName = computed(() => {
|
||||
// @ts-ignore
|
||||
return icons[props.name]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="ui-icon">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
:fill="fill ? 'currentColor' : 'none'"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.2"
|
||||
v-html="iconName"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
14
src/components/index.ts
Normal file
14
src/components/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export { default as UiField } from './Field.vue'
|
||||
export { default as UiFieldText } from './FieldText.vue'
|
||||
export { default as UiFieldPassword } from './FieldPassword.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 UiFieldTextArea } from './FieldTextArea.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 UiBtn } from './Btn.vue'
|
||||
export { default as UiAlert } from './Alert.vue'
|
||||
export { default as UiIcon } from './Icon.vue'
|
||||
export { default as UiForm } from './Form.vue'
|
64
src/css/common.sass
Normal file
64
src/css/common.sass
Normal file
@ -0,0 +1,64 @@
|
||||
body
|
||||
@apply text-dark
|
||||
*
|
||||
@apply text-base
|
||||
.title
|
||||
@apply mb-4
|
||||
h1
|
||||
@apply text-xl md:text-2xl font-bold
|
||||
h2
|
||||
@apply text-lg md:text-xl font-bold
|
||||
h3
|
||||
@apply text-base md:text-lg font-bold
|
||||
h4
|
||||
@apply text-sm md:text-base font-bold
|
||||
|
||||
.ui
|
||||
&-btn,
|
||||
&-field .input
|
||||
@apply h-[38px] text-base
|
||||
|
||||
&-btn
|
||||
@apply px-4 border
|
||||
@apply flex justify-center items-center
|
||||
> *:not(:last-child)
|
||||
@apply mr-1
|
||||
> .ui-icon:first-child
|
||||
@apply ml-[-5px]
|
||||
|
||||
&-field
|
||||
.label
|
||||
@apply mb-1
|
||||
.input
|
||||
@apply block w-full border px-2 py-2
|
||||
outline: none
|
||||
.message
|
||||
@apply mt-1 text-sm
|
||||
|
||||
&-checkbox
|
||||
@apply flex items-center justify-start
|
||||
.input
|
||||
@apply w-max order-first mr-4
|
||||
@apply w-[24px] h-[24px] border rounded
|
||||
|
||||
&-textarea
|
||||
.input
|
||||
@apply min-h-[80px]
|
||||
|
||||
&-icon
|
||||
@apply block w-[24px]
|
||||
|
||||
&-form
|
||||
&-header
|
||||
@apply pb-4
|
||||
&-messages
|
||||
@apply pb-4
|
||||
> *:not(:last-child)
|
||||
@apply mb-2
|
||||
&-body
|
||||
> *:not(:last-child)
|
||||
@apply mb-4
|
||||
&-actions
|
||||
@apply flex items-center pt-4
|
||||
> *:not(:last-child)
|
||||
@apply mr-2
|
30
src/css/theme.sass
Normal file
30
src/css/theme.sass
Normal file
@ -0,0 +1,30 @@
|
||||
.ui
|
||||
&-field
|
||||
&.disabled
|
||||
.input
|
||||
@apply bg-light/10 text-dark/60
|
||||
&.error
|
||||
.label,
|
||||
.input,
|
||||
.message
|
||||
@apply text-error border-error
|
||||
&-btn
|
||||
&.primary
|
||||
@apply bg-primary text-white
|
||||
&.accent
|
||||
@apply bg-accent text-white
|
||||
&.dark
|
||||
@apply bg-dark text-white
|
||||
&.light
|
||||
@apply bg-light text-white
|
||||
&.error
|
||||
@apply bg-error text-white
|
||||
&.success
|
||||
@apply bg-success text-white
|
||||
&:disabled
|
||||
@apply text-white bg-light border-light
|
||||
&-btn,
|
||||
&-field .input
|
||||
@apply rounded
|
||||
&-field .input
|
||||
@apply bg-white
|
1
src/index.ts
Normal file
1
src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './components'
|
5
src/types/vueshim.d.ts
vendored
Normal file
5
src/types/vueshim.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
31
tailwind.config.ts
Normal file
31
tailwind.config.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
const config: Config = {
|
||||
content: ['./src/components/**/*.vue'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#3c87a4',
|
||||
accent: '#f00000',
|
||||
dark: '#363636',
|
||||
light: '#cdcdcd',
|
||||
error: '#f00000',
|
||||
success: '#07b859'
|
||||
},
|
||||
container: {
|
||||
center: true,
|
||||
padding: '1rem'
|
||||
},
|
||||
fontSize: {
|
||||
sm: ['.8rem', '1'],
|
||||
base: ['1rem', '1'],
|
||||
lg: ['1.3rem', '1'],
|
||||
xl: ['1.8rem', '1'],
|
||||
'2xl': ['2.2rem', '1']
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: []
|
||||
}
|
||||
|
||||
export default config
|
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
|
||||
"strict": true,
|
||||
"moduleResolution": "Node",
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"useDefineForClassFields": true,
|
||||
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user