paris 0.18.1 → 0.19.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.
- package/CHANGELOG.md +6 -0
- package/package.json +15 -1
- package/src/stories/markdowneditor/FixedToolbar.module.scss +24 -0
- package/src/stories/markdowneditor/FixedToolbar.tsx +244 -0
- package/src/stories/markdowneditor/FloatingToolbar.module.scss +21 -0
- package/src/stories/markdowneditor/FloatingToolbar.tsx +94 -0
- package/src/stories/markdowneditor/LinkPopover.module.scss +124 -0
- package/src/stories/markdowneditor/LinkPopover.tsx +135 -0
- package/src/stories/markdowneditor/MarkdownEditor.module.scss +405 -0
- package/src/stories/markdowneditor/MarkdownEditor.stories.tsx +223 -0
- package/src/stories/markdowneditor/MarkdownEditor.tsx +123 -0
- package/src/stories/markdowneditor/MarkdownEditorContext.tsx +17 -0
- package/src/stories/markdowneditor/ToolbarButton.module.scss +35 -0
- package/src/stories/markdowneditor/ToolbarButton.tsx +52 -0
- package/src/stories/markdowneditor/features.ts +92 -0
- package/src/stories/markdowneditor/index.ts +11 -0
- package/src/stories/markdowneditor/useMarkdownEditor.ts +75 -0
- package/src/stories/tabs/Tabs.module.scss +0 -2
- package/src/stories/tabs/Tabs.tsx +9 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/* ── MarkdownEditor styles ────────────────────────────────────
|
|
2
|
+
* Uses Paris design tokens (--pte-*) for consistent theming.
|
|
3
|
+
* Editor content styles mirror Markdown.module.scss but target
|
|
4
|
+
* native HTML elements rendered by Tiptap (not CSS module classes).
|
|
5
|
+
* ──────────────────────────────────────────────────────────── */
|
|
6
|
+
|
|
7
|
+
.root {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
width: 100%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ── Editor container (input-like wrapper) ─────────────────
|
|
14
|
+
.editorContainer {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
border-radius: var(--pte-borders-radius-rectangle);
|
|
18
|
+
background-color: var(--pte-new-colors-inputFill);
|
|
19
|
+
border: 1px solid var(--pte-new-colors-inputFill);
|
|
20
|
+
transition: var(--pte-animations-interaction);
|
|
21
|
+
|
|
22
|
+
&:focus-within {
|
|
23
|
+
outline: none;
|
|
24
|
+
border-color: var(--pte-new-colors-inputBorderFocus);
|
|
25
|
+
background-color: var(--pte-new-colors-inputFillFocus);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&[data-status='error'] {
|
|
29
|
+
border-color: var(--pte-new-colors-inputBorderNegative);
|
|
30
|
+
background-color: var(--pte-new-colors-inputFillNegative);
|
|
31
|
+
color: var(--pte-new-colors-contentNegative);
|
|
32
|
+
|
|
33
|
+
&:focus-within {
|
|
34
|
+
color: var(--pte-new-colors-contentPrimary);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&[data-status='success'] {
|
|
39
|
+
border-color: var(--pte-new-colors-backgroundPositive);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&[data-disabled='true'] {
|
|
43
|
+
background-color: var(--pte-new-colors-inputFillDisabled);
|
|
44
|
+
border-color: var(--pte-new-colors-inputFillDisabled);
|
|
45
|
+
color: var(--pte-new-colors-contentDisabled);
|
|
46
|
+
pointer-events: none;
|
|
47
|
+
cursor: default;
|
|
48
|
+
|
|
49
|
+
&:focus-within {
|
|
50
|
+
border-color: var(--pte-new-colors-inputFillDisabled) !important;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Editor content area ───────────────────────────────────
|
|
56
|
+
// Targets native HTML elements rendered by Tiptap (not CSS module classes).
|
|
57
|
+
// Mirrors the visual output of the read-only Markdown component.
|
|
58
|
+
.editorContent {
|
|
59
|
+
padding: 12px 16px;
|
|
60
|
+
min-height: 120px;
|
|
61
|
+
outline: none;
|
|
62
|
+
line-height: 1.7;
|
|
63
|
+
color: var(--pte-new-colors-contentPrimary);
|
|
64
|
+
font-size: var(--markdown-base-font-size, 14px);
|
|
65
|
+
|
|
66
|
+
// Tiptap root element
|
|
67
|
+
:global(.tiptap) {
|
|
68
|
+
outline: none;
|
|
69
|
+
min-height: inherit;
|
|
70
|
+
|
|
71
|
+
> *:first-child {
|
|
72
|
+
margin-top: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
> *:last-child {
|
|
76
|
+
margin-bottom: 0;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Placeholder ──────────────────────────────────────
|
|
81
|
+
:global(.tiptap p.is-editor-empty:first-child::before) {
|
|
82
|
+
content: attr(data-placeholder);
|
|
83
|
+
color: var(--pte-new-colors-contentTertiary);
|
|
84
|
+
pointer-events: none;
|
|
85
|
+
float: left;
|
|
86
|
+
height: 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Headings ─────────────────────────────────────────
|
|
90
|
+
h1,
|
|
91
|
+
h2,
|
|
92
|
+
h3,
|
|
93
|
+
h4,
|
|
94
|
+
h5,
|
|
95
|
+
h6 {
|
|
96
|
+
margin-top: 1.5em;
|
|
97
|
+
margin-bottom: 0.6em;
|
|
98
|
+
color: var(--pte-new-colors-contentPrimary);
|
|
99
|
+
line-height: 1.3;
|
|
100
|
+
|
|
101
|
+
&:first-child {
|
|
102
|
+
margin-top: 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
h1 {
|
|
107
|
+
font-size: var(--pte-new-typography-styles-headingLarge-fontSize, 32px);
|
|
108
|
+
font-weight: var(--pte-new-typography-styles-headingLarge-fontWeight, 700);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
h2 {
|
|
112
|
+
font-size: var(--pte-new-typography-styles-headingMedium-fontSize, 24px);
|
|
113
|
+
font-weight: var(--pte-new-typography-styles-headingMedium-fontWeight, 700);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
h3 {
|
|
117
|
+
font-size: var(--pte-new-typography-styles-headingSmall-fontSize, 20px);
|
|
118
|
+
font-weight: var(--pte-new-typography-styles-headingSmall-fontWeight, 700);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
h4 {
|
|
122
|
+
font-size: var(--pte-new-typography-styles-headingXSmall-fontSize, 18px);
|
|
123
|
+
font-weight: var(--pte-new-typography-styles-headingXSmall-fontWeight, 600);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
h5 {
|
|
127
|
+
font-size: var(--pte-new-typography-styles-headingXXSmall-fontSize, 16px);
|
|
128
|
+
font-weight: var(--pte-new-typography-styles-headingXXSmall-fontWeight, 600);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
h6 {
|
|
132
|
+
font-size: var(--pte-new-typography-styles-labelMedium-fontSize, 14px);
|
|
133
|
+
font-weight: var(--pte-new-typography-styles-labelMedium-fontWeight, 600);
|
|
134
|
+
text-transform: uppercase;
|
|
135
|
+
letter-spacing: 0.05em;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Paragraphs ───────────────────────────────────────
|
|
139
|
+
p {
|
|
140
|
+
margin-bottom: 1em;
|
|
141
|
+
line-height: 1.7;
|
|
142
|
+
|
|
143
|
+
&:last-child {
|
|
144
|
+
margin-bottom: 0;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Inline marks ─────────────────────────────────────
|
|
149
|
+
strong {
|
|
150
|
+
font-weight: 600;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
em {
|
|
154
|
+
font-style: italic;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
s {
|
|
158
|
+
text-decoration: line-through;
|
|
159
|
+
text-decoration-color: var(--pte-new-colors-contentTertiary);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── Links ────────────────────────────────────────────
|
|
163
|
+
a {
|
|
164
|
+
color: var(--pte-new-colors-contentAccent);
|
|
165
|
+
text-decoration: underline;
|
|
166
|
+
text-underline-offset: 2px;
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
|
|
169
|
+
&:hover {
|
|
170
|
+
text-decoration-color: var(--pte-new-colors-contentAccent);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Images ───────────────────────────────────────────
|
|
175
|
+
img {
|
|
176
|
+
max-width: 100%;
|
|
177
|
+
height: auto;
|
|
178
|
+
border-radius: var(--pte-new-borders-radius-rounded);
|
|
179
|
+
border: 1px solid var(--pte-new-colors-borderSubtle);
|
|
180
|
+
margin: 1em 0;
|
|
181
|
+
display: block;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── Blockquotes ──────────────────────────────────────
|
|
185
|
+
blockquote {
|
|
186
|
+
border-left: 3px solid var(--pte-new-colors-borderMedium);
|
|
187
|
+
padding: 8px 16px;
|
|
188
|
+
margin: 1em 0;
|
|
189
|
+
background-color: var(--pte-new-colors-backgroundSecondary);
|
|
190
|
+
border-radius: 0 var(--pte-new-borders-radius-rounded, 8px)
|
|
191
|
+
var(--pte-new-borders-radius-rounded, 8px) 0;
|
|
192
|
+
color: var(--pte-new-colors-contentSecondary);
|
|
193
|
+
|
|
194
|
+
blockquote {
|
|
195
|
+
margin: 0.5em 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
p {
|
|
199
|
+
margin-bottom: 0.5em;
|
|
200
|
+
|
|
201
|
+
&:last-child {
|
|
202
|
+
margin-bottom: 0;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ── Horizontal rules ─────────────────────────────────
|
|
208
|
+
hr {
|
|
209
|
+
border: none;
|
|
210
|
+
border-top: 1px solid var(--pte-new-colors-borderMedium);
|
|
211
|
+
margin: 1.5em 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ── Lists ────────────────────────────────────────────
|
|
215
|
+
ul,
|
|
216
|
+
ol {
|
|
217
|
+
margin: 0.5em 0 1em;
|
|
218
|
+
padding-left: 1.5em;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
ul {
|
|
222
|
+
list-style-type: disc;
|
|
223
|
+
|
|
224
|
+
ul {
|
|
225
|
+
list-style-type: circle;
|
|
226
|
+
margin: 0.25em 0;
|
|
227
|
+
|
|
228
|
+
ul {
|
|
229
|
+
list-style-type: square;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
ol {
|
|
235
|
+
list-style-type: decimal;
|
|
236
|
+
|
|
237
|
+
ol {
|
|
238
|
+
list-style-type: lower-alpha;
|
|
239
|
+
margin: 0.25em 0;
|
|
240
|
+
|
|
241
|
+
ol {
|
|
242
|
+
list-style-type: lower-roman;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
li {
|
|
248
|
+
margin-bottom: 0.25em;
|
|
249
|
+
line-height: 1.7;
|
|
250
|
+
|
|
251
|
+
p {
|
|
252
|
+
margin-bottom: 0.25em;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── Task lists (Tiptap v3 uses data-checked on li) ────
|
|
257
|
+
ul[data-type='taskList'] {
|
|
258
|
+
list-style: none;
|
|
259
|
+
padding-left: 0;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
li[data-checked] {
|
|
263
|
+
display: flex;
|
|
264
|
+
flex-direction: row;
|
|
265
|
+
align-items: baseline;
|
|
266
|
+
gap: 8px;
|
|
267
|
+
|
|
268
|
+
> label {
|
|
269
|
+
flex-shrink: 0;
|
|
270
|
+
|
|
271
|
+
input[type='checkbox'] {
|
|
272
|
+
appearance: none;
|
|
273
|
+
width: 14px;
|
|
274
|
+
height: 14px;
|
|
275
|
+
border: 2px solid var(--pte-new-colors-contentTertiary);
|
|
276
|
+
border-radius: var(--pte-borders-radius-rectangle);
|
|
277
|
+
background: transparent;
|
|
278
|
+
cursor: pointer;
|
|
279
|
+
position: relative;
|
|
280
|
+
top: 2px;
|
|
281
|
+
transition: var(--pte-animations-interaction);
|
|
282
|
+
|
|
283
|
+
&:checked {
|
|
284
|
+
// Matches the Paris Checkbox: filled SVG covers the full 14x14 area
|
|
285
|
+
&::after {
|
|
286
|
+
content: '';
|
|
287
|
+
position: absolute;
|
|
288
|
+
inset: -2px;
|
|
289
|
+
background-color: var(--pte-new-colors-contentPrimary);
|
|
290
|
+
mask-image: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 14 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.333374 0.333252V13.6666H13.6667V0.333252H0.333374ZM6.00004 10.3999L2.26672 6.66658L3.66671 5.26658L5.93339 7.53325L10.2 3.26658L11.6001 4.66659L6.00004 10.3999Z' fill='black'/%3E%3C/svg%3E");
|
|
291
|
+
mask-size: contain;
|
|
292
|
+
mask-repeat: no-repeat;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
> div {
|
|
299
|
+
flex: 1;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ── Inline code ──────────────────────────────────────
|
|
304
|
+
code {
|
|
305
|
+
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', ui-monospace, monospace;
|
|
306
|
+
font-size: 0.875em;
|
|
307
|
+
padding: 2px 6px;
|
|
308
|
+
border-radius: 6px;
|
|
309
|
+
background-color: var(--pte-new-colors-backgroundSecondary);
|
|
310
|
+
border: 1px solid var(--pte-new-colors-borderSubtle);
|
|
311
|
+
color: var(--pte-new-colors-contentPrimary);
|
|
312
|
+
word-break: break-word;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ── Code blocks ──────────────────────────────────────
|
|
316
|
+
pre {
|
|
317
|
+
margin: 1em 0;
|
|
318
|
+
padding: 0;
|
|
319
|
+
background: transparent;
|
|
320
|
+
overflow: visible;
|
|
321
|
+
|
|
322
|
+
code {
|
|
323
|
+
display: block;
|
|
324
|
+
padding: 16px;
|
|
325
|
+
overflow-x: auto;
|
|
326
|
+
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', ui-monospace, monospace;
|
|
327
|
+
font-size: 13px;
|
|
328
|
+
line-height: 1.6;
|
|
329
|
+
tab-size: 4;
|
|
330
|
+
white-space: pre;
|
|
331
|
+
border-radius: var(--pte-new-borders-radius-rounded, 8px);
|
|
332
|
+
background-color: var(--pte-new-colors-backgroundSecondary);
|
|
333
|
+
border: 1px solid var(--pte-new-colors-borderSubtle);
|
|
334
|
+
color: var(--pte-new-colors-contentPrimary);
|
|
335
|
+
|
|
336
|
+
&::-webkit-scrollbar {
|
|
337
|
+
height: 6px;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
&::-webkit-scrollbar-track {
|
|
341
|
+
background: transparent;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
&::-webkit-scrollbar-thumb {
|
|
345
|
+
background-color: var(--pte-new-colors-borderMedium);
|
|
346
|
+
border-radius: 3px;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ── Tables ───────────────────────────────────────────
|
|
352
|
+
table {
|
|
353
|
+
width: 100%;
|
|
354
|
+
border-collapse: collapse;
|
|
355
|
+
margin: 1em 0;
|
|
356
|
+
font-size: var(--pte-new-typography-paragraphSmall-fontSize, 13px);
|
|
357
|
+
border-radius: var(--pte-new-borders-radius-rounded, 8px);
|
|
358
|
+
border: 1px solid var(--pte-new-colors-borderSubtle);
|
|
359
|
+
overflow: hidden;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
thead {
|
|
363
|
+
background-color: var(--pte-new-colors-backgroundSecondary);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
th {
|
|
367
|
+
padding: 10px 16px;
|
|
368
|
+
text-align: left;
|
|
369
|
+
border-bottom: 1px solid var(--pte-new-colors-borderMedium);
|
|
370
|
+
white-space: nowrap;
|
|
371
|
+
color: var(--pte-new-colors-contentSecondary);
|
|
372
|
+
font-size: var(--pte-new-typography-styles-labelXSmall-fontSize, 11px);
|
|
373
|
+
font-weight: 600;
|
|
374
|
+
text-transform: uppercase;
|
|
375
|
+
letter-spacing: 0.05em;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
td {
|
|
379
|
+
padding: 10px 16px;
|
|
380
|
+
border-bottom: 1px solid var(--pte-new-colors-borderSubtle);
|
|
381
|
+
color: var(--pte-new-colors-contentPrimary);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
tr:last-child td {
|
|
385
|
+
border-bottom: none;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
tbody tr {
|
|
389
|
+
transition: background-color 0.15s ease;
|
|
390
|
+
|
|
391
|
+
&:hover {
|
|
392
|
+
background-color: var(--pte-new-colors-backgroundSecondary);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ── Tiptap table selection states ────────────────────
|
|
397
|
+
:global(.selectedCell) {
|
|
398
|
+
background-color: var(--pte-new-colors-backgroundAccentSubtle, rgba(99, 102, 241, 0.1));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ── Selection highlight ──────────────────────────────
|
|
402
|
+
::selection {
|
|
403
|
+
background-color: var(--pte-new-colors-backgroundAccentSubtle, rgba(99, 102, 241, 0.2));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/nextjs-vite';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { FixedToolbar } from './FixedToolbar';
|
|
4
|
+
import { FloatingToolbar } from './FloatingToolbar';
|
|
5
|
+
import { MarkdownEditor } from './MarkdownEditor';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof MarkdownEditor> = {
|
|
8
|
+
title: 'Content/MarkdownEditor',
|
|
9
|
+
component: MarkdownEditor,
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
argTypes: {
|
|
12
|
+
size: {
|
|
13
|
+
control: 'select',
|
|
14
|
+
options: ['paragraphLarge', 'paragraphMedium', 'paragraphSmall', 'paragraphXSmall', 'paragraphXXSmall'],
|
|
15
|
+
},
|
|
16
|
+
status: {
|
|
17
|
+
control: 'select',
|
|
18
|
+
options: ['default', 'error', 'success'],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
type Story = StoryObj<typeof MarkdownEditor>;
|
|
25
|
+
|
|
26
|
+
const sampleMarkdown = `# Getting Started
|
|
27
|
+
|
|
28
|
+
This is a **WYSIWYG** markdown editor built on *Tiptap*.
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- Bold, italic, and ~~strikethrough~~
|
|
33
|
+
- [Links](https://paris.slingshot.fm) and \`inline code\`
|
|
34
|
+
- Headings, blockquotes, and horizontal rules
|
|
35
|
+
|
|
36
|
+
> Blockquotes render with Paris styling.
|
|
37
|
+
|
|
38
|
+
### Task Lists
|
|
39
|
+
|
|
40
|
+
- [x] Set up Tiptap
|
|
41
|
+
- [x] Add markdown serialization
|
|
42
|
+
- [ ] Style with Paris tokens
|
|
43
|
+
|
|
44
|
+
\`\`\`typescript
|
|
45
|
+
const [md, setMd] = useState('');
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
That's it! The editor outputs **markdown** on every change.`;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Default editor with both FixedToolbar and FloatingToolbar.
|
|
54
|
+
* All features enabled.
|
|
55
|
+
*/
|
|
56
|
+
export const Default: Story = {
|
|
57
|
+
render: (args) => {
|
|
58
|
+
const [value, setValue] = useState(sampleMarkdown);
|
|
59
|
+
return (
|
|
60
|
+
<div style={{ maxWidth: 700 }}>
|
|
61
|
+
<MarkdownEditor {...args} value={value} onChange={setValue}>
|
|
62
|
+
<FixedToolbar />
|
|
63
|
+
<FloatingToolbar />
|
|
64
|
+
</MarkdownEditor>
|
|
65
|
+
<details style={{ marginTop: 16 }}>
|
|
66
|
+
<summary
|
|
67
|
+
style={{ cursor: 'pointer', fontSize: 12, color: 'var(--pte-new-colors-contentTertiary)' }}
|
|
68
|
+
>
|
|
69
|
+
Markdown output
|
|
70
|
+
</summary>
|
|
71
|
+
<pre
|
|
72
|
+
style={{
|
|
73
|
+
marginTop: 8,
|
|
74
|
+
padding: 12,
|
|
75
|
+
fontSize: 12,
|
|
76
|
+
background: 'var(--pte-new-colors-backgroundSecondary)',
|
|
77
|
+
borderRadius: 8,
|
|
78
|
+
overflow: 'auto',
|
|
79
|
+
whiteSpace: 'pre-wrap',
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
{value}
|
|
83
|
+
</pre>
|
|
84
|
+
</details>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
args: {
|
|
89
|
+
placeholder: 'Start writing...',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Editor with only the FloatingToolbar (appears on text selection).
|
|
95
|
+
* Clean editing surface without a fixed toolbar.
|
|
96
|
+
*/
|
|
97
|
+
export const FloatingOnly: Story = {
|
|
98
|
+
render: (args) => {
|
|
99
|
+
const [value, setValue] = useState('Select some text to see the floating toolbar.');
|
|
100
|
+
return (
|
|
101
|
+
<div style={{ maxWidth: 700 }}>
|
|
102
|
+
<MarkdownEditor {...args} value={value} onChange={setValue}>
|
|
103
|
+
<FloatingToolbar />
|
|
104
|
+
</MarkdownEditor>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
args: {
|
|
109
|
+
placeholder: 'Start writing...',
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Editor with only the FixedToolbar.
|
|
115
|
+
*/
|
|
116
|
+
export const FixedOnly: Story = {
|
|
117
|
+
render: (args) => {
|
|
118
|
+
const [value, setValue] = useState('');
|
|
119
|
+
return (
|
|
120
|
+
<div style={{ maxWidth: 700 }}>
|
|
121
|
+
<MarkdownEditor {...args} value={value} onChange={setValue}>
|
|
122
|
+
<FixedToolbar />
|
|
123
|
+
</MarkdownEditor>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
args: {
|
|
128
|
+
placeholder: 'Start writing...',
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Editor with a limited feature set — only bold, italic, heading, and link.
|
|
134
|
+
* Toolbar only shows buttons for enabled features.
|
|
135
|
+
*/
|
|
136
|
+
export const LimitedFeatures: Story = {
|
|
137
|
+
render: (args) => {
|
|
138
|
+
const [value, setValue] = useState('Only **bold**, *italic*, headings, and links are available.');
|
|
139
|
+
return (
|
|
140
|
+
<div style={{ maxWidth: 700 }}>
|
|
141
|
+
<MarkdownEditor {...args} value={value} onChange={setValue}>
|
|
142
|
+
<FixedToolbar />
|
|
143
|
+
<FloatingToolbar />
|
|
144
|
+
</MarkdownEditor>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
},
|
|
148
|
+
args: {
|
|
149
|
+
features: ['bold', 'italic', 'heading', 'link'],
|
|
150
|
+
placeholder: 'Limited formatting...',
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Read-only editor with pre-filled content.
|
|
156
|
+
* The editor is not editable — useful for preview modes.
|
|
157
|
+
*/
|
|
158
|
+
export const ReadOnly: Story = {
|
|
159
|
+
render: (args) => {
|
|
160
|
+
return (
|
|
161
|
+
<div style={{ maxWidth: 700 }}>
|
|
162
|
+
<MarkdownEditor {...args} value={sampleMarkdown} editable={false} />
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Editor with error status — shows error border styling.
|
|
170
|
+
*/
|
|
171
|
+
export const ErrorStatus: Story = {
|
|
172
|
+
render: (args) => {
|
|
173
|
+
const [value, setValue] = useState('');
|
|
174
|
+
return (
|
|
175
|
+
<div style={{ maxWidth: 700 }}>
|
|
176
|
+
<MarkdownEditor {...args} value={value} onChange={setValue} status="error">
|
|
177
|
+
<FixedToolbar />
|
|
178
|
+
</MarkdownEditor>
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
},
|
|
182
|
+
args: {
|
|
183
|
+
placeholder: 'This field has an error...',
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Editor with no toolbar — users rely on keyboard shortcuts and
|
|
189
|
+
* markdown input rules (e.g., type `## ` for heading 2).
|
|
190
|
+
*/
|
|
191
|
+
export const NoToolbar: Story = {
|
|
192
|
+
render: (args) => {
|
|
193
|
+
const [value, setValue] = useState('');
|
|
194
|
+
return (
|
|
195
|
+
<div style={{ maxWidth: 700 }}>
|
|
196
|
+
<MarkdownEditor {...args} value={value} onChange={setValue} />
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
},
|
|
200
|
+
args: {
|
|
201
|
+
placeholder: 'Type markdown shortcuts: # heading, **bold**, - list item...',
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Editor with custom placeholder text.
|
|
207
|
+
*/
|
|
208
|
+
export const WithPlaceholder: Story = {
|
|
209
|
+
render: (args) => {
|
|
210
|
+
const [value, setValue] = useState('');
|
|
211
|
+
return (
|
|
212
|
+
<div style={{ maxWidth: 700 }}>
|
|
213
|
+
<MarkdownEditor {...args} value={value} onChange={setValue}>
|
|
214
|
+
<FixedToolbar />
|
|
215
|
+
</MarkdownEditor>
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
},
|
|
219
|
+
args: {
|
|
220
|
+
placeholder: 'Write your thoughts here...',
|
|
221
|
+
size: 'paragraphMedium',
|
|
222
|
+
},
|
|
223
|
+
};
|