@fragments-sdk/ui 0.6.4 → 0.7.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/README.md +3 -3
- package/fragments.json +1 -1
- package/package.json +16 -3
- package/src/blocks/AIChat.block.ts +266 -0
- package/src/blocks/AccountSettings.block.ts +47 -0
- package/src/blocks/ActivityFeed.block.ts +38 -0
- package/src/blocks/AppShell.block.ts +175 -0
- package/src/blocks/CTABanner.block.ts +24 -0
- package/src/blocks/CardGrid.block.ts +22 -0
- package/src/blocks/ChatInterface.block.ts +87 -0
- package/src/blocks/ChatMessages.block.ts +35 -0
- package/src/blocks/CheckoutForm.block.ts +62 -0
- package/src/blocks/CodeExamples.block.ts +66 -0
- package/src/blocks/ConfirmDialog.block.ts +19 -0
- package/src/blocks/ContactForm.block.ts +28 -0
- package/src/blocks/ConversationWithHistory.block.ts +45 -0
- package/src/blocks/DashboardLayout.block.ts +73 -0
- package/src/blocks/DashboardNav.block.ts +183 -0
- package/src/blocks/DataTable.block.ts +29 -0
- package/src/blocks/EmptyState.block.ts +21 -0
- package/src/blocks/FAQSection.block.ts +35 -0
- package/src/blocks/FeatureGrid.block.ts +33 -0
- package/src/blocks/ForgotPassword.block.ts +26 -0
- package/src/blocks/FormLayout.block.ts +31 -0
- package/src/blocks/HeroSection.block.ts +31 -0
- package/src/blocks/InsetDashboardLayout.block.ts +79 -0
- package/src/blocks/LoginForm.block.ts +26 -0
- package/src/blocks/MetricDashboard.block.ts +38 -0
- package/src/blocks/NewsletterSignup.block.ts +26 -0
- package/src/blocks/NotificationList.block.ts +39 -0
- package/src/blocks/NotificationPreferences.block.ts +40 -0
- package/src/blocks/OrderSummary.block.ts +52 -0
- package/src/blocks/PricingComparison.block.ts +44 -0
- package/src/blocks/ProductCard.block.ts +33 -0
- package/src/blocks/ProfileEditForm.block.ts +51 -0
- package/src/blocks/RegistrationForm.block.ts +38 -0
- package/src/blocks/SearchResults.block.ts +39 -0
- package/src/blocks/SettingsPage.block.ts +58 -0
- package/src/blocks/SettingsPanel.block.ts +35 -0
- package/src/blocks/ShoppingCart.block.ts +46 -0
- package/src/blocks/StatsCard.block.ts +26 -0
- package/src/blocks/StreamingMessage.block.ts +24 -0
- package/src/blocks/TestimonialCard.block.ts +27 -0
- package/src/blocks/ThinkingStates.block.ts +48 -0
- package/src/blocks/UserProfileCard.block.ts +29 -0
- package/src/components/AppShell/AppShell.module.scss +2 -1
- package/src/components/Box/Box.fragment.tsx +110 -0
- package/src/components/Box/Box.module.scss +39 -0
- package/src/components/Box/index.tsx +68 -1
- package/src/components/Breadcrumbs/Breadcrumbs.fragment.tsx +162 -0
- package/src/components/Breadcrumbs/Breadcrumbs.module.scss +120 -0
- package/src/components/Breadcrumbs/index.tsx +202 -0
- package/src/components/Chip/Chip.fragment.tsx +175 -0
- package/src/components/Chip/Chip.module.scss +174 -0
- package/src/components/Chip/index.tsx +151 -0
- package/src/components/Markdown/Markdown.fragment.tsx +226 -0
- package/src/components/Markdown/Markdown.module.scss +219 -0
- package/src/components/Markdown/index.tsx +106 -0
- package/src/components/Message/Message.module.scss +0 -4
- package/src/components/Message/index.tsx +9 -2
- package/src/components/Prompt/index.tsx +2 -1
- package/src/components/Stack/Stack.fragment.tsx +16 -0
- package/src/components/Stack/Stack.module.scss +16 -0
- package/src/components/Stack/index.tsx +35 -1
- package/src/index.ts +17 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
@use '../../tokens/variables' as *;
|
|
2
|
+
@use '../../tokens/mixins' as *;
|
|
3
|
+
|
|
4
|
+
.chip {
|
|
5
|
+
@include button-reset;
|
|
6
|
+
@include interactive-base;
|
|
7
|
+
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: var(--fui-space-1, $fui-space-1);
|
|
11
|
+
border-radius: var(--fui-radius-full, $fui-radius-full);
|
|
12
|
+
font-family: var(--fui-font-sans, $fui-font-sans);
|
|
13
|
+
font-weight: var(--fui-font-weight-medium, $fui-font-weight-medium);
|
|
14
|
+
line-height: 1;
|
|
15
|
+
white-space: nowrap;
|
|
16
|
+
user-select: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Sizes
|
|
20
|
+
.sm {
|
|
21
|
+
height: var(--fui-button-height-sm, $fui-button-height-sm);
|
|
22
|
+
padding: 0 var(--fui-space-2, $fui-space-2);
|
|
23
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.md {
|
|
27
|
+
height: var(--fui-button-height-md, $fui-button-height-md);
|
|
28
|
+
padding: 0 var(--fui-space-3, $fui-space-3);
|
|
29
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Variants
|
|
33
|
+
.filled {
|
|
34
|
+
background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
|
|
35
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
36
|
+
|
|
37
|
+
&:hover:not(:disabled) {
|
|
38
|
+
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&:active:not(:disabled) {
|
|
42
|
+
background-color: var(--fui-bg-active, $fui-bg-active);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&.selected {
|
|
46
|
+
background-color: var(--fui-color-accent, $fui-color-accent);
|
|
47
|
+
color: var(--fui-text-inverse, $fui-text-inverse);
|
|
48
|
+
|
|
49
|
+
&:hover:not(:disabled) {
|
|
50
|
+
background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&:active:not(:disabled) {
|
|
54
|
+
background-color: var(--fui-color-accent-active, $fui-color-accent-active);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.outlined {
|
|
60
|
+
background-color: transparent;
|
|
61
|
+
color: var(--fui-text-primary, $fui-text-primary);
|
|
62
|
+
border: 1px solid var(--fui-border, $fui-border);
|
|
63
|
+
|
|
64
|
+
&:hover:not(:disabled) {
|
|
65
|
+
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
66
|
+
border-color: var(--fui-border-strong, $fui-border-strong);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&:active:not(:disabled) {
|
|
70
|
+
background-color: var(--fui-bg-active, $fui-bg-active);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
&.selected {
|
|
74
|
+
background-color: var(--fui-color-accent, $fui-color-accent);
|
|
75
|
+
color: var(--fui-text-inverse, $fui-text-inverse);
|
|
76
|
+
border-color: var(--fui-color-accent, $fui-color-accent);
|
|
77
|
+
|
|
78
|
+
&:hover:not(:disabled) {
|
|
79
|
+
background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
|
|
80
|
+
border-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
&:active:not(:disabled) {
|
|
84
|
+
background-color: var(--fui-color-accent-active, $fui-color-accent-active);
|
|
85
|
+
border-color: var(--fui-color-accent-active, $fui-color-accent-active);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.soft {
|
|
91
|
+
background-color: var(--fui-color-info-bg, $fui-color-info-bg);
|
|
92
|
+
color: var(--fui-color-info, $fui-color-info);
|
|
93
|
+
|
|
94
|
+
&:hover:not(:disabled) {
|
|
95
|
+
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&:active:not(:disabled) {
|
|
99
|
+
background-color: var(--fui-bg-active, $fui-bg-active);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&.selected {
|
|
103
|
+
background-color: var(--fui-color-accent, $fui-color-accent);
|
|
104
|
+
color: var(--fui-text-inverse, $fui-text-inverse);
|
|
105
|
+
|
|
106
|
+
&:hover:not(:disabled) {
|
|
107
|
+
background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
&:active:not(:disabled) {
|
|
111
|
+
background-color: var(--fui-color-accent-active, $fui-color-accent-active);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Avatar slot
|
|
117
|
+
.avatar {
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
margin-left: calc(-1 * var(--fui-space-1, $fui-space-1));
|
|
121
|
+
|
|
122
|
+
img {
|
|
123
|
+
width: var(--fui-icon-md, $fui-icon-md);
|
|
124
|
+
height: var(--fui-icon-md, $fui-icon-md);
|
|
125
|
+
border-radius: var(--fui-radius-full, $fui-radius-full);
|
|
126
|
+
object-fit: cover;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.sm & {
|
|
130
|
+
img {
|
|
131
|
+
width: var(--fui-icon-sm, $fui-icon-sm);
|
|
132
|
+
height: var(--fui-icon-sm, $fui-icon-sm);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Icon slot
|
|
138
|
+
.icon {
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Remove button
|
|
144
|
+
.remove {
|
|
145
|
+
@include button-reset;
|
|
146
|
+
@include interactive-base;
|
|
147
|
+
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
justify-content: center;
|
|
151
|
+
margin-right: calc(-1 * var(--fui-space-1, $fui-space-1));
|
|
152
|
+
padding: var(--fui-space-0-5, $fui-space-0-5);
|
|
153
|
+
font-size: var(--fui-font-size-sm, $fui-font-size-sm);
|
|
154
|
+
color: inherit;
|
|
155
|
+
opacity: 0.6;
|
|
156
|
+
line-height: 1;
|
|
157
|
+
border-radius: var(--fui-radius-full, $fui-radius-full);
|
|
158
|
+
|
|
159
|
+
.sm & {
|
|
160
|
+
font-size: var(--fui-font-size-xs, $fui-font-size-xs);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
&:hover {
|
|
164
|
+
opacity: 1;
|
|
165
|
+
background-color: var(--fui-bg-hover, $fui-bg-hover);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Chip.Group
|
|
170
|
+
.group {
|
|
171
|
+
display: flex;
|
|
172
|
+
flex-wrap: wrap;
|
|
173
|
+
gap: var(--fui-space-2, $fui-space-2);
|
|
174
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styles from './Chip.module.scss';
|
|
3
|
+
import '../../styles/globals.scss';
|
|
4
|
+
|
|
5
|
+
export interface ChipProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
/** Visual style variant */
|
|
8
|
+
variant?: 'filled' | 'outlined' | 'soft';
|
|
9
|
+
/** Size of the chip */
|
|
10
|
+
size?: 'sm' | 'md';
|
|
11
|
+
/** Whether the chip is selected */
|
|
12
|
+
selected?: boolean;
|
|
13
|
+
/** Icon element rendered before the label */
|
|
14
|
+
icon?: React.ReactNode;
|
|
15
|
+
/** Avatar element rendered before the label */
|
|
16
|
+
avatar?: React.ReactNode;
|
|
17
|
+
/** Makes chip removable. Called when X is clicked. */
|
|
18
|
+
onRemove?: () => void;
|
|
19
|
+
/** Value identifier used by Chip.Group */
|
|
20
|
+
value?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ChipGroupProps {
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
/** Controlled selected values */
|
|
26
|
+
value?: string[];
|
|
27
|
+
/** Default selected values (uncontrolled) */
|
|
28
|
+
defaultValue?: string[];
|
|
29
|
+
/** Called when selection changes */
|
|
30
|
+
onChange?: (value: string[]) => void;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ChipBase = React.forwardRef<HTMLButtonElement, ChipProps>(
|
|
35
|
+
function Chip(
|
|
36
|
+
{
|
|
37
|
+
children,
|
|
38
|
+
variant = 'filled',
|
|
39
|
+
size = 'md',
|
|
40
|
+
selected = false,
|
|
41
|
+
disabled = false,
|
|
42
|
+
icon,
|
|
43
|
+
avatar,
|
|
44
|
+
onRemove,
|
|
45
|
+
className,
|
|
46
|
+
onClick,
|
|
47
|
+
value: _value,
|
|
48
|
+
...htmlProps
|
|
49
|
+
},
|
|
50
|
+
ref
|
|
51
|
+
) {
|
|
52
|
+
const classes = [
|
|
53
|
+
styles.chip,
|
|
54
|
+
styles[size],
|
|
55
|
+
styles[variant],
|
|
56
|
+
selected && styles.selected,
|
|
57
|
+
className,
|
|
58
|
+
]
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.join(' ');
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<button
|
|
64
|
+
ref={ref}
|
|
65
|
+
type="button"
|
|
66
|
+
role="option"
|
|
67
|
+
aria-selected={selected}
|
|
68
|
+
disabled={disabled}
|
|
69
|
+
className={classes}
|
|
70
|
+
onClick={onClick}
|
|
71
|
+
{...htmlProps}
|
|
72
|
+
>
|
|
73
|
+
{avatar && (
|
|
74
|
+
<span className={styles.avatar} aria-hidden="true">
|
|
75
|
+
{avatar}
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
{icon && (
|
|
79
|
+
<span className={styles.icon} aria-hidden="true">
|
|
80
|
+
{icon}
|
|
81
|
+
</span>
|
|
82
|
+
)}
|
|
83
|
+
<span>{children}</span>
|
|
84
|
+
{onRemove && (
|
|
85
|
+
<span
|
|
86
|
+
role="button"
|
|
87
|
+
tabIndex={0}
|
|
88
|
+
aria-label={`Remove ${typeof children === 'string' ? children : 'chip'}`}
|
|
89
|
+
className={styles.remove}
|
|
90
|
+
onClick={(e) => {
|
|
91
|
+
e.stopPropagation();
|
|
92
|
+
onRemove();
|
|
93
|
+
}}
|
|
94
|
+
onKeyDown={(e) => {
|
|
95
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
e.stopPropagation();
|
|
98
|
+
onRemove();
|
|
99
|
+
}
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
×
|
|
103
|
+
</span>
|
|
104
|
+
)}
|
|
105
|
+
</button>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
function ChipGroupInner(
|
|
111
|
+
{ children, value: controlledValue, defaultValue = [], onChange, className }: ChipGroupProps,
|
|
112
|
+
ref: React.Ref<HTMLDivElement>
|
|
113
|
+
) {
|
|
114
|
+
const [internalValue, setInternalValue] = React.useState<string[]>(defaultValue);
|
|
115
|
+
const isControlled = controlledValue !== undefined;
|
|
116
|
+
const currentValue = isControlled ? controlledValue : internalValue;
|
|
117
|
+
|
|
118
|
+
const toggle = React.useCallback(
|
|
119
|
+
(chipValue: string) => {
|
|
120
|
+
const next = currentValue.includes(chipValue)
|
|
121
|
+
? currentValue.filter((v) => v !== chipValue)
|
|
122
|
+
: [...currentValue, chipValue];
|
|
123
|
+
|
|
124
|
+
if (!isControlled) {
|
|
125
|
+
setInternalValue(next);
|
|
126
|
+
}
|
|
127
|
+
onChange?.(next);
|
|
128
|
+
},
|
|
129
|
+
[currentValue, isControlled, onChange]
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const classes = [styles.group, className].filter(Boolean).join(' ');
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div ref={ref} role="listbox" aria-multiselectable="true" className={classes}>
|
|
136
|
+
{React.Children.map(children, (child) => {
|
|
137
|
+
if (!React.isValidElement<ChipProps>(child)) return child;
|
|
138
|
+
const chipValue = child.props.value ?? (typeof child.props.children === 'string' ? child.props.children : '');
|
|
139
|
+
return React.cloneElement(child, {
|
|
140
|
+
selected: currentValue.includes(chipValue),
|
|
141
|
+
onClick: () => toggle(chipValue),
|
|
142
|
+
} as Partial<ChipProps>);
|
|
143
|
+
})}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const ChipGroup = React.forwardRef<HTMLDivElement, ChipGroupProps>(ChipGroupInner);
|
|
149
|
+
|
|
150
|
+
// Compose Chip with static Group property
|
|
151
|
+
export const Chip = Object.assign(ChipBase, { Group: ChipGroup });
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineSegment } from '@fragments/core';
|
|
3
|
+
import { Markdown } from '.';
|
|
4
|
+
|
|
5
|
+
const defaultContent = `# Hello World
|
|
6
|
+
|
|
7
|
+
This is a paragraph with **bold text** and *italic text*.
|
|
8
|
+
|
|
9
|
+
Here is some \`inline code\` within a sentence.
|
|
10
|
+
|
|
11
|
+
- First item
|
|
12
|
+
- Second item
|
|
13
|
+
- Third item
|
|
14
|
+
|
|
15
|
+
> A blockquote for emphasis.
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const gfmTableContent = `## Data Overview
|
|
19
|
+
|
|
20
|
+
| Feature | Status | Priority |
|
|
21
|
+
|------------|-----------|----------|
|
|
22
|
+
| Markdown | Done | High |
|
|
23
|
+
| Tables | Done | Medium |
|
|
24
|
+
| Task Lists | Planned | Low |
|
|
25
|
+
|
|
26
|
+
Notes:
|
|
27
|
+
- [x] Support GFM tables
|
|
28
|
+
- [x] Support task lists
|
|
29
|
+
- [ ] Syntax highlighting
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
const codeBlockContent = `## Code Example
|
|
33
|
+
|
|
34
|
+
Here is a JavaScript function:
|
|
35
|
+
|
|
36
|
+
\`\`\`js
|
|
37
|
+
function greet(name) {
|
|
38
|
+
return \\\`Hello, \\\${name}!\\\`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(greet('World'));
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
And some inline code: \`const x = 42;\`
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const mixedContent = `# Project Update
|
|
48
|
+
|
|
49
|
+
## Summary
|
|
50
|
+
|
|
51
|
+
The project is progressing well. Here are the **key highlights**:
|
|
52
|
+
|
|
53
|
+
1. Completed the *design system* components
|
|
54
|
+
2. Added markdown rendering support
|
|
55
|
+
3. Integrated with the documentation site
|
|
56
|
+
|
|
57
|
+
### Performance Metrics
|
|
58
|
+
|
|
59
|
+
| Metric | Before | After |
|
|
60
|
+
|-------------|--------|--------|
|
|
61
|
+
| Bundle Size | 142kb | 98kb |
|
|
62
|
+
| Load Time | 1.2s | 0.8s |
|
|
63
|
+
| Lighthouse | 72 | 95 |
|
|
64
|
+
|
|
65
|
+
> These improvements were achieved through tree-shaking and code splitting.
|
|
66
|
+
|
|
67
|
+
### Next Steps
|
|
68
|
+
|
|
69
|
+
- [ ] Add syntax highlighting
|
|
70
|
+
- [ ] Support custom themes
|
|
71
|
+
- [x] GFM table support
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
For more details, see the [documentation](#).
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
export default defineSegment({
|
|
79
|
+
component: Markdown,
|
|
80
|
+
|
|
81
|
+
meta: {
|
|
82
|
+
name: 'Markdown',
|
|
83
|
+
description: 'Renders markdown strings as styled prose using react-markdown and remark-gfm. Supports headings, lists, tables, code blocks, blockquotes, and more.',
|
|
84
|
+
category: 'display',
|
|
85
|
+
status: 'stable',
|
|
86
|
+
tags: ['markdown', 'prose', 'content', 'text', 'ai', 'chat'],
|
|
87
|
+
since: '0.7.0',
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
usage: {
|
|
91
|
+
when: [
|
|
92
|
+
'Rendering AI/LLM response content',
|
|
93
|
+
'Displaying user-authored markdown text',
|
|
94
|
+
'Showing documentation or readme content',
|
|
95
|
+
'Rich text display without a WYSIWYG editor',
|
|
96
|
+
],
|
|
97
|
+
whenNot: [
|
|
98
|
+
'Plain text without formatting (use Text)',
|
|
99
|
+
'Editing markdown (use a markdown editor component)',
|
|
100
|
+
'Rendering trusted HTML directly (use dangerouslySetInnerHTML)',
|
|
101
|
+
],
|
|
102
|
+
guidelines: [
|
|
103
|
+
'Install react-markdown and remark-gfm as peer dependencies',
|
|
104
|
+
'Use the components prop to override default element rendering',
|
|
105
|
+
'Content is sanitized by react-markdown by default',
|
|
106
|
+
'Falls back to plain text paragraphs if react-markdown is not installed',
|
|
107
|
+
],
|
|
108
|
+
accessibility: [
|
|
109
|
+
'Rendered HTML follows semantic structure (headings, lists, tables)',
|
|
110
|
+
'Links are rendered as proper anchor elements',
|
|
111
|
+
'Images include alt text from markdown syntax',
|
|
112
|
+
'Tables use proper th/td structure for screen readers',
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
props: {
|
|
117
|
+
content: {
|
|
118
|
+
type: 'string',
|
|
119
|
+
description: 'Markdown string to render',
|
|
120
|
+
required: true,
|
|
121
|
+
},
|
|
122
|
+
components: {
|
|
123
|
+
type: 'object',
|
|
124
|
+
description: 'Override map for markdown element components (e.g., { h1: MyHeading })',
|
|
125
|
+
},
|
|
126
|
+
className: {
|
|
127
|
+
type: 'string',
|
|
128
|
+
description: 'Additional CSS class name',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
relations: [
|
|
133
|
+
{ component: 'Text', relationship: 'alternative', note: 'Use Text for plain, non-markdown text' },
|
|
134
|
+
{ component: 'CodeBlock', relationship: 'complementary', note: 'CodeBlock can be used via components prop for syntax highlighting' },
|
|
135
|
+
{ component: 'Message', relationship: 'parent', note: 'Message wraps Markdown when markdown prop is true' },
|
|
136
|
+
],
|
|
137
|
+
|
|
138
|
+
contract: {
|
|
139
|
+
propsSummary: [
|
|
140
|
+
'content: string - Markdown string to render',
|
|
141
|
+
'components: object - Override map for element components',
|
|
142
|
+
'className: string - Additional CSS class',
|
|
143
|
+
],
|
|
144
|
+
scenarioTags: [
|
|
145
|
+
'content.markdown',
|
|
146
|
+
'content.prose',
|
|
147
|
+
'ai.response',
|
|
148
|
+
],
|
|
149
|
+
a11yRules: ['A11Y_SEMANTIC_HTML'],
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
variants: [
|
|
153
|
+
{
|
|
154
|
+
name: 'Default',
|
|
155
|
+
description: 'Basic markdown with headings, paragraphs, inline code, lists, and blockquote',
|
|
156
|
+
code: `<Markdown content={\`# Hello World
|
|
157
|
+
|
|
158
|
+
This is a paragraph with **bold text** and *italic text*.
|
|
159
|
+
|
|
160
|
+
Here is some \\\`inline code\\\` within a sentence.
|
|
161
|
+
|
|
162
|
+
- First item
|
|
163
|
+
- Second item
|
|
164
|
+
- Third item
|
|
165
|
+
|
|
166
|
+
> A blockquote for emphasis.
|
|
167
|
+
\`} />`,
|
|
168
|
+
render: () => <Markdown content={defaultContent} />,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'GFM Table',
|
|
172
|
+
description: 'GitHub Flavored Markdown with tables and task lists',
|
|
173
|
+
code: `<Markdown content={\`## Data Overview
|
|
174
|
+
|
|
175
|
+
| Feature | Status | Priority |
|
|
176
|
+
|------------|-----------|----------|
|
|
177
|
+
| Markdown | Done | High |
|
|
178
|
+
| Tables | Done | Medium |
|
|
179
|
+
| Task Lists | Planned | Low |
|
|
180
|
+
|
|
181
|
+
Notes:
|
|
182
|
+
- [x] Support GFM tables
|
|
183
|
+
- [x] Support task lists
|
|
184
|
+
- [ ] Syntax highlighting
|
|
185
|
+
\`} />`,
|
|
186
|
+
render: () => <Markdown content={gfmTableContent} />,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'Code Block',
|
|
190
|
+
description: 'Markdown with fenced code blocks and inline code',
|
|
191
|
+
code: '<Markdown content={`## Code Example\\n\\nHere is a JavaScript function:\\n\\n```js\\nfunction greet(name) {\\n return \\`Hello, ${name}!\\`;\\n}\\n```\\n\\nAnd some inline code: \\`const x = 42;\\``} />',
|
|
192
|
+
render: () => <Markdown content={codeBlockContent} />,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'Mixed Content',
|
|
196
|
+
description: 'Complex markdown mixing headings, lists, tables, blockquotes, and task lists',
|
|
197
|
+
code: `<Markdown content={\`# Project Update
|
|
198
|
+
|
|
199
|
+
## Summary
|
|
200
|
+
|
|
201
|
+
The project is progressing well. Here are the **key highlights**:
|
|
202
|
+
|
|
203
|
+
1. Completed the *design system* components
|
|
204
|
+
2. Added markdown rendering support
|
|
205
|
+
3. Integrated with the documentation site
|
|
206
|
+
|
|
207
|
+
### Performance Metrics
|
|
208
|
+
|
|
209
|
+
| Metric | Before | After |
|
|
210
|
+
|-------------|--------|-------|
|
|
211
|
+
| Bundle Size | 142kb | 98kb |
|
|
212
|
+
| Load Time | 1.2s | 0.8s |
|
|
213
|
+
| Lighthouse | 72 | 95 |
|
|
214
|
+
|
|
215
|
+
> These improvements were achieved through tree-shaking and code splitting.
|
|
216
|
+
|
|
217
|
+
### Next Steps
|
|
218
|
+
|
|
219
|
+
- [ ] Add syntax highlighting
|
|
220
|
+
- [ ] Support custom themes
|
|
221
|
+
- [x] GFM table support
|
|
222
|
+
\`} />`,
|
|
223
|
+
render: () => <Markdown content={mixedContent} />,
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
});
|