This commit is contained in:
2025-10-31 19:10:01 +07:00
commit e54eb70c86
50 changed files with 11579 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
<script lang="ts" setup="">
import dayjs from "dayjs";
import ru from 'dayjs/locale/ru'
import type {Material} from "~/stores/materials/types.materials";
import IconCalendar from "~/components/icons/iconCalendar.vue";
dayjs.locale(ru);
interface Props {
item: Material
}
const props = defineProps<Props>()
const date = computed(() => dayjs(props.item.datetime).format('D MMMM'))
</script>
<template>
<div class="item-container">
<div class="date-container">
<IconCalendar class="icon" />
<span class="date">{{date}}</span>
</div>
<div class="item-content" >
<h2 class="title">{{item.title}}</h2>
<p class="description">{{item.short_description}}</p>
</div>
</div>
</template>
<style scoped lang="scss">
.item-container {
border-radius: 24px;
padding: 32px 24px;
transition: all .3s ease;
box-shadow: unset;
background: #fff;
box-sizing: border-box;
@include mixins.square(276px);
&:hover {
cursor: pointer;
box-shadow: -2px 5px 13px 0px #0000000A, -9px 21px 23px 0px #0000000A, -21px 48px 31px 0px #00000005, -37px 85px 37px 0px #00000003, -57px 133px 40px 0px #00000000;
}
}
.date-container {
display: flex;
gap: 8px;
align-items: center;
color: var(--text-light-gray);
}
.icon {
@include mixins.square(20px);
}
.date {
font-family: FuturaPT, monospace;
font-weight: 400;
font-size: 16px;
line-height: 100%;
}
.item-content {
.title, .description {
padding-top: 20px;
margin: 0;
}
.title {
font-family: SourceSans3, monospace;
font-weight: 600;
font-size: 20px;
line-height: 120%;
color: var(--text-black)
}
.description {
font-family: FuturaPT, monospace;
font-weight: 400;
font-size: 16px;
line-height: 120%;
color: var(--text-gray);
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
}
@media screen and (max-width: 768px) {
.item-container {
max-width: unset;
min-width: unset;
width: unset;
height: unset;
min-height: unset;
}
}
</style>

View File

@@ -0,0 +1,364 @@
<template>
<div v-if="editor" class="container tiptap">
<div class="control-group">
<div class="button-group">
<button
:class="{ 'is-active': editor.isActive('paragraph') }"
@click="editor.chain().focus().setParagraph().run()"
>
Paragraph
</button>
<button
:disabled="hasH1"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
@click="toggleH1"
>
H1
</button>
<button
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
>
H2
</button>
<button
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
>
H3
</button>
<button
:class="{ 'is-active': editor.isActive('blockquote') }"
@click="editor.chain().focus().toggleBlockquote().run()"
>
Blockquote
</button>
</div>
</div>
<EditorContent :editor="editor" />
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, computed, inject, ref, Ref } from 'vue'
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import { ListItem } from '@tiptap/extension-list'
import { Placeholder } from '@tiptap/extensions'
import { Color, TextStyle } from '@tiptap/extension-text-style'
import { EDITOR_KEY } from '~/shared/constants'
const TITLE_MAX = 80
const DESCRIPTION_MAX = 255
const editor = inject<Ref<Editor | null>>(EDITOR_KEY)
const titleLength = ref(0)
const descriptionLength = ref(0)
const hasH1 = computed(() => {
if (!editor?.value) return false
let found = false
editor.value.state.doc.descendants((node) => {
if (node.type.name === 'heading' && node.attrs.level === 1) {
found = true
return false
}
return true
})
return found
})
// Получить текст H1
function getH1Text(): string {
if (!editor?.value) return ''
let h1Text = ''
editor.value.state.doc.descendants((node) => {
if (node.type.name === 'heading' && node.attrs.level === 1) {
h1Text = node.textContent
return false
}
return true
})
return h1Text
}
// Получить текст первого P
function getFirstPText(): string {
if (!editor?.value) return ''
let firstPText = ''
let foundP = false
editor.value.state.doc.descendants((node) => {
if (node.type.name === 'paragraph' && !foundP) {
firstPText = node.textContent
foundP = true
return false
}
return true
})
return firstPText
}
// Обновить счетчики
function updateCharCount() {
titleLength.value = getH1Text().length
descriptionLength.value = getFirstPText().length
}
// Функция для переключения H1
function toggleH1() {
if (!editor?.value) return
if (!hasH1.value) {
editor.value.chain().focus().toggleHeading({ level: 1 }).run()
} else {
return
}
}
// Получить валидные данные
function getValidatedContent() {
const titleText = getH1Text()
const descriptionText = getFirstPText()
return {
title: titleText,
short_description: descriptionText,
content: editor?.value?.getJSON()
}
}
defineExpose({
getValidatedContent
})
onMounted(() => {
if (!editor) return
editor.value = new Editor({
extensions: [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
StarterKit,
Placeholder.configure({
emptyNodeClass: 'my-custom-is-empty-class',
showOnlyWhenEditable: false,
showOnlyCurrent: false,
placeholder: ({ node, editor, pos }) => {
if (node.type.name === 'heading') {
return 'Введите заголовок'
}
if (node.type.name === 'paragraph') {
let paragraphIndex = 0
let foundPos = null
editor.state.doc.descendants((child, childPos) => {
if (child.type.name === 'paragraph') {
if (childPos === pos) {
foundPos = paragraphIndex
return false
}
paragraphIndex++
}
return true
})
if (foundPos === 0) {
return 'Введите краткое описание...'
}
return 'Введите текст...'
}
return ''
}
}),
],
content: `
<h1></h1>
<p></p>
<p></p>
`,
onUpdate: ({ editor }) => {
updateCharCount()
let paragraphIndex = 0
let h1Found = false
editor.state.doc.descendants((node, pos) => {
// Ограничиваем H1
if (node.type.name === 'heading' && node.attrs.level === 1) {
const text = node.textContent
if (text.length > TITLE_MAX) {
editor.commands.setTextSelection({
from: pos + TITLE_MAX + 1,
to: pos + text.length + 1
})
editor.commands.deleteSelection()
}
h1Found = true
return
}
// Ограничиваем только первый P
if (node.type.name === 'paragraph') {
if (paragraphIndex === 0) {
const text = node.textContent
if (text.length > DESCRIPTION_MAX) {
editor.commands.setTextSelection({
from: pos + DESCRIPTION_MAX + 1,
to: pos + text.length + 1
})
editor.commands.deleteSelection()
}
}
paragraphIndex++
}
})
}
})
updateCharCount()
})
onBeforeUnmount(() => {
if (!editor?.value) return
editor.value?.destroy()
})
</script>
<style lang="scss" scoped>
.tiptap {
:first-child {
margin-top: 0;
border-radius: 32px;
}
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-family: SourceSans3, monospace;
font-size: 24px;
font-weight: 600;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
.tiptap p.is-empty::before {
color: #adb5bd;
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}
}
.control-group {
margin-bottom: 1rem;
.button-group {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
button {
padding: 0.5rem 1rem;
border: 1px solid var(--gray-3, #ddd);
border-radius: 4px;
background-color: white;
cursor: pointer;
transition: all 0.2s;
&:hover:not(:disabled) {
background-color: var(--gray-1, #f5f5f5);
}
&.is-active {
background-color: var(--blue-light, #e3f2fd);
border-color: var(--blue, #1976d2);
color: var(--blue, #1976d2);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
}
}
</style>

View File

@@ -0,0 +1,117 @@
<script lang="ts" setup="">
import IconLogo from "~/components/icons/iconLogo.vue";
import {Button} from "~/components/ui/Buttons";
import {ButtonKind} from "~/components/ui/Buttons/constants.buttons";
import {EDITOR_KEY} from "~/shared/constants";
import type {Editor} from "@tiptap/vue-3";
import type {MaterialSendDto} from "~/stores/materials/types.materials";
import dayjs from "dayjs";
import {useMaterialsStore} from "~/stores/materials/useMaterialsStore";
const router = useRouter()
const route = useRoute()
const editor = inject<Ref<Editor | null>>(EDITOR_KEY)
const isLoadingSave = ref(false);
const materialsStore = useMaterialsStore()
const isntMaterialCreatePage = computed(() => route.name !== 'materials-create')
const getButtonKind = computed(() => {
if (route.name === 'materials-create') return ButtonKind.Send
return ButtonKind.CreateMaterial
})
const onClick = () => {
if (isntMaterialCreatePage.value) {
router.push({name: 'materials-create'})
return;
}
prepareEditorData()
}
const prepareEditorData = async() => {
const json = editor?.value?.getJSON()
const html = editor?.value?.getHTML()
if (!json || !html) return;
const title = json.content?.find(el => el.type === 'heading' && el.attrs?.level === 1)?.content?.[0]?.text
const shortDescription = json.content?.find(el => el.type === 'paragraph')?.content?.[0]?.text
const sendData: MaterialSendDto = {
title,
short_description: shortDescription,
datetime: dayjs().format('YYYY-MM-DDTHH:mm:ss.sssZ'),
description_json: json,
description_html: html
}
try {
isLoadingSave.value = true
const {data,error} = await useFetch('/api/save', {
method: 'POST',
body: sendData
})
if (data.value) {
router.push({name: 'materials-id', params: {id: data.value.id}})
}
} catch {
} finally {
isLoadingSave.value = false
}
}
</script>
<template>
<div class="bg-header">
<div class="header">
<NuxtLink to="/" class="link">
<IconLogo class="icon" />
</NuxtLink>
<div>
<Button.ButtonDefault :kind="getButtonKind" @click="onClick" :loading="isLoadingSave" />
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.header {
max-width: 1224px;
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 20px 36px;
background: #fff;
align-items: center;
}
.icon {
height: 44px;
width: 115px;
color: var(--text-blue);
}
.bg-header {
background: #fff;
width: 100dvw;
}
.link {
line-height: 80%;
}
@media screen and (max-width: 768px) {
.header {
padding: 8px 16px;
}
.icon {
height: 36px;
width: 93px;
}
}
</style>

View File

@@ -0,0 +1,8 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 12.75H6C5.59 12.75 5.25 12.41 5.25 12C5.25 11.59 5.59 11.25 6 11.25H18C18.41 11.25 18.75 11.59 18.75 12C18.75 12.41 18.41 12.75 18 12.75Z" fill="currentColor"/>
<path d="M12 18.75C11.59 18.75 11.25 18.41 11.25 18V6C11.25 5.59 11.59 5.25 12 5.25C12.41 5.25 12.75 5.59 12.75 6V18C12.75 18.41 12.41 18.75 12 18.75Z" fill="currentColor"/>
</svg>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.57 18.82C9.38 18.82 9.19 18.75 9.04 18.6L2.97 12.53C2.68 12.24 2.68 11.76 2.97 11.47L9.04 5.4C9.33 5.11 9.81 5.11 10.1 5.4C10.39 5.69 10.39 6.17 10.1 6.46L4.56 12L10.1 17.54C10.39 17.83 10.39 18.31 10.1 18.6C9.96 18.75 9.76 18.82 9.57 18.82Z" fill="currentColor"/>
<path d="M20.5 12.75H3.67C3.26 12.75 2.92 12.41 2.92 12C2.92 11.59 3.26 11.25 3.67 11.25H20.5C20.91 11.25 21.25 11.59 21.25 12C21.25 12.41 20.91 12.75 20.5 12.75Z" fill="currentColor"/>
</svg>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.43 18.82C14.24 18.82 14.05 18.75 13.9 18.6C13.61 18.31 13.61 17.83 13.9 17.54L19.44 12L13.9 6.46C13.61 6.17 13.61 5.69 13.9 5.4C14.19 5.11 14.67 5.11 14.96 5.4L21.03 11.47C21.32 11.76 21.32 12.24 21.03 12.53L14.96 18.6C14.81 18.75 14.62 18.82 14.43 18.82Z" fill="currentColor"/>
<path d="M20.33 12.75H3.5C3.09 12.75 2.75 12.41 2.75 12C2.75 11.59 3.09 11.25 3.5 11.25H20.33C20.74 11.25 21.08 11.59 21.08 12C21.08 12.41 20.74 12.75 20.33 12.75Z" fill="currentColor"/>
</svg>
</template>

View File

@@ -0,0 +1,7 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 22.75H9C3.57 22.75 1.25 20.43 1.25 15V9C1.25 3.57 3.57 1.25 9 1.25H15C20.43 1.25 22.75 3.57 22.75 9V15C22.75 20.43 20.43 22.75 15 22.75ZM9 2.75C4.39 2.75 2.75 4.39 2.75 9V15C2.75 19.61 4.39 21.25 9 21.25H15C19.61 21.25 21.25 19.61 21.25 15V9C21.25 4.39 19.61 2.75 15 2.75H9Z" fill="currentColor"/>
<path d="M13.26 16.28C13.07 16.28 12.88 16.21 12.73 16.06L9.2 12.53C8.91 12.24 8.91 11.76 9.2 11.47L12.73 7.94C13.02 7.65 13.5 7.65 13.79 7.94C14.08 8.23 14.08 8.71 13.79 9L10.79 12L13.79 15C14.08 15.29 14.08 15.77 13.79 16.06C13.65 16.21 13.46 16.28 13.26 16.28Z" fill="currentColor"/>
</svg>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 22.75H9C3.57 22.75 1.25 20.43 1.25 15V9C1.25 3.57 3.57 1.25 9 1.25H15C20.43 1.25 22.75 3.57 22.75 9V15C22.75 20.43 20.43 22.75 15 22.75ZM9 2.75C4.39 2.75 2.75 4.39 2.75 9V15C2.75 19.61 4.39 21.25 9 21.25H15C19.61 21.25 21.25 19.61 21.25 15V9C21.25 4.39 19.61 2.75 15 2.75H9Z" fill="currentColor"/>
<path d="M10.74 16.28C10.55 16.28 10.36 16.21 10.21 16.06C9.92 15.77 9.92 15.29 10.21 15L13.21 12L10.21 9C9.92 8.71 9.92 8.23 10.21 7.94C10.5 7.65 10.98 7.65 11.27 7.94L14.8 11.47C15.09 11.76 15.09 12.24 14.8 12.53L11.27 16.06C11.12 16.21 10.93 16.28 10.74 16.28Z" fill="currentColor"/>
</svg>
</template>

View File

@@ -0,0 +1,15 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 5.75C7.59 5.75 7.25 5.41 7.25 5V2C7.25 1.59 7.59 1.25 8 1.25C8.41 1.25 8.75 1.59 8.75 2V5C8.75 5.41 8.41 5.75 8 5.75Z" fill="currentColor"/>
<path d="M16 5.75C15.59 5.75 15.25 5.41 15.25 5V2C15.25 1.59 15.59 1.25 16 1.25C16.41 1.25 16.75 1.59 16.75 2V5C16.75 5.41 16.41 5.75 16 5.75Z" fill="currentColor"/>
<path d="M8.5 14.5C8.37 14.5 8.24 14.47 8.12 14.42C7.99 14.37 7.89 14.3 7.79 14.21C7.61 14.02 7.5 13.77 7.5 13.5C7.5 13.37 7.53 13.24 7.58 13.12C7.63 13 7.7 12.89 7.79 12.79C7.89 12.7 7.99 12.63 8.12 12.58C8.48 12.43 8.93 12.51 9.21 12.79C9.39 12.98 9.5 13.24 9.5 13.5C9.5 13.56 9.49 13.63 9.48 13.7C9.47 13.76 9.45 13.82 9.42 13.88C9.4 13.94 9.37 14 9.33 14.06C9.3 14.11 9.25 14.16 9.21 14.21C9.02 14.39 8.76 14.5 8.5 14.5Z" fill="currentColor"/>
<path d="M12 14.5C11.87 14.5 11.74 14.47 11.62 14.42C11.49 14.37 11.39 14.3 11.29 14.21C11.11 14.02 11 13.77 11 13.5C11 13.37 11.03 13.24 11.08 13.12C11.13 13 11.2 12.89 11.29 12.79C11.39 12.7 11.49 12.63 11.62 12.58C11.98 12.42 12.43 12.51 12.71 12.79C12.89 12.98 13 13.24 13 13.5C13 13.56 12.99 13.63 12.98 13.7C12.97 13.76 12.95 13.82 12.92 13.88C12.9 13.94 12.87 14 12.83 14.06C12.8 14.11 12.75 14.16 12.71 14.21C12.52 14.39 12.26 14.5 12 14.5Z" fill="currentColor"/>
<path d="M15.5 14.5C15.37 14.5 15.24 14.47 15.12 14.42C14.99 14.37 14.89 14.3 14.79 14.21C14.75 14.16 14.71 14.11 14.67 14.06C14.63 14 14.6 13.94 14.58 13.88C14.55 13.82 14.53 13.76 14.52 13.7C14.51 13.63 14.5 13.56 14.5 13.5C14.5 13.24 14.61 12.98 14.79 12.79C14.89 12.7 14.99 12.63 15.12 12.58C15.49 12.42 15.93 12.51 16.21 12.79C16.39 12.98 16.5 13.24 16.5 13.5C16.5 13.56 16.49 13.63 16.48 13.7C16.47 13.76 16.45 13.82 16.42 13.88C16.4 13.94 16.37 14 16.33 14.06C16.3 14.11 16.25 14.16 16.21 14.21C16.02 14.39 15.76 14.5 15.5 14.5Z" fill="currentColor"/>
<path d="M8.5 18C8.37 18 8.24 17.97 8.12 17.92C8 17.87 7.89 17.8 7.79 17.71C7.61 17.52 7.5 17.26 7.5 17C7.5 16.87 7.53 16.74 7.58 16.62C7.63 16.49 7.7 16.38 7.79 16.29C8.16 15.92 8.84 15.92 9.21 16.29C9.39 16.48 9.5 16.74 9.5 17C9.5 17.26 9.39 17.52 9.21 17.71C9.02 17.89 8.76 18 8.5 18Z" fill="currentColor"/>
<path d="M12 18C11.74 18 11.48 17.89 11.29 17.71C11.11 17.52 11 17.26 11 17C11 16.87 11.03 16.74 11.08 16.62C11.13 16.49 11.2 16.38 11.29 16.29C11.66 15.92 12.34 15.92 12.71 16.29C12.8 16.38 12.87 16.49 12.92 16.62C12.97 16.74 13 16.87 13 17C13 17.26 12.89 17.52 12.71 17.71C12.52 17.89 12.26 18 12 18Z" fill="currentColor"/>
<path d="M15.5 18C15.24 18 14.98 17.89 14.79 17.71C14.7 17.62 14.63 17.51 14.58 17.38C14.53 17.26 14.5 17.13 14.5 17C14.5 16.87 14.53 16.74 14.58 16.62C14.63 16.49 14.7 16.38 14.79 16.29C15.02 16.06 15.37 15.95 15.69 16.02C15.76 16.03 15.82 16.05 15.88 16.08C15.94 16.1 16 16.13 16.06 16.17C16.11 16.2 16.16 16.25 16.21 16.29C16.39 16.48 16.5 16.74 16.5 17C16.5 17.26 16.39 17.52 16.21 17.71C16.02 17.89 15.76 18 15.5 18Z" fill="currentColor"/>
<path d="M20.5 9.83997H3.5C3.09 9.83997 2.75 9.49997 2.75 9.08997C2.75 8.67997 3.09 8.33997 3.5 8.33997H20.5C20.91 8.33997 21.25 8.67997 21.25 9.08997C21.25 9.49997 20.91 9.83997 20.5 9.83997Z" fill="currentColor"/>
<path d="M16 22.75H8C4.35 22.75 2.25 20.65 2.25 17V8.5C2.25 4.85 4.35 2.75 8 2.75H16C19.65 2.75 21.75 4.85 21.75 8.5V17C21.75 20.65 19.65 22.75 16 22.75ZM8 4.25C5.14 4.25 3.75 5.64 3.75 8.5V17C3.75 19.86 5.14 21.25 8 21.25H16C18.86 21.25 20.25 19.86 20.25 17V8.5C20.25 5.64 18.86 4.25 16 4.25H8Z" fill="currentColor"/>
</svg>
</template>

View File

@@ -0,0 +1,7 @@
<template>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.6696 7.58373C18.9869 8.72224 19.0768 9.91209 18.9343 11.0854C18.7918 12.2586 18.4196 13.3923 17.8389 14.4218C17.2582 15.4512 16.4805 16.3561 15.55 17.085C14.6196 17.8138 13.5548 18.3523 12.4163 18.6696C11.2778 18.9869 10.0879 19.0768 8.91463 18.9343C7.74136 18.7918 6.60766 18.4196 5.57824 17.8389C4.54882 17.2582 3.64386 16.4805 2.91502 15.55C2.18617 14.6196 1.64773 13.5548 1.33042 12.4163" stroke="#00B2FF" stroke-width="2"/>
</svg>
</template>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.22 21.63C13.04 21.63 11.37 20.8 10.05 16.83L9.33 14.67L7.17 13.95C3.21 12.63 2.38 10.96 2.38 9.78001C2.38 8.61001 3.21 6.93001 7.17 5.60001L15.66 2.77001C17.78 2.06001 19.55 2.27001 20.64 3.35001C21.73 4.43001 21.94 6.21001 21.23 8.33001L18.4 16.82C17.07 20.8 15.4 21.63 14.22 21.63ZM7.64 7.03001C4.86 7.96001 3.87 9.06001 3.87 9.78001C3.87 10.5 4.86 11.6 7.64 12.52L10.16 13.36C10.38 13.43 10.56 13.61 10.63 13.83L11.47 16.35C12.39 19.13 13.5 20.12 14.22 20.12C14.94 20.12 16.04 19.13 16.97 16.35L19.8 7.86001C20.31 6.32001 20.22 5.06001 19.57 4.41001C18.92 3.76001 17.66 3.68001 16.13 4.19001L7.64 7.03001Z" fill="currentColor"/>
<path d="M10.11 14.4C9.92 14.4 9.73001 14.33 9.58001 14.18C9.29001 13.89 9.29001 13.41 9.58001 13.12L13.16 9.53001C13.45 9.24001 13.93 9.24001 14.22 9.53001C14.51 9.82001 14.51 10.3 14.22 10.59L10.64 14.18C10.5 14.33 10.3 14.4 10.11 14.4Z" fill="currentColor"/>
</svg>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22.75C6.07 22.75 1.25 17.93 1.25 12C1.25 6.07 6.07 1.25 12 1.25C17.93 1.25 22.75 6.07 22.75 12C22.75 17.93 17.93 22.75 12 22.75ZM12 2.75C6.9 2.75 2.75 6.9 2.75 12C2.75 17.1 6.9 21.25 12 21.25C17.1 21.25 21.25 17.1 21.25 12C21.25 6.9 17.1 2.75 12 2.75Z" fill="currentColor"/>
<path d="M10.58 15.58C10.38 15.58 10.19 15.5 10.05 15.36L7.22 12.53C6.93 12.24 6.93 11.76 7.22 11.47C7.51 11.18 7.99 11.18 8.28 11.47L10.58 13.77L15.72 8.63C16.01 8.34 16.49 8.34 16.78 8.63C17.07 8.92 17.07 9.4 16.78 9.69L11.11 15.36C10.97 15.5 10.78 15.58 10.58 15.58Z" fill="currentColor"/>
</svg>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 22.75H9C3.57 22.75 1.25 20.43 1.25 15V9C1.25 3.57 3.57 1.25 9 1.25H15C20.43 1.25 22.75 3.57 22.75 9V15C22.75 20.43 20.43 22.75 15 22.75ZM9 2.75C4.39 2.75 2.75 4.39 2.75 9V15C2.75 19.61 4.39 21.25 9 21.25H15C19.61 21.25 21.25 19.61 21.25 15V9C21.25 4.39 19.61 2.75 15 2.75H9Z" fill="currentColor"/>
<path d="M10.58 15.58C10.38 15.58 10.19 15.5 10.05 15.36L7.22 12.53C6.93 12.24 6.93 11.76 7.22 11.47C7.51 11.18 7.99 11.18 8.28 11.47L10.58 13.77L15.72 8.63C16.01 8.34 16.49 8.34 16.78 8.63C17.07 8.92 17.07 9.4 16.78 9.69L11.11 15.36C10.97 15.5 10.78 15.58 10.58 15.58Z" fill="currentColor"/>
</svg>
</template>

View File

@@ -0,0 +1,47 @@
<script lang="ts" setup="">
import {useGoBackOrHome} from "~/composables/useGoBackOrHome";
const router = useRouter()
const {goBack, canGoBack} = useGoBackOrHome()
const onClickBack = () => {
if (canGoBack.value) {
goBack()
return
}
router.push('/')
}
</script>
<template>
<button type="button" class="button" @click="onClickBack">
<IconsIconArrowLeft class="icon"/>
<span class="text" >Назад</span>
</button>
</template>
<style scoped lang="scss">
.button {
display: flex;
gap: 12px;
align-items: center;
background: none;
cursor: pointer;
color: var(--text-black);
transition: all .3s ease;
&:hover {
color: var(--text-blue);
}
}
.icon {
@include mixins.square(24px);
}
.text {
font-family: FuturaPT, sans-serif;
font-weight: 500;
font-size: 18px;
line-height: 100%;
}
</style>

View File

@@ -0,0 +1,110 @@
<script lang="ts" setup>
import IconLoader from "~/components/icons/iconLoader.vue";
import type {ButtonProps} from "~/components/ui/Buttons/types.buttons";
import {ButtonKind} from "~/components/ui/Buttons/constants.buttons";
import IconAdd from "~/components/icons/iconAdd.vue";
import IconSend from "~/components/icons/iconSend.vue";
import {useWindowSize} from "@vueuse/core";
const {width} = useWindowSize()
const props = withDefaults(defineProps<ButtonProps>(), {
loading: false,
kind: ButtonKind.CreateMaterial
})
const getButtonTextAndIcon = computed(() => {
switch (props.kind) {
case ButtonKind.CreateStory:
return {
text: 'Создать историю',
icon: IconAdd,
};
case ButtonKind.CreateMaterial:
return {
text: 'Создать материал',
icon: IconAdd,
};
case ButtonKind.Send:
default:
return {
text: 'Сохранить',
icon: IconSend
}
}
})
</script>
<template>
<button type="button" class="button">
<IconLoader class="icon loader" :class="{loading}" v-if="loading" />
<template v-else>
<span v-if="width > 768">{{getButtonTextAndIcon.text}}</span>
<component v-else :is="getButtonTextAndIcon.icon" class="icon"/>
</template>
</button>
</template>
<style scoped lang="scss">
.button {
border-radius: 12px;
padding: 0 24px;
height: 44px;
display: flex;
align-items: center;
background: #E7F8FE;
color: var(--text-blue);
font-family: FuturaPT, sans-serif;
font-weight: 500;
font-size: 18px;
line-height: 100%;
cursor: pointer;
transition: all .3s ease;
&:hover:not(:disabled) {
background: #D2F3FF;
}
&:disabled {
background: #E7F8FE;
cursor: not-allowed;
}
&.loading {
pointer-events: none;
}
}
.icon {
@include mixins.square(20px);
}
.loader {
animation: rotate 1s linear infinite;
}
@keyframes rotate {
0% {
rotate: 0deg;
}
100% {
rotate: 360deg;
}
}
@media screen and (max-width: 768px) {
.button {
padding: 10px;
height: 48px;
}
.icon {
@include mixins.square(28px);
}
}
</style>

View File

@@ -0,0 +1,5 @@
export enum ButtonKind {
Send = 'send',
CreateStory = 'createStory',
CreateMaterial = 'createMaterial'
}

View File

@@ -0,0 +1,7 @@
import ButtonBack from "~/components/ui/Buttons/ButtonBack.vue";
import ButtonDefault from "~/components/ui/Buttons/ButtonDefault.vue";
export const Button = {
ButtonBack: ButtonBack,
ButtonDefault: ButtonDefault
}

View File

@@ -0,0 +1,7 @@
import {ButtonBackTheme, ButtonKind} from "~/components/ui/Buttons/constants.buttons";
export interface ButtonProps {
loading?: boolean
kind?: ButtonKind,
}