anteriority-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/.github/workflows/ci.yml +31 -0
  2. package/.github/workflows/publish.yml +45 -0
  3. package/.npmrc.bak +1 -0
  4. package/.storybook/main.js +15 -0
  5. package/.storybook/preview.jsx +26 -0
  6. package/AnteriorityUI.md +2400 -0
  7. package/CLAUDE.md +91 -0
  8. package/README.md +182 -0
  9. package/assets/anteriorityUI/AnteriorityUI.logo.jpeg +0 -0
  10. package/assets/favicon/anteriorityUI.ico +0 -0
  11. package/common/api/client.js +43 -0
  12. package/common/storage/LocalStorage.js +13 -0
  13. package/common/styles/typography.css +5 -0
  14. package/common/styles/variables.css +192 -0
  15. package/common/theme/muiTheme.js +9 -0
  16. package/features/password-reset/api.js +39 -0
  17. package/features/user-profile/api.js +5 -0
  18. package/hooks/useAdminPasswordReset.js +133 -0
  19. package/index.js +57 -0
  20. package/package.json +57 -0
  21. package/pnpm-workspace.yaml +3 -0
  22. package/screens/components/AlertModal/AlertModal.jsx +259 -0
  23. package/screens/components/AlertModal/AlertModal.module.css +185 -0
  24. package/screens/components/AlertModal/AlertModal.stories.jsx +87 -0
  25. package/screens/components/Avatar/index.jsx +0 -0
  26. package/screens/components/Avatar/index.module.css +0 -0
  27. package/screens/components/Avatar/utils.js +0 -0
  28. package/screens/components/Badge/index.jsx +33 -0
  29. package/screens/components/Badge/index.module.css +18 -0
  30. package/screens/components/Badge/index.stories.jsx +40 -0
  31. package/screens/components/Barnner/index.jsx +21 -0
  32. package/screens/components/Barnner/index.module.css +33 -0
  33. package/screens/components/Barnner/index.stories.jsx +39 -0
  34. package/screens/components/Barnner/utils.js +0 -0
  35. package/screens/components/BlueLink/index.jsx +27 -0
  36. package/screens/components/BlueLink/index.module.css +11 -0
  37. package/screens/components/BlueLink/index.stories.jsx +29 -0
  38. package/screens/components/Breadcrumbs/index.jsx +62 -0
  39. package/screens/components/Breadcrumbs/index.module.css +158 -0
  40. package/screens/components/Breadcrumbs/index.stories.jsx +37 -0
  41. package/screens/components/Button/index.jsx +169 -0
  42. package/screens/components/Button/index.module.css +168 -0
  43. package/screens/components/Button/index.stories.jsx +71 -0
  44. package/screens/components/Button/utils.js +28 -0
  45. package/screens/components/CalendarComponent/CustomDatePicker.jsx +248 -0
  46. package/screens/components/CalendarComponent/CustomDatePicker.module.css +323 -0
  47. package/screens/components/CalendarComponent/CustomDatePicker.stories.jsx +45 -0
  48. package/screens/components/CalendarPicker/Constants.js +12 -0
  49. package/screens/components/CalendarPicker/index.jsx +263 -0
  50. package/screens/components/CalendarPicker/index.module.css +147 -0
  51. package/screens/components/CalendarPicker/index.stories.jsx +45 -0
  52. package/screens/components/CalendarPicker/utils.js +42 -0
  53. package/screens/components/CardV1/index.jsx +165 -0
  54. package/screens/components/CardV1/index.module.css +232 -0
  55. package/screens/components/CardV1/index.stories.jsx +62 -0
  56. package/screens/components/CardV1/utils.js +49 -0
  57. package/screens/components/ChatWindow/index.jsx +251 -0
  58. package/screens/components/ChatWindow/index.module.css +311 -0
  59. package/screens/components/ChatWindow/index.stories.jsx +62 -0
  60. package/screens/components/Checks/index.jsx +31 -0
  61. package/screens/components/Checks/index.module.css +32 -0
  62. package/screens/components/Checks/index.stories.jsx +32 -0
  63. package/screens/components/Container/index.jsx +36 -0
  64. package/screens/components/Container/index.module.css +24 -0
  65. package/screens/components/Container/index.stories.jsx +32 -0
  66. package/screens/components/CopyText/index.jsx +42 -0
  67. package/screens/components/CopyText/index.module.css +52 -0
  68. package/screens/components/CopyText/index.stories.jsx +24 -0
  69. package/screens/components/CountdownBar/constants.js +3 -0
  70. package/screens/components/CountdownBar/index.jsx +81 -0
  71. package/screens/components/CountdownBar/index.module.css +45 -0
  72. package/screens/components/CountdownBar/index.stories.jsx +43 -0
  73. package/screens/components/CountdownBar/utils.js +119 -0
  74. package/screens/components/DateTag/index.jsx +33 -0
  75. package/screens/components/DateTag/index.module.css +12 -0
  76. package/screens/components/DateTag/index.stories.jsx +29 -0
  77. package/screens/components/DesplegablePanel/constants.js +0 -0
  78. package/screens/components/DesplegablePanel/index.jsx +61 -0
  79. package/screens/components/DesplegablePanel/index.module.css +120 -0
  80. package/screens/components/DesplegablePanel/index.stories.jsx +44 -0
  81. package/screens/components/DesplegablePanel/utils.js +0 -0
  82. package/screens/components/DrawPanel/index.jsx +217 -0
  83. package/screens/components/DrawPanel/index.module.css +252 -0
  84. package/screens/components/DrawPanel/index.stories.jsx +66 -0
  85. package/screens/components/DrawPanel/utils.js +0 -0
  86. package/screens/components/Filter/index.jsx +105 -0
  87. package/screens/components/Filter/index.module.css +91 -0
  88. package/screens/components/Filter/index.stories.jsx +46 -0
  89. package/screens/components/Filter/utils.js +0 -0
  90. package/screens/components/FormControls/FormControls.module.css +86 -0
  91. package/screens/components/FormControls/index.jsx +51 -0
  92. package/screens/components/FormControls/index.stories.jsx +36 -0
  93. package/screens/components/Frame/index.jsx +54 -0
  94. package/screens/components/Frame/index.module.css +102 -0
  95. package/screens/components/Frame/index.stories.jsx +37 -0
  96. package/screens/components/Frame/utils.js +0 -0
  97. package/screens/components/GreenHighlight/index.jsx +11 -0
  98. package/screens/components/GreenHighlight/index.module.css +7 -0
  99. package/screens/components/GreenHighlight/index.stories.jsx +26 -0
  100. package/screens/components/GreenHighlight/utils.jsx +45 -0
  101. package/screens/components/HoursPicker/Constants.js +26 -0
  102. package/screens/components/HoursPicker/index.jsx +350 -0
  103. package/screens/components/HoursPicker/index.module.css +99 -0
  104. package/screens/components/HoursPicker/index.stories.jsx +44 -0
  105. package/screens/components/HoursPicker/utils.js +48 -0
  106. package/screens/components/InfoTooltip/index.jsx +59 -0
  107. package/screens/components/InfoTooltip/index.module.css +98 -0
  108. package/screens/components/InfoTooltip/index.stories.jsx +35 -0
  109. package/screens/components/Input/index.jsx +36 -0
  110. package/screens/components/Input/index.module.css +49 -0
  111. package/screens/components/Input/index.stories.jsx +37 -0
  112. package/screens/components/Input/utils.js +0 -0
  113. package/screens/components/Loading/index.jsx +76 -0
  114. package/screens/components/Loading/index.module.css +193 -0
  115. package/screens/components/Loading/index.stories.jsx +38 -0
  116. package/screens/components/Loading/utils.js +0 -0
  117. package/screens/components/MarkdownContent/index.jsx +16 -0
  118. package/screens/components/MarkdownContent/index.module.css +149 -0
  119. package/screens/components/MarkdownContent/index.stories.jsx +30 -0
  120. package/screens/components/MarkdownEditor/index.jsx +430 -0
  121. package/screens/components/MarkdownEditor/index.module.css +192 -0
  122. package/screens/components/MarkdownEditor/index.stories.jsx +50 -0
  123. package/screens/components/Material-UI/Components/InputWithIcon/index.jsx +87 -0
  124. package/screens/components/Material-UI/Components/InputWithIcon/index.stories.jsx +49 -0
  125. package/screens/components/Material-UI/Components/MenuPopover/index.jsx +117 -0
  126. package/screens/components/Material-UI/Components/MenuPopover/index.stories.jsx +36 -0
  127. package/screens/components/Material-UI/Components/MultilinePlaceholder/index.jsx +44 -0
  128. package/screens/components/Material-UI/Components/MultilinePlaceholder/index.stories.jsx +45 -0
  129. package/screens/components/Material-UI/Components/Paginador/index.jsx +79 -0
  130. package/screens/components/Material-UI/Components/Paginador/index.stories.jsx +49 -0
  131. package/screens/components/Material-UI/Components/Search/index.jsx +81 -0
  132. package/screens/components/Material-UI/Components/Search/index.stories.jsx +68 -0
  133. package/screens/components/Material-UI/Components/Selector/index.jsx +100 -0
  134. package/screens/components/Material-UI/Components/Selector/index.module.css +0 -0
  135. package/screens/components/Material-UI/Components/Selector/index.stories.jsx +52 -0
  136. package/screens/components/Material-UI/Components/Selector/utils.js +0 -0
  137. package/screens/components/Material-UI/Components/TextField/index.jsx +61 -0
  138. package/screens/components/Material-UI/Components/TextField/index.stories.jsx +53 -0
  139. package/screens/components/ModalTooltip/ModalTooltip.jsx +122 -0
  140. package/screens/components/ModalTooltip/ModalTooltip.module.css +65 -0
  141. package/screens/components/ModalTooltip/ModalTooltip.stories.jsx +39 -0
  142. package/screens/components/Notification/Error/ErrorMessage.jsx +30 -0
  143. package/screens/components/Notification/Error/ErrorMessage.module.css +57 -0
  144. package/screens/components/Notification/Error/ErrorMessage.stories.jsx +37 -0
  145. package/screens/components/PasswordResetPanel/index.jsx +325 -0
  146. package/screens/components/PasswordResetPanel/index.stories.jsx +26 -0
  147. package/screens/components/PlaceholderButton/index.jsx +169 -0
  148. package/screens/components/PlaceholderButton/index.module.css +162 -0
  149. package/screens/components/PlaceholderButton/index.stories.jsx +69 -0
  150. package/screens/components/PlaceholderButton/utils.js +42 -0
  151. package/screens/components/Profile/Avatar/index.jsx +125 -0
  152. package/screens/components/Profile/Avatar/index.module.css +86 -0
  153. package/screens/components/Profile/Avatar/index.stories.jsx +48 -0
  154. package/screens/components/Profile/Avatar/utils.js +18 -0
  155. package/screens/components/Profile/index.jsx +58 -0
  156. package/screens/components/Profile/index.module.css +0 -0
  157. package/screens/components/Profile/index.stories.jsx +38 -0
  158. package/screens/components/Profile/utils.js +0 -0
  159. package/screens/components/ScrollBar/index.jsx +62 -0
  160. package/screens/components/ScrollBar/index.module.css +78 -0
  161. package/screens/components/ScrollBar/index.stories.jsx +64 -0
  162. package/screens/components/ScrollBar/utils.js +127 -0
  163. package/screens/components/Sidebar/constants.js +16 -0
  164. package/screens/components/Sidebar/index.jsx +399 -0
  165. package/screens/components/Sidebar/index.module.css +405 -0
  166. package/screens/components/Sidebar/index.stories.jsx +86 -0
  167. package/screens/components/Sidebar/utils.js +75 -0
  168. package/screens/components/Skeleton/index.jsx +11 -0
  169. package/screens/components/Skeleton/index.module.css +26 -0
  170. package/screens/components/Skeleton/index.stories.jsx +42 -0
  171. package/screens/components/SubSidebarLayout/index.jsx +44 -0
  172. package/screens/components/SubSidebarLayout/index.module.css +80 -0
  173. package/screens/components/SubSidebarLayout/index.stories.jsx +51 -0
  174. package/screens/components/SummaryCard/index.jsx +50 -0
  175. package/screens/components/SummaryCard/index.module.css +30 -0
  176. package/screens/components/SummaryCard/index.stories.jsx +29 -0
  177. package/screens/components/Swich/constants.js +4 -0
  178. package/screens/components/Swich/index.jsx +85 -0
  179. package/screens/components/Swich/index.module.css +68 -0
  180. package/screens/components/Swich/index.stories.jsx +44 -0
  181. package/screens/components/Swich/utils.js +0 -0
  182. package/screens/components/TableA/index.jsx +156 -0
  183. package/screens/components/TableA/index.module.css +105 -0
  184. package/screens/components/TableA/index.stories.jsx +116 -0
  185. package/screens/components/TableA/utils.js +11 -0
  186. package/screens/components/TableB/index.jsx +222 -0
  187. package/screens/components/TableB/index.module.css +295 -0
  188. package/screens/components/TableB/index.stories.jsx +110 -0
  189. package/screens/components/TableB/utils.js +11 -0
  190. package/screens/components/ToggleCard/index.jsx +64 -0
  191. package/screens/components/ToggleCard/index.module.css +111 -0
  192. package/screens/components/ToggleCard/index.stories.jsx +49 -0
  193. package/screens/components/UserEditPopover/index.jsx +102 -0
  194. package/screens/components/UserEditPopover/index.module.css +97 -0
  195. package/screens/components/UserEditPopover/index.stories.jsx +49 -0
  196. package/scripts/build.mjs +129 -0
  197. package/scripts/check-case.mjs +127 -0
  198. package/scripts/verify-dist.mjs +45 -0
  199. package/vitest.config.js +36 -0
@@ -0,0 +1,149 @@
1
+ .markdown {
2
+ font-size: 0.88rem;
3
+ line-height: 1.6;
4
+ color: #cbd0d9;
5
+ word-break: break-word;
6
+ }
7
+
8
+ .markdown > *:first-child {
9
+ margin-top: 0;
10
+ }
11
+
12
+ .markdown > *:last-child {
13
+ margin-bottom: 0;
14
+ }
15
+
16
+ .markdown h1,
17
+ .markdown h2,
18
+ .markdown h3,
19
+ .markdown h4,
20
+ .markdown h5,
21
+ .markdown h6 {
22
+ color: inherit;
23
+ font-weight: 700;
24
+ line-height: 1.35;
25
+ margin: 1.1em 0 0.5em 0;
26
+ }
27
+
28
+ .markdown h1 { font-size: 1.35rem; }
29
+ .markdown h2 { font-size: 1.2rem; }
30
+ .markdown h3 { font-size: 1.05rem; }
31
+ .markdown h4,
32
+ .markdown h5,
33
+ .markdown h6 { font-size: 0.95rem; }
34
+
35
+ .markdown p {
36
+ margin: 0 0 0.75em 0;
37
+ }
38
+
39
+ .markdown a {
40
+ color: var(--markdown-link, #7c9cff);
41
+ text-decoration: underline;
42
+ }
43
+
44
+ .markdown a:hover {
45
+ color: #a9bfff;
46
+ }
47
+
48
+ .markdown strong {
49
+ color: inherit;
50
+ font-weight: 700;
51
+ }
52
+
53
+ .markdown em {
54
+ font-style: italic;
55
+ }
56
+
57
+ .markdown del {
58
+ color: inherit;
59
+ opacity: 0.7;
60
+ }
61
+
62
+ .markdown ul,
63
+ .markdown ol {
64
+ margin: 0 0 0.75em 0;
65
+ padding-left: 1.4rem;
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 0.25rem;
69
+ }
70
+
71
+ .markdown li > ul,
72
+ .markdown li > ol {
73
+ margin-top: 0.25rem;
74
+ margin-bottom: 0;
75
+ }
76
+
77
+ .markdown li input[type='checkbox'] {
78
+ margin-right: 0.4rem;
79
+ }
80
+
81
+ .markdown blockquote {
82
+ margin: 0 0 0.75em 0;
83
+ padding: 0.25rem 1rem;
84
+ border-left: 3px solid rgba(124, 156, 255, 0.5);
85
+ color: inherit;
86
+ opacity: 0.85;
87
+ }
88
+
89
+ .markdown blockquote p:last-child {
90
+ margin-bottom: 0;
91
+ }
92
+
93
+ .markdown code {
94
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
95
+ font-size: 0.85em;
96
+ background: rgba(128, 128, 128, 0.18);
97
+ color: inherit;
98
+ padding: 0.1em 0.35em;
99
+ border-radius: 4px;
100
+ }
101
+
102
+ .markdown pre {
103
+ margin: 0 0 0.75em 0;
104
+ padding: 0.85rem 1rem;
105
+ background: rgba(128, 128, 128, 0.12);
106
+ border: 1px solid rgba(128, 128, 128, 0.2);
107
+ border-radius: 8px;
108
+ overflow-x: auto;
109
+ }
110
+
111
+ .markdown pre code {
112
+ background: none;
113
+ padding: 0;
114
+ border-radius: 0;
115
+ color: inherit;
116
+ }
117
+
118
+ .markdown hr {
119
+ border: none;
120
+ border-top: 1px solid rgba(128, 128, 128, 0.25);
121
+ margin: 1em 0;
122
+ }
123
+
124
+ .markdown img {
125
+ max-width: 100%;
126
+ border-radius: 6px;
127
+ }
128
+
129
+ .markdown table {
130
+ width: 100%;
131
+ border-collapse: collapse;
132
+ margin: 0 0 0.75em 0;
133
+ font-size: 0.85rem;
134
+ display: block;
135
+ overflow-x: auto;
136
+ }
137
+
138
+ .markdown th,
139
+ .markdown td {
140
+ border: 1px solid rgba(128, 128, 128, 0.25);
141
+ padding: 0.4rem 0.6rem;
142
+ text-align: left;
143
+ }
144
+
145
+ .markdown th {
146
+ background: rgba(128, 128, 128, 0.15);
147
+ color: inherit;
148
+ font-weight: 700;
149
+ }
@@ -0,0 +1,30 @@
1
+ import { MarkdownContent } from './index';
2
+
3
+ export default {
4
+ title: 'Components/MarkdownContent',
5
+ component: MarkdownContent,
6
+ };
7
+
8
+ export const Default = {
9
+ args: {
10
+ children: '# Heading\n\nThis is a **bold** paragraph with some *italic* text and a [link](https://example.com).',
11
+ },
12
+ };
13
+
14
+ export const WithList = {
15
+ args: {
16
+ children: '## Features\n\n- First item\n- Second item\n- Third item\n\n1. Ordered one\n2. Ordered two',
17
+ },
18
+ };
19
+
20
+ export const WithTable = {
21
+ args: {
22
+ children: '| Column 1 | Column 2 | Column 3 |\n| --- | --- | --- |\n| Data 1 | Data 2 | Data 3 |\n| Data 4 | Data 5 | Data 6 |',
23
+ },
24
+ };
25
+
26
+ export const WithCodeBlock = {
27
+ args: {
28
+ children: '```js\nfunction greet(name) {\n return `Hello, ${name}!`;\n}\n```',
29
+ },
30
+ };
@@ -0,0 +1,430 @@
1
+ import { useRef, useState } from 'react';
2
+ import { Tooltip } from '@mui/material';
3
+ import {
4
+ Bold,
5
+ Italic,
6
+ Heading2,
7
+ Heading3,
8
+ List,
9
+ ListOrdered,
10
+ Quote,
11
+ Code,
12
+ Link as LinkIcon,
13
+ Table as TableIcon,
14
+ Eye,
15
+ Edit3,
16
+ Columns,
17
+ } from 'lucide-react';
18
+ import { MarkdownContent } from '../MarkdownContent/index';
19
+ import styles from './index.module.css';
20
+
21
+ export const MarkdownEditor = ({
22
+ value = '',
23
+ onChange,
24
+ placeholder = '',
25
+ defaultHeight = 320,
26
+ labels = {},
27
+ activeColor,
28
+ }) => {
29
+ const {
30
+ editorTab = 'Editor',
31
+ splitTab = 'Dividido',
32
+ previewTab = 'Vista previa',
33
+ splitEmptyPreview = 'Vista previa del formato markdown...',
34
+ previewEmptyPreview = 'Sin contenido para previsualizar',
35
+ boldTooltip = 'Negrita (Ctrl+B)',
36
+ italicTooltip = 'Cursiva (Ctrl+I)',
37
+ heading2Tooltip = 'Encabezado principal',
38
+ heading3Tooltip = 'Subtítulo',
39
+ bulletListTooltip = 'Lista con viñetas',
40
+ orderedListTooltip = 'Lista numerada',
41
+ quoteTooltip = 'Cita',
42
+ codeBlockTooltip = 'Bloque de código',
43
+ linkTooltip = 'Insertar enlace (Ctrl+K)',
44
+ tableTooltip = 'Insertar tabla',
45
+ resizeHandleTitle = 'Arrastra para cambiar la altura (doble click para expandir/reducir)',
46
+ boldDefaultText = 'negrita',
47
+ italicDefaultText = 'cursiva',
48
+ linkDefaultText = 'enlace',
49
+ codeDefaultText = 'código',
50
+ tableColumnLabels = ['Columna 1', 'Columna 2', 'Columna 3'],
51
+ tableRowLabels = ['Dato 1', 'Dato 2', 'Dato 3'],
52
+ } = labels;
53
+
54
+ const [viewMode, setViewMode] = useState('write'); // 'write' | 'split' | 'preview'
55
+ const [editorHeight, setEditorHeight] = useState(defaultHeight);
56
+ const [isDragging, setIsDragging] = useState(false);
57
+ const textareaRef = useRef(null);
58
+ const dragStartYRef = useRef(0);
59
+ const dragStartHeightRef = useRef(defaultHeight);
60
+
61
+ const handleMouseDown = (event) => {
62
+ event.preventDefault();
63
+ setIsDragging(true);
64
+ dragStartYRef.current = event.clientY;
65
+ dragStartHeightRef.current = editorHeight;
66
+
67
+ const handleMouseMove = (moveEvent) => {
68
+ const deltaY = moveEvent.clientY - dragStartYRef.current;
69
+ const newHeight = Math.max(180, Math.min(1200, dragStartHeightRef.current + deltaY));
70
+ setEditorHeight(newHeight);
71
+ };
72
+
73
+ const handleMouseUp = () => {
74
+ setIsDragging(false);
75
+ window.removeEventListener('mousemove', handleMouseMove);
76
+ window.removeEventListener('mouseup', handleMouseUp);
77
+ document.body.style.userSelect = '';
78
+ document.body.style.cursor = '';
79
+ };
80
+
81
+ document.body.style.userSelect = 'none';
82
+ document.body.style.cursor = 'ns-resize';
83
+ window.addEventListener('mousemove', handleMouseMove);
84
+ window.addEventListener('mouseup', handleMouseUp);
85
+ };
86
+
87
+ const handleDoubleClick = () => {
88
+ setEditorHeight((prev) => (prev > 450 ? 320 : 580));
89
+ };
90
+
91
+ const insertFormatting = (before, after = '', defaultText = '') => {
92
+ const textarea = textareaRef.current;
93
+ if (!textarea) return;
94
+
95
+ const start = textarea.selectionStart;
96
+ const end = textarea.selectionEnd;
97
+ const selectedText = value.substring(start, end);
98
+ const textToInsert = selectedText || defaultText;
99
+
100
+ const newContent =
101
+ value.substring(0, start) +
102
+ before +
103
+ textToInsert +
104
+ after +
105
+ value.substring(end);
106
+
107
+ onChange({ target: { value: newContent } });
108
+
109
+ setTimeout(() => {
110
+ textarea.focus();
111
+ const newCursorStart = start + before.length;
112
+ const newCursorEnd = newCursorStart + textToInsert.length;
113
+ textarea.setSelectionRange(newCursorStart, newCursorEnd);
114
+ }, 0);
115
+ };
116
+
117
+ const insertLinePrefix = (prefix) => {
118
+ const textarea = textareaRef.current;
119
+ if (!textarea) return;
120
+
121
+ const start = textarea.selectionStart;
122
+ const end = textarea.selectionEnd;
123
+
124
+ // Find start of current line
125
+ const lastNewline = value.lastIndexOf('\n', start - 1);
126
+ const lineStart = lastNewline === -1 ? 0 : lastNewline + 1;
127
+
128
+ const selectedLines = value.substring(lineStart, end).split('\n');
129
+ const prefixedLines = selectedLines.map((line) => `${prefix}${line}`).join('\n');
130
+
131
+ const newContent =
132
+ value.substring(0, lineStart) +
133
+ prefixedLines +
134
+ value.substring(end);
135
+
136
+ onChange({ target: { value: newContent } });
137
+
138
+ setTimeout(() => {
139
+ textarea.focus();
140
+ textarea.setSelectionRange(
141
+ lineStart + prefix.length,
142
+ lineStart + prefixedLines.length
143
+ );
144
+ }, 0);
145
+ };
146
+
147
+ const handleKeyDown = (event) => {
148
+ // Shortcuts: Ctrl+B (Bold), Ctrl+I (Italic), Ctrl+K (Link)
149
+ if ((event.ctrlKey || event.metaKey) && event.key === 'b') {
150
+ event.preventDefault();
151
+ insertFormatting('**', '**', boldDefaultText);
152
+ } else if ((event.ctrlKey || event.metaKey) && event.key === 'i') {
153
+ event.preventDefault();
154
+ insertFormatting('*', '*', italicDefaultText);
155
+ } else if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
156
+ event.preventDefault();
157
+ insertFormatting('[', '](https://)', linkDefaultText);
158
+ } else if (event.key === 'Enter') {
159
+ // Auto list continuation
160
+ const textarea = textareaRef.current;
161
+ if (!textarea) return;
162
+
163
+ const start = textarea.selectionStart;
164
+ const lastNewline = value.lastIndexOf('\n', start - 1);
165
+ const lineStart = lastNewline === -1 ? 0 : lastNewline + 1;
166
+ const currentLine = value.substring(lineStart, start);
167
+
168
+ const bulletMatch = currentLine.match(/^(\s*)-\s+/);
169
+ const orderedMatch = currentLine.match(/^(\s*)(\d+)\.\s+/);
170
+
171
+ if (bulletMatch) {
172
+ if (currentLine.trim() === '-') {
173
+ // Empty bullet -> clear line
174
+ event.preventDefault();
175
+ const newContent = value.substring(0, lineStart) + value.substring(start);
176
+ onChange({ target: { value: newContent } });
177
+ setTimeout(() => {
178
+ textarea.setSelectionRange(lineStart, lineStart);
179
+ }, 0);
180
+ } else {
181
+ event.preventDefault();
182
+ const indent = bulletMatch[1];
183
+ const newText = `\n${indent}- `;
184
+ const newContent = value.substring(0, start) + newText + value.substring(start);
185
+ onChange({ target: { value: newContent } });
186
+ setTimeout(() => {
187
+ textarea.setSelectionRange(start + newText.length, start + newText.length);
188
+ }, 0);
189
+ }
190
+ } else if (orderedMatch) {
191
+ const num = parseInt(orderedMatch[2], 10);
192
+ if (currentLine.trim() === `${num}.`) {
193
+ // Empty item -> clear line
194
+ event.preventDefault();
195
+ const newContent = value.substring(0, lineStart) + value.substring(start);
196
+ onChange({ target: { value: newContent } });
197
+ setTimeout(() => {
198
+ textarea.setSelectionRange(lineStart, lineStart);
199
+ }, 0);
200
+ } else {
201
+ event.preventDefault();
202
+ const indent = orderedMatch[1];
203
+ const newText = `\n${indent}${num + 1}. `;
204
+ const newContent = value.substring(0, start) + newText + value.substring(start);
205
+ onChange({ target: { value: newContent } });
206
+ setTimeout(() => {
207
+ textarea.setSelectionRange(start + newText.length, start + newText.length);
208
+ }, 0);
209
+ }
210
+ }
211
+ }
212
+ };
213
+
214
+ const insertTable = () => {
215
+ const [col1, col2, col3] = tableColumnLabels;
216
+ const [row1, row2, row3] = tableRowLabels;
217
+ const tableTemplate = `\n| ${col1} | ${col2} | ${col3} |\n| --- | --- | --- |\n| ${row1} | ${row2} | ${row3} |\n`;
218
+ insertFormatting(tableTemplate, '');
219
+ };
220
+
221
+ return (
222
+ <div
223
+ className={styles.editorContainer}
224
+ style={activeColor ? { '--markdown-editor-active': activeColor } : undefined}
225
+ >
226
+ <div className={styles.toolbar}>
227
+ <div className={styles.toolbarGroup}>
228
+ <Tooltip title={boldTooltip} arrow>
229
+ <button
230
+ type="button"
231
+ className={styles.toolButton}
232
+ onClick={() => insertFormatting('**', '**', boldDefaultText)}
233
+ disabled={viewMode === 'preview'}
234
+ >
235
+ <Bold size={16} />
236
+ </button>
237
+ </Tooltip>
238
+
239
+ <Tooltip title={italicTooltip} arrow>
240
+ <button
241
+ type="button"
242
+ className={styles.toolButton}
243
+ onClick={() => insertFormatting('*', '*', italicDefaultText)}
244
+ disabled={viewMode === 'preview'}
245
+ >
246
+ <Italic size={16} />
247
+ </button>
248
+ </Tooltip>
249
+
250
+ <div className={styles.toolbarDivider} />
251
+
252
+ <Tooltip title={heading2Tooltip} arrow>
253
+ <button
254
+ type="button"
255
+ className={styles.toolButton}
256
+ onClick={() => insertLinePrefix('## ')}
257
+ disabled={viewMode === 'preview'}
258
+ >
259
+ <Heading2 size={16} />
260
+ </button>
261
+ </Tooltip>
262
+
263
+ <Tooltip title={heading3Tooltip} arrow>
264
+ <button
265
+ type="button"
266
+ className={styles.toolButton}
267
+ onClick={() => insertLinePrefix('### ')}
268
+ disabled={viewMode === 'preview'}
269
+ >
270
+ <Heading3 size={16} />
271
+ </button>
272
+ </Tooltip>
273
+
274
+ <div className={styles.toolbarDivider} />
275
+
276
+ <Tooltip title={bulletListTooltip} arrow>
277
+ <button
278
+ type="button"
279
+ className={styles.toolButton}
280
+ onClick={() => insertLinePrefix('- ')}
281
+ disabled={viewMode === 'preview'}
282
+ >
283
+ <List size={16} />
284
+ </button>
285
+ </Tooltip>
286
+
287
+ <Tooltip title={orderedListTooltip} arrow>
288
+ <button
289
+ type="button"
290
+ className={styles.toolButton}
291
+ onClick={() => insertLinePrefix('1. ')}
292
+ disabled={viewMode === 'preview'}
293
+ >
294
+ <ListOrdered size={16} />
295
+ </button>
296
+ </Tooltip>
297
+
298
+ <Tooltip title={quoteTooltip} arrow>
299
+ <button
300
+ type="button"
301
+ className={styles.toolButton}
302
+ onClick={() => insertLinePrefix('> ')}
303
+ disabled={viewMode === 'preview'}
304
+ >
305
+ <Quote size={16} />
306
+ </button>
307
+ </Tooltip>
308
+
309
+ <div className={styles.toolbarDivider} />
310
+
311
+ <Tooltip title={codeBlockTooltip} arrow>
312
+ <button
313
+ type="button"
314
+ className={styles.toolButton}
315
+ onClick={() => insertFormatting('```\n', '\n```', codeDefaultText)}
316
+ disabled={viewMode === 'preview'}
317
+ >
318
+ <Code size={16} />
319
+ </button>
320
+ </Tooltip>
321
+
322
+ <Tooltip title={linkTooltip} arrow>
323
+ <button
324
+ type="button"
325
+ className={styles.toolButton}
326
+ onClick={() => insertFormatting('[', '](https://)', linkDefaultText)}
327
+ disabled={viewMode === 'preview'}
328
+ >
329
+ <LinkIcon size={16} />
330
+ </button>
331
+ </Tooltip>
332
+
333
+ <Tooltip title={tableTooltip} arrow>
334
+ <button
335
+ type="button"
336
+ className={styles.toolButton}
337
+ onClick={insertTable}
338
+ disabled={viewMode === 'preview'}
339
+ >
340
+ <TableIcon size={16} />
341
+ </button>
342
+ </Tooltip>
343
+ </div>
344
+
345
+ <div className={styles.modeToggle}>
346
+ <button
347
+ type="button"
348
+ className={`${styles.modeButton} ${viewMode === 'write' ? styles.modeButtonActive : ''}`}
349
+ onClick={() => setViewMode('write')}
350
+ >
351
+ <Edit3 size={13} />
352
+ {editorTab}
353
+ </button>
354
+ <button
355
+ type="button"
356
+ className={`${styles.modeButton} ${viewMode === 'split' ? styles.modeButtonActive : ''}`}
357
+ onClick={() => setViewMode('split')}
358
+ >
359
+ <Columns size={13} />
360
+ {splitTab}
361
+ </button>
362
+ <button
363
+ type="button"
364
+ className={`${styles.modeButton} ${viewMode === 'preview' ? styles.modeButtonActive : ''}`}
365
+ onClick={() => setViewMode('preview')}
366
+ >
367
+ <Eye size={13} />
368
+ {previewTab}
369
+ </button>
370
+ </div>
371
+ </div>
372
+
373
+ <div className={styles.contentArea} style={{ height: `${editorHeight}px` }}>
374
+ {viewMode === 'write' && (
375
+ <textarea
376
+ ref={textareaRef}
377
+ className={styles.textarea}
378
+ value={value}
379
+ onChange={onChange}
380
+ onKeyDown={handleKeyDown}
381
+ placeholder={placeholder}
382
+ spellCheck={false}
383
+ />
384
+ )}
385
+
386
+ {viewMode === 'split' && (
387
+ <div className={styles.splitArea}>
388
+ <textarea
389
+ ref={textareaRef}
390
+ className={`${styles.textarea} ${styles.splitEditor}`}
391
+ value={value}
392
+ onChange={onChange}
393
+ onKeyDown={handleKeyDown}
394
+ placeholder={placeholder}
395
+ spellCheck={false}
396
+ />
397
+ <div className={styles.previewPane}>
398
+ {value.trim() ? (
399
+ <MarkdownContent>{value}</MarkdownContent>
400
+ ) : (
401
+ <span className={styles.emptyPreview}>{splitEmptyPreview}</span>
402
+ )}
403
+ </div>
404
+ </div>
405
+ )}
406
+
407
+ {viewMode === 'preview' && (
408
+ <div className={styles.previewPane}>
409
+ {value.trim() ? (
410
+ <MarkdownContent>{value}</MarkdownContent>
411
+ ) : (
412
+ <span className={styles.emptyPreview}>{previewEmptyPreview}</span>
413
+ )}
414
+ </div>
415
+ )}
416
+ </div>
417
+
418
+ <div
419
+ className={`${styles.resizeHandle} ${isDragging ? styles.resizeHandleDragging : ''}`}
420
+ onMouseDown={handleMouseDown}
421
+ onDoubleClick={handleDoubleClick}
422
+ title={resizeHandleTitle}
423
+ >
424
+ <div className={styles.gripPill} />
425
+ </div>
426
+ </div>
427
+ );
428
+ };
429
+
430
+ export default MarkdownEditor;