Skip to content
14 changes: 14 additions & 0 deletions packages/react/storybook/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import type {Preview} from '@storybook/react-vite'

const preview: Preview = {
globalTypes: {
showPlainValue: {
name: 'Plain Value',
description: 'Toggle plain value panel',
defaultValue: 'show',
toolbar: {
icon: 'eye',
items: [
{value: 'show', icon: 'eye'},
{value: 'hide', icon: 'eyeclose'},
],
},
},
},
parameters: {
controls: {
hideNoControlsWarning: true,
Expand Down
177 changes: 87 additions & 90 deletions packages/react/storybook/src/pages/Drag/Drag.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {MarkedInput} from '@markput/react'
import type {MarkProps, Option} from '@markput/react'
import {MarkedInput, useMark} from '@markput/react'
import type {MarkProps, MarkedInputProps, Option} from '@markput/react'
import type {Meta, StoryObj} from '@storybook/react-vite'
import type {CSSProperties, ReactNode} from 'react'
import {useState} from 'react'

import {Text} from '../../shared/components/Text'
import {DRAG_MARKDOWN} from '../../shared/lib/sampleTexts'
import {withPlainValue} from '../../shared/lib/withPlainValue'
import {markdownOptions} from '../Nested/MarkdownOptions'

export default {
Expand Down Expand Up @@ -72,114 +73,120 @@ const todoOptions: Option<TodoMarkProps>[] = [
}),
},
{
markup: '- [ ] __nested__\n\n',
mark: (props: MarkProps) => ({...props, todo: 'pending' as const, style: {display: 'block'} as CSSProperties}),
},
{
markup: '- [x] __nested__\n\n',
mark: (props: MarkProps) => ({
...props,
todo: 'done' as const,
style: {display: 'block', textDecoration: 'line-through', opacity: 0.5} as CSSProperties,
}),
},
{
markup: '\t- [ ] __nested__\n\n',
mark: (props: MarkProps) => ({
...props,
todo: 'pending' as const,
style: {display: 'block', paddingLeft: '1.5em'} as CSSProperties,
}),
markup: '- [__value__] __nested__\n',
mark: (props: MarkProps) => {
const isDone = props.value === 'x'
return {
...props,
todo: isDone ? 'done' : 'pending',
style: {
display: 'block',
opacity: isDone ? 0.55 : undefined,
} as CSSProperties,
}
},
},
{
markup: '\t- [x] __nested__\n\n',
mark: (props: MarkProps) => ({
...props,
todo: 'done' as const,
style: {
display: 'block',
paddingLeft: '1.5em',
textDecoration: 'line-through',
opacity: 0.5,
} as CSSProperties,
}),
markup: '\t- [__value__] __nested__\n',
mark: (props: MarkProps) => {
const isDone = props.value === 'x'
return {
...props,
todo: isDone ? 'done' : 'pending',
style: {
display: 'block',
paddingLeft: '22px',
borderLeft: '2px solid #d0d7de',
opacity: isDone ? 0.55 : undefined,
} as CSSProperties,
}
},
},
{
markup: '\t\t- [ ] __nested__\n\n',
mark: (props: MarkProps) => ({
...props,
todo: 'pending' as const,
style: {display: 'block', paddingLeft: '3em'} as CSSProperties,
}),
markup: '\t\t- [__value__] __nested__\n',
mark: (props: MarkProps) => {
const isDone = props.value === 'x'
return {
...props,
todo: isDone ? 'done' : 'pending',
style: {
display: 'block',
paddingLeft: '22px',
marginLeft: '24px',
borderLeft: '2px solid #d0d7de',
opacity: isDone ? 0.55 : undefined,
} as CSSProperties,
}
},
},
{
markup: '\t\t- [x] __nested__\n\n',
markup: '> __nested__\n\n',
mark: (props: MarkProps) => ({
...props,
todo: 'done' as const,
style: {
display: 'block',
paddingLeft: '3em',
textDecoration: 'line-through',
opacity: 0.5,
fontSize: '0.85em',
color: '#888',
fontStyle: 'italic',
marginTop: 16,
} as CSSProperties,
}),
},
{
markup: '> __nested__\n\n',
mark: (props: MarkProps) => ({
...props,
style: {display: 'block', fontSize: '0.85em', color: '#888', fontStyle: 'italic'} as CSSProperties,
}),
},
]

const TodoMark = ({children, value, style, todo}: TodoMarkProps) => (
<span style={{...style, margin: '0 1px'}}>
{todo && <span style={{marginRight: 6}}>{todo === 'done' ? '\u2611' : '\u2610'}</span>}
{children || value}
</span>
)
const TodoMark = ({nested, style, todo}: TodoMarkProps) => {
const mark = useMark()
const [isDone, setIsDone] = useState(mark.value === 'x')

return (
<span
style={{
...style,
margin: '0 1px',
opacity: isDone ? 0.55 : undefined,
}}
>
{todo !== undefined && (
<input
type="checkbox"
checked={isDone}
onChange={() => {
const next = !isDone
setIsDone(next)
mark.value = next ? 'x' : ' '
}}
disabled={mark.readOnly}
style={{marginRight: 6}}
/>
)}
{nested}
</span>
)
}

const TODO_VALUE = `# \u{1F4CB} Project Launch Checklist

- [ ] Design Phase

\t- [ ] Create wireframes

\t- [x] Define color palette

\t- [ ] Design component library

- [x] Research

\t- [x] Analyze competitors

\t- [x] User interviews

\t\t- [x] Draft interview questions

\t\t- [x] Schedule 5 sessions

- [ ] Development

\t- [ ] Set up CI/CD pipeline

\t- [x] Write unit tests

\t- [ ] API integration

\t\t- [ ] Auth endpoints

\t\t- [ ] Data sync

- [ ] Launch

\t- [ ] Final QA pass

\t- [ ] Deploy to production
> \u2610 = pending \u2611 = done

> \u2610 = pending \u2611 = done`
`

// ─── Test helper stories (used by Drag.spec.tsx) ─────────────────────────────

Expand Down Expand Up @@ -229,22 +236,12 @@ export const ReadOnlyDrag: Story = {

// ─── Todo list (all marks include \n\n) ───────────────────────────────────────

export const TodoList: Story = {
render: () => {
const [value, setValue] = useState(TODO_VALUE)

return (
<div style={{maxWidth: 700, margin: '0 auto', paddingLeft: 52}}>
<MarkedInput
Mark={TodoMark}
options={todoOptions}
value={value}
onChange={setValue}
drag
style={{minHeight: 200, padding: 12, border: '1px solid #e0e0e0', borderRadius: 8}}
/>
<Text label="Raw value:" value={value} />
</div>
)
export const TodoList: StoryObj<MarkedInputProps<TodoMarkProps>> = {
decorators: [withPlainValue],
args: {
Mark: TodoMark,
options: todoOptions,
value: TODO_VALUE,
drag: true,
},
}
89 changes: 78 additions & 11 deletions packages/react/storybook/src/shared/components/Text/Text.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,79 @@
pre {
font-family:
Apple-System,
Arial,
Helvetica,
PingFang SC,
Hiragino Sans GB,
Microsoft YaHei,
STXihei,
sans-serif;
font-size: 14px;
.text-container {
border: 1px solid #d0d7de;
border-radius: 6px;
overflow: hidden;
}

.text-header {
background: #f0f3f6;
padding: 6px 16px;
border-bottom: 1px solid #d0d7de;
}

.text-label {
font-size: 12px;
font-weight: 600;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.06em;
font-family: Arial, Helvetica, sans-serif;
}

.text-label::before {
content: '</> ';
color: #0550ae;
font-style: normal;
letter-spacing: 0;
}

.text-container.text-inset {
border: none;
border-left: 1px solid #d0d7de;
border-radius: 0;
height: 100%;
background: #f6f8fa;
}

.text-container.text-inset .text-header {
background: transparent;
border-bottom-color: #d0d7de;
}

.text-container.text-inset .text-pre {
background: transparent;
}

.tok-heading-prefix {
color: #8b949e;
}
.tok-heading-text {
color: #0550ae;
font-weight: 600;
}
.tok-bullet {
color: #8b949e;
}
.tok-checked {
color: #1a7f37;
font-weight: 600;
}
.tok-unchecked {
color: #8b949e;
}
.tok-blockquote {
color: #8b949e;
font-style: italic;
}

.text-pre {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 13px;
background: #f6f8fa;
padding: 12px 12px;
overflow-x: auto;
line-height: 1.5;
color: #24292f;
margin: 0;
white-space: pre-wrap;
word-break: break-word;
}
16 changes: 10 additions & 6 deletions packages/react/storybook/src/shared/components/Text/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import './Text.css'
export interface TextProps {
value: string
label?: string
className?: string
}

export const Text = ({value, label}: TextProps) => (
<>
<br />
{label && <b>{label}</b>}
<pre>{value}</pre>
</>
export const Text = ({value, label, className}: TextProps) => (
<div className={`text-container${className ? ` ${className}` : ''}`}>
{label && (
<div className="text-header">
<span className="text-label">{label}</span>
</div>
)}
<pre className="text-pre">{value}</pre>
</div>
)
Loading
Loading