base commit

This commit is contained in:
@kirill.komarov
2026-05-03 19:57:12 +05:00
parent 9139a24093
commit fe10f25b8c
53 changed files with 2064 additions and 1071 deletions
+43
View File
@@ -0,0 +1,43 @@
import Button from '@mui/material/Button'
import Stack from '@mui/material/Stack'
type EntityRowActionsProps = {
onEdit?: () => void
onDelete?: () => void
deleteDisabled?: boolean
confirmDeleteMessage?: string
editLabel?: string
deleteLabel?: string
}
export function EntityRowActions({
onEdit,
onDelete,
deleteDisabled = false,
confirmDeleteMessage,
editLabel = 'Изменить',
deleteLabel = 'Удалить',
}: EntityRowActionsProps) {
return (
<Stack direction="row" spacing={0.5} sx={{ justifyContent: 'flex-end' }}>
{onEdit && (
<Button size="small" onClick={onEdit}>
{editLabel}
</Button>
)}
{onDelete && (
<Button
size="small"
color="error"
disabled={deleteDisabled}
onClick={() => {
if (confirmDeleteMessage && !confirm(confirmDeleteMessage)) return
onDelete()
}}
>
{deleteLabel}
</Button>
)}
</Stack>
)
}
@@ -0,0 +1,101 @@
import { useEffect } from 'react'
import FormatBoldOutlinedIcon from '@mui/icons-material/FormatBoldOutlined'
import FormatItalicOutlinedIcon from '@mui/icons-material/FormatItalicOutlined'
import FormatListBulletedOutlinedIcon from '@mui/icons-material/FormatListBulletedOutlined'
import Box from '@mui/material/Box'
import IconButton from '@mui/material/IconButton'
import Stack from '@mui/material/Stack'
import Placeholder from '@tiptap/extension-placeholder'
import { EditorContent, useEditor } from '@tiptap/react'
import TiptapStarterKit from '@tiptap/starter-kit'
type RichTextMessageEditorProps = {
value: string
onChange: (next: string) => void
placeholder?: string
disabled?: boolean
}
export function RichTextMessageEditor({
value,
onChange,
placeholder = 'Введите сообщение',
disabled = false,
}: RichTextMessageEditorProps) {
const editor = useEditor({
extensions: [
TiptapStarterKit.configure({ heading: false, codeBlock: false, blockquote: false, horizontalRule: false }),
Placeholder.configure({ placeholder }),
],
content: value,
editable: !disabled,
onUpdate: ({ editor: tiptap }) => onChange(tiptap.getText()),
})
useEffect(() => {
if (!editor) return
editor.setEditable(!disabled)
}, [disabled, editor])
useEffect(() => {
if (!editor) return
if (editor.getText() === value) return
editor.commands.setContent(value, false)
}, [editor, value])
return (
<Box sx={{ border: 1, borderColor: 'divider', borderRadius: 2, overflow: 'hidden', bgcolor: 'background.paper' }}>
<Stack direction="row" spacing={0.5} sx={{ p: 0.75, borderBottom: 1, borderColor: 'divider' }}>
<IconButton
size="small"
onClick={() => editor?.chain().focus().toggleBold().run()}
color={editor?.isActive('bold') ? 'primary' : 'default'}
disabled={disabled}
aria-label="Жирный"
>
<FormatBoldOutlinedIcon fontSize="small" />
</IconButton>
<IconButton
size="small"
onClick={() => editor?.chain().focus().toggleItalic().run()}
color={editor?.isActive('italic') ? 'primary' : 'default'}
disabled={disabled}
aria-label="Курсив"
>
<FormatItalicOutlinedIcon fontSize="small" />
</IconButton>
<IconButton
size="small"
onClick={() => editor?.chain().focus().toggleBulletList().run()}
color={editor?.isActive('bulletList') ? 'primary' : 'default'}
disabled={disabled}
aria-label="Список"
>
<FormatListBulletedOutlinedIcon fontSize="small" />
</IconButton>
</Stack>
<Box
sx={{
px: 1.5,
py: 1.25,
'& .ProseMirror': {
minHeight: 72,
outline: 'none',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
},
'& .ProseMirror p.is-editor-empty:first-of-type::before': {
content: `"${placeholder}"`,
color: 'text.disabled',
pointerEvents: 'none',
float: 'left',
height: 0,
},
}}
>
<EditorContent editor={editor} />
</Box>
</Box>
)
}