@fragments-sdk/ui 0.4.0 → 0.5.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 +98 -2
- package/fragments.json +1 -1
- package/package.json +3 -2
- package/src/components/Accordion/Accordion.fragment.tsx +1 -1
- package/src/components/Alert/Alert.fragment.tsx +1 -1
- package/src/components/AppShell/AppShell.fragment.tsx +4 -4
- package/src/components/Avatar/Avatar.fragment.tsx +2 -2
- package/src/components/Badge/Badge.fragment.tsx +2 -2
- package/src/components/Badge/Badge.module.scss +1 -1
- package/src/components/Box/Box.fragment.tsx +1 -1
- package/src/components/Button/Button.fragment.tsx +2 -2
- package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +153 -0
- package/src/components/Card/Card.fragment.tsx +1 -1
- package/src/components/Chart/Chart.fragment.tsx +213 -0
- package/src/components/Chart/Chart.module.scss +123 -0
- package/src/components/Chart/index.tsx +267 -0
- package/src/components/Checkbox/Checkbox.fragment.tsx +1 -1
- package/src/components/CodeBlock/CodeBlock.fragment.tsx +265 -6
- package/src/components/CodeBlock/CodeBlock.module.scss +141 -3
- package/src/components/CodeBlock/index.tsx +250 -36
- package/src/components/Collapsible/Collapsible.fragment.tsx +199 -0
- package/src/components/Collapsible/Collapsible.module.scss +117 -0
- package/src/components/Collapsible/index.tsx +219 -0
- package/src/components/ColorPicker/ColorPicker.fragment.tsx +196 -0
- package/src/components/ColorPicker/ColorPicker.module.scss +33 -23
- package/src/components/ColorPicker/index.tsx +34 -12
- package/src/components/ConversationList/ConversationList.fragment.tsx +202 -0
- package/src/components/ConversationList/ConversationList.module.scss +160 -0
- package/src/components/ConversationList/index.tsx +254 -0
- package/src/components/Dialog/Dialog.fragment.tsx +3 -3
- package/src/components/EmptyState/EmptyState.fragment.tsx +2 -2
- package/src/components/Field/Field.fragment.tsx +3 -3
- package/src/components/Fieldset/Fieldset.fragment.tsx +7 -7
- package/src/components/Form/Form.fragment.tsx +11 -11
- package/src/components/Grid/Grid.fragment.tsx +1 -1
- package/src/components/Header/Header.fragment.tsx +4 -4
- package/src/components/Header/Header.module.scss +9 -10
- package/src/components/Icon/Icon.fragment.tsx +2 -2
- package/src/components/Image/Image.fragment.tsx +2 -2
- package/src/components/Input/Input.fragment.tsx +1 -1
- package/src/components/Input/Input.module.scss +2 -2
- package/src/components/Link/Link.fragment.tsx +1 -1
- package/src/components/List/List.fragment.tsx +2 -2
- package/src/components/Listbox/Listbox.fragment.tsx +1 -1
- package/src/components/Loading/Loading.fragment.tsx +153 -0
- package/src/components/Loading/Loading.module.scss +256 -0
- package/src/components/Loading/index.tsx +236 -0
- package/src/components/Menu/Menu.fragment.tsx +3 -3
- package/src/components/Message/Message.fragment.tsx +200 -0
- package/src/components/Message/Message.module.scss +224 -0
- package/src/components/Message/index.tsx +278 -0
- package/src/components/Popover/Popover.fragment.tsx +4 -4
- package/src/components/Progress/Progress.fragment.tsx +1 -1
- package/src/components/Prompt/Prompt.fragment.tsx +2 -2
- package/src/components/RadioGroup/RadioGroup.fragment.tsx +1 -1
- package/src/components/RadioGroup/RadioGroup.module.scss +7 -4
- package/src/components/Select/Select.fragment.tsx +1 -1
- package/src/components/Select/Select.module.scss +8 -0
- package/src/components/Select/index.tsx +85 -5
- package/src/components/Separator/Separator.fragment.tsx +1 -1
- package/src/components/Sidebar/Sidebar.fragment.tsx +2 -2
- package/src/components/Sidebar/Sidebar.module.scss +19 -0
- package/src/components/Sidebar/index.tsx +52 -11
- package/src/components/Skeleton/Skeleton.fragment.tsx +1 -1
- package/src/components/Slider/Slider.fragment.tsx +201 -0
- package/src/components/Stack/Stack.fragment.tsx +194 -0
- package/src/components/Table/Table.fragment.tsx +3 -3
- package/src/components/Tabs/Tabs.fragment.tsx +1 -1
- package/src/components/Tabs/Tabs.module.scss +2 -2
- package/src/components/Text/Text.fragment.tsx +188 -0
- package/src/components/Textarea/Textarea.fragment.tsx +1 -1
- package/src/components/Theme/Theme.fragment.tsx +2 -2
- package/src/components/Theme/ThemeToggle.module.scss +13 -13
- package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +182 -0
- package/src/components/ThinkingIndicator/ThinkingIndicator.module.scss +226 -0
- package/src/components/ThinkingIndicator/index.tsx +258 -0
- package/src/components/Toast/Toast.fragment.tsx +1 -1
- package/src/components/Toggle/Toggle.fragment.tsx +1 -1
- package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +207 -0
- package/src/components/Tooltip/Tooltip.fragment.tsx +3 -3
- package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +2 -2
- package/src/index.ts +86 -3
- package/src/recipes/AIChat.recipe.ts +266 -0
- package/src/tokens/_computed.scss +212 -0
- package/src/tokens/_density.scss +171 -0
- package/src/tokens/_derive.scss +287 -0
- package/src/tokens/_index.scss +39 -1
- package/src/tokens/_mixins.scss +41 -0
- package/src/tokens/_palettes.scss +185 -0
- package/src/tokens/_radius.scss +107 -0
- package/src/tokens/_seeds.scss +59 -0
- package/src/tokens/_variables.scss +171 -130
- package/src/components/ColorChip/ColorChip.module.scss +0 -165
- package/src/components/ColorChip/index.tsx +0 -157
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Tooltip as RechartsTooltip,
|
|
6
|
+
Legend as RechartsLegend,
|
|
7
|
+
} from 'recharts';
|
|
8
|
+
import type { Props as RechartsLegendProps } from 'recharts/types/component/Legend';
|
|
9
|
+
import styles from './Chart.module.scss';
|
|
10
|
+
import '../../styles/globals.scss';
|
|
11
|
+
|
|
12
|
+
// ============================================
|
|
13
|
+
// Types
|
|
14
|
+
// ============================================
|
|
15
|
+
|
|
16
|
+
export type ChartConfig = Record<
|
|
17
|
+
string,
|
|
18
|
+
{
|
|
19
|
+
label: string;
|
|
20
|
+
color: string;
|
|
21
|
+
icon?: React.ComponentType;
|
|
22
|
+
}
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
export interface ChartContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
26
|
+
config: ChartConfig;
|
|
27
|
+
children: React.ReactElement;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ChartTooltipContentProps {
|
|
31
|
+
active?: boolean;
|
|
32
|
+
payload?: readonly {
|
|
33
|
+
name?: string;
|
|
34
|
+
value?: number | string;
|
|
35
|
+
dataKey?: string | number;
|
|
36
|
+
color?: string;
|
|
37
|
+
payload?: Record<string, unknown>;
|
|
38
|
+
}[];
|
|
39
|
+
label?: string;
|
|
40
|
+
indicator?: 'dot' | 'line' | 'dashed';
|
|
41
|
+
hideLabel?: boolean;
|
|
42
|
+
hideIndicator?: boolean;
|
|
43
|
+
labelFormatter?: (label: string, payload: ChartTooltipContentProps['payload']) => React.ReactNode;
|
|
44
|
+
valueFormatter?: (value: number | string) => string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ChartLegendContentProps {
|
|
48
|
+
payload?: readonly {
|
|
49
|
+
value?: string;
|
|
50
|
+
dataKey?: string | number;
|
|
51
|
+
color?: string;
|
|
52
|
+
}[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================
|
|
56
|
+
// Context
|
|
57
|
+
// ============================================
|
|
58
|
+
|
|
59
|
+
const ChartConfigContext = React.createContext<ChartConfig | null>(null);
|
|
60
|
+
|
|
61
|
+
export function useChartConfig() {
|
|
62
|
+
const ctx = React.useContext(ChartConfigContext);
|
|
63
|
+
if (!ctx) {
|
|
64
|
+
throw new Error('useChartConfig must be used within a <ChartContainer>');
|
|
65
|
+
}
|
|
66
|
+
return ctx;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ============================================
|
|
70
|
+
// ChartContainer
|
|
71
|
+
// ============================================
|
|
72
|
+
|
|
73
|
+
export function ChartContainer({
|
|
74
|
+
config,
|
|
75
|
+
children,
|
|
76
|
+
className,
|
|
77
|
+
style,
|
|
78
|
+
...htmlProps
|
|
79
|
+
}: ChartContainerProps) {
|
|
80
|
+
// Build CSS custom properties from config (--chart-<key>)
|
|
81
|
+
const cssVars = React.useMemo(() => {
|
|
82
|
+
const vars: Record<string, string> = {};
|
|
83
|
+
Object.entries(config).forEach(([key, val]) => {
|
|
84
|
+
vars[`--chart-${key}`] = val.color;
|
|
85
|
+
});
|
|
86
|
+
return vars;
|
|
87
|
+
}, [config]);
|
|
88
|
+
|
|
89
|
+
const rootClasses = [styles.container, className].filter(Boolean).join(' ');
|
|
90
|
+
|
|
91
|
+
// Inject responsive + width/height into the chart child (recharts v3 API)
|
|
92
|
+
const chartChild = React.cloneElement(children as React.ReactElement<Record<string, unknown>>, {
|
|
93
|
+
responsive: true,
|
|
94
|
+
width: '100%',
|
|
95
|
+
height: '100%',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<ChartConfigContext.Provider value={config}>
|
|
100
|
+
<div
|
|
101
|
+
{...htmlProps}
|
|
102
|
+
className={rootClasses}
|
|
103
|
+
style={{ ...cssVars, ...style }}
|
|
104
|
+
>
|
|
105
|
+
{chartChild}
|
|
106
|
+
</div>
|
|
107
|
+
</ChartConfigContext.Provider>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ============================================
|
|
112
|
+
// ChartTooltipContent
|
|
113
|
+
// ============================================
|
|
114
|
+
|
|
115
|
+
export function ChartTooltipContent({
|
|
116
|
+
active,
|
|
117
|
+
payload,
|
|
118
|
+
label,
|
|
119
|
+
indicator = 'dot',
|
|
120
|
+
hideLabel = false,
|
|
121
|
+
hideIndicator = false,
|
|
122
|
+
labelFormatter,
|
|
123
|
+
valueFormatter,
|
|
124
|
+
}: ChartTooltipContentProps) {
|
|
125
|
+
const config = React.useContext(ChartConfigContext);
|
|
126
|
+
|
|
127
|
+
if (!active || !payload?.length) return null;
|
|
128
|
+
|
|
129
|
+
const formattedLabel = labelFormatter
|
|
130
|
+
? labelFormatter(String(label), payload)
|
|
131
|
+
: label;
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div className={styles.tooltip}>
|
|
135
|
+
{!hideLabel && formattedLabel && (
|
|
136
|
+
<div className={styles.tooltipLabel}>{formattedLabel}</div>
|
|
137
|
+
)}
|
|
138
|
+
<div className={styles.tooltipItems}>
|
|
139
|
+
{payload.map((entry, i) => {
|
|
140
|
+
const key = String(entry.dataKey ?? entry.name ?? i);
|
|
141
|
+
const configEntry = config?.[key];
|
|
142
|
+
const displayLabel = configEntry?.label ?? entry.name ?? key;
|
|
143
|
+
const color = entry.color ?? configEntry?.color;
|
|
144
|
+
const displayValue = valueFormatter
|
|
145
|
+
? valueFormatter(entry.value ?? 0)
|
|
146
|
+
: String(entry.value ?? '');
|
|
147
|
+
|
|
148
|
+
const indicatorClass = [
|
|
149
|
+
styles.tooltipIndicator,
|
|
150
|
+
indicator === 'line' && styles.tooltipIndicatorLine,
|
|
151
|
+
indicator === 'dashed' && styles.tooltipIndicatorDashed,
|
|
152
|
+
].filter(Boolean).join(' ');
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<div key={key} className={styles.tooltipItem}>
|
|
156
|
+
{!hideIndicator && (
|
|
157
|
+
<span
|
|
158
|
+
className={indicatorClass}
|
|
159
|
+
style={{ backgroundColor: indicator === 'dashed' ? undefined : color, borderColor: color }}
|
|
160
|
+
/>
|
|
161
|
+
)}
|
|
162
|
+
<span className={styles.tooltipItemLabel}>{displayLabel}</span>
|
|
163
|
+
<span className={styles.tooltipItemValue}>{displayValue}</span>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
})}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ============================================
|
|
173
|
+
// ChartTooltip (thin wrapper)
|
|
174
|
+
// ============================================
|
|
175
|
+
|
|
176
|
+
type ChartTooltipProps = Omit<React.ComponentProps<typeof RechartsTooltip>, 'content'> & {
|
|
177
|
+
indicator?: 'dot' | 'line' | 'dashed';
|
|
178
|
+
hideLabel?: boolean;
|
|
179
|
+
hideIndicator?: boolean;
|
|
180
|
+
labelFormatter?: ChartTooltipContentProps['labelFormatter'];
|
|
181
|
+
valueFormatter?: ChartTooltipContentProps['valueFormatter'];
|
|
182
|
+
content?: React.ComponentProps<typeof RechartsTooltip>['content'];
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export function ChartTooltip({
|
|
186
|
+
indicator,
|
|
187
|
+
hideLabel,
|
|
188
|
+
hideIndicator,
|
|
189
|
+
labelFormatter,
|
|
190
|
+
valueFormatter,
|
|
191
|
+
content,
|
|
192
|
+
...props
|
|
193
|
+
}: ChartTooltipProps) {
|
|
194
|
+
const defaultContent = React.useCallback(
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
196
|
+
(tooltipProps: any) => (
|
|
197
|
+
<ChartTooltipContent
|
|
198
|
+
{...tooltipProps}
|
|
199
|
+
indicator={indicator}
|
|
200
|
+
hideLabel={hideLabel}
|
|
201
|
+
hideIndicator={hideIndicator}
|
|
202
|
+
labelFormatter={labelFormatter}
|
|
203
|
+
valueFormatter={valueFormatter}
|
|
204
|
+
/>
|
|
205
|
+
),
|
|
206
|
+
[indicator, hideLabel, hideIndicator, labelFormatter, valueFormatter],
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<RechartsTooltip
|
|
211
|
+
cursor={{ stroke: 'var(--fui-border)' }}
|
|
212
|
+
content={content ?? defaultContent}
|
|
213
|
+
{...props}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ============================================
|
|
219
|
+
// ChartLegendContent
|
|
220
|
+
// ============================================
|
|
221
|
+
|
|
222
|
+
export function ChartLegendContent({ payload }: ChartLegendContentProps) {
|
|
223
|
+
const config = React.useContext(ChartConfigContext);
|
|
224
|
+
|
|
225
|
+
if (!payload?.length) return null;
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div className={styles.legend}>
|
|
229
|
+
{payload.map((entry) => {
|
|
230
|
+
const key = String(entry.dataKey ?? entry.value ?? '');
|
|
231
|
+
const configEntry = config?.[key];
|
|
232
|
+
const label = configEntry?.label ?? entry.value ?? key;
|
|
233
|
+
const color = entry.color ?? configEntry?.color;
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<div key={key} className={styles.legendItem}>
|
|
237
|
+
<span
|
|
238
|
+
className={styles.legendDot}
|
|
239
|
+
style={{ backgroundColor: color }}
|
|
240
|
+
/>
|
|
241
|
+
<span className={styles.legendLabel}>{label}</span>
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
})}
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ============================================
|
|
250
|
+
// ChartLegend (thin wrapper)
|
|
251
|
+
// ============================================
|
|
252
|
+
|
|
253
|
+
type ChartLegendProps = Omit<RechartsLegendProps, 'content'> & {
|
|
254
|
+
content?: RechartsLegendProps['content'];
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
export function ChartLegend({ content, ...props }: ChartLegendProps) {
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
259
|
+
const defaultContent = (legendProps: any) => <ChartLegendContent {...legendProps} />;
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<RechartsLegend
|
|
263
|
+
content={content ?? defaultContent}
|
|
264
|
+
{...props}
|
|
265
|
+
/>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { defineSegment } from '@fragments/core';
|
|
3
|
-
import { Checkbox } from '
|
|
3
|
+
import { Checkbox } from '.';
|
|
4
4
|
|
|
5
5
|
// Stateful wrapper for interactive demos
|
|
6
6
|
function StatefulCheckbox(props: React.ComponentProps<typeof Checkbox>) {
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { defineSegment } from '@fragments/core';
|
|
3
|
-
import { CodeBlock } from '
|
|
3
|
+
import { CodeBlock } from '.';
|
|
4
4
|
|
|
5
5
|
export default defineSegment({
|
|
6
6
|
component: CodeBlock,
|
|
7
7
|
|
|
8
8
|
meta: {
|
|
9
9
|
name: 'CodeBlock',
|
|
10
|
-
description: 'Syntax-highlighted code display with copy functionality',
|
|
11
|
-
category: '
|
|
10
|
+
description: 'Syntax-highlighted code display with copy functionality, theming, diff view, and collapsible sections',
|
|
11
|
+
category: 'display',
|
|
12
12
|
status: 'stable',
|
|
13
|
-
tags: ['code', 'syntax', 'highlighting', 'documentation', 'developer'],
|
|
13
|
+
tags: ['code', 'syntax', 'highlighting', 'documentation', 'developer', 'diff'],
|
|
14
14
|
},
|
|
15
15
|
|
|
16
16
|
usage: {
|
|
@@ -19,6 +19,8 @@ export default defineSegment({
|
|
|
19
19
|
'Showing installation commands',
|
|
20
20
|
'Presenting configuration snippets',
|
|
21
21
|
'Teaching programming concepts',
|
|
22
|
+
'Showing code diffs or changes',
|
|
23
|
+
'Displaying long code files with collapse functionality',
|
|
22
24
|
],
|
|
23
25
|
whenNot: [
|
|
24
26
|
'User-editable code (use code editor)',
|
|
@@ -31,12 +33,17 @@ export default defineSegment({
|
|
|
31
33
|
'Use title prop for external labels above the code block',
|
|
32
34
|
'Enable line numbers for longer code samples',
|
|
33
35
|
'Use highlightLines to draw attention to key lines',
|
|
36
|
+
'Use addedLines/removedLines for diff highlighting',
|
|
37
|
+
'Set maxHeight for very long code blocks to prevent layout issues',
|
|
38
|
+
'Use collapsible for code samples that users may want to skim',
|
|
39
|
+
'Choose a theme that matches your documentation style',
|
|
34
40
|
'Keep code examples concise and focused',
|
|
35
41
|
],
|
|
36
42
|
accessibility: [
|
|
37
43
|
'Code is presented in a semantic pre/code structure',
|
|
38
44
|
'Copy button has appropriate aria-label',
|
|
39
45
|
'Keyboard accessible copy functionality',
|
|
46
|
+
'Collapse button has aria-expanded state',
|
|
40
47
|
],
|
|
41
48
|
},
|
|
42
49
|
|
|
@@ -48,10 +55,26 @@ export default defineSegment({
|
|
|
48
55
|
},
|
|
49
56
|
language: {
|
|
50
57
|
type: 'enum',
|
|
51
|
-
values: [
|
|
58
|
+
values: [
|
|
59
|
+
'tsx', 'typescript', 'javascript', 'jsx', 'bash', 'shell',
|
|
60
|
+
'css', 'scss', 'sass', 'json', 'html', 'xml', 'markdown', 'md',
|
|
61
|
+
'yaml', 'yml', 'python', 'py', 'ruby', 'go', 'rust', 'java',
|
|
62
|
+
'kotlin', 'swift', 'c', 'cpp', 'csharp', 'php', 'sql', 'graphql',
|
|
63
|
+
'diff', 'plaintext',
|
|
64
|
+
],
|
|
52
65
|
default: 'tsx',
|
|
53
66
|
description: 'Programming language for syntax highlighting',
|
|
54
67
|
},
|
|
68
|
+
theme: {
|
|
69
|
+
type: 'enum',
|
|
70
|
+
values: [
|
|
71
|
+
'synthwave-84', 'github-dark', 'github-light', 'one-dark-pro',
|
|
72
|
+
'dracula', 'nord', 'monokai', 'vitesse-dark', 'vitesse-light',
|
|
73
|
+
'min-dark', 'min-light',
|
|
74
|
+
],
|
|
75
|
+
default: 'synthwave-84',
|
|
76
|
+
description: 'Syntax highlighting theme',
|
|
77
|
+
},
|
|
55
78
|
showCopy: {
|
|
56
79
|
type: 'boolean',
|
|
57
80
|
default: true,
|
|
@@ -65,15 +88,56 @@ export default defineSegment({
|
|
|
65
88
|
type: 'string',
|
|
66
89
|
description: 'Optional filename shown in header bar inside code block',
|
|
67
90
|
},
|
|
91
|
+
caption: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: 'Optional caption displayed below the code block',
|
|
94
|
+
},
|
|
68
95
|
showLineNumbers: {
|
|
69
96
|
type: 'boolean',
|
|
70
97
|
default: false,
|
|
71
98
|
description: 'Whether to display line numbers',
|
|
72
99
|
},
|
|
100
|
+
startLineNumber: {
|
|
101
|
+
type: 'number',
|
|
102
|
+
default: 1,
|
|
103
|
+
description: 'Starting line number (useful for code excerpts)',
|
|
104
|
+
},
|
|
73
105
|
highlightLines: {
|
|
74
106
|
type: 'array',
|
|
75
107
|
description: 'Lines to highlight (e.g., [1, 3, "5-7"])',
|
|
76
108
|
},
|
|
109
|
+
addedLines: {
|
|
110
|
+
type: 'array',
|
|
111
|
+
description: 'Lines marked as added in diff view (e.g., [2, "4-6"])',
|
|
112
|
+
},
|
|
113
|
+
removedLines: {
|
|
114
|
+
type: 'array',
|
|
115
|
+
description: 'Lines marked as removed in diff view (e.g., [1, 3])',
|
|
116
|
+
},
|
|
117
|
+
wordWrap: {
|
|
118
|
+
type: 'boolean',
|
|
119
|
+
default: false,
|
|
120
|
+
description: 'Enable word wrapping for long lines',
|
|
121
|
+
},
|
|
122
|
+
maxHeight: {
|
|
123
|
+
type: 'number',
|
|
124
|
+
description: 'Maximum height in pixels (enables scrolling)',
|
|
125
|
+
},
|
|
126
|
+
collapsible: {
|
|
127
|
+
type: 'boolean',
|
|
128
|
+
default: false,
|
|
129
|
+
description: 'Allow collapsing/expanding the code block',
|
|
130
|
+
},
|
|
131
|
+
defaultCollapsed: {
|
|
132
|
+
type: 'boolean',
|
|
133
|
+
default: false,
|
|
134
|
+
description: 'Initial collapsed state (only applies when collapsible is true)',
|
|
135
|
+
},
|
|
136
|
+
collapsedLines: {
|
|
137
|
+
type: 'number',
|
|
138
|
+
default: 5,
|
|
139
|
+
description: 'Number of lines to show when collapsed',
|
|
140
|
+
},
|
|
77
141
|
className: {
|
|
78
142
|
type: 'string',
|
|
79
143
|
description: 'Additional CSS class name',
|
|
@@ -96,12 +160,22 @@ export default defineSegment({
|
|
|
96
160
|
contract: {
|
|
97
161
|
propsSummary: [
|
|
98
162
|
'code: string - required code content',
|
|
99
|
-
'language: tsx|typescript|javascript|bash|css|scss|json|html',
|
|
163
|
+
'language: tsx|typescript|javascript|jsx|bash|shell|css|scss|sass|json|html|xml|markdown|md|yaml|yml|python|py|ruby|go|rust|java|kotlin|swift|c|cpp|csharp|php|sql|graphql|diff|plaintext',
|
|
164
|
+
'theme: synthwave-84|github-dark|github-light|one-dark-pro|dracula|nord|monokai|vitesse-dark|vitesse-light|min-dark|min-light',
|
|
100
165
|
'showCopy: boolean (default: true)',
|
|
101
166
|
'title: string - optional external label',
|
|
102
167
|
'filename: string - optional filename in header bar',
|
|
168
|
+
'caption: string - optional footer caption',
|
|
103
169
|
'showLineNumbers: boolean (default: false)',
|
|
170
|
+
'startLineNumber: number (default: 1)',
|
|
104
171
|
'highlightLines: (number | string)[] - lines to emphasize',
|
|
172
|
+
'addedLines: (number | string)[] - diff added lines',
|
|
173
|
+
'removedLines: (number | string)[] - diff removed lines',
|
|
174
|
+
'wordWrap: boolean (default: false)',
|
|
175
|
+
'maxHeight: number - max height with scrolling',
|
|
176
|
+
'collapsible: boolean (default: false)',
|
|
177
|
+
'defaultCollapsed: boolean (default: false)',
|
|
178
|
+
'collapsedLines: number (default: 5)',
|
|
105
179
|
],
|
|
106
180
|
scenarioTags: [
|
|
107
181
|
'documentation.code',
|
|
@@ -152,6 +226,17 @@ function App() {
|
|
|
152
226
|
<CodeBlock title="Installation" code="npm install @fragments-sdk/ui" language="bash" />
|
|
153
227
|
),
|
|
154
228
|
},
|
|
229
|
+
{
|
|
230
|
+
name: 'With Caption',
|
|
231
|
+
description: 'Code block with footer caption',
|
|
232
|
+
render: () => (
|
|
233
|
+
<CodeBlock
|
|
234
|
+
code={`const API_URL = process.env.NEXT_PUBLIC_API_URL;`}
|
|
235
|
+
language="typescript"
|
|
236
|
+
caption="Environment variables must be prefixed with NEXT_PUBLIC_ to be available in the browser."
|
|
237
|
+
/>
|
|
238
|
+
),
|
|
239
|
+
},
|
|
155
240
|
{
|
|
156
241
|
name: 'With Line Numbers',
|
|
157
242
|
description: 'Shows line numbers for reference',
|
|
@@ -161,6 +246,24 @@ const name = "World";
|
|
|
161
246
|
console.log(\`\${greeting}, \${name}!\`);`} language="typescript" showLineNumbers />
|
|
162
247
|
),
|
|
163
248
|
},
|
|
249
|
+
{
|
|
250
|
+
name: 'Custom Start Line',
|
|
251
|
+
description: 'Shows code excerpt starting from a specific line number',
|
|
252
|
+
render: () => (
|
|
253
|
+
<CodeBlock
|
|
254
|
+
code={` return (
|
|
255
|
+
<button onClick={() => setCount(c => c + 1)}>
|
|
256
|
+
Count: {count}
|
|
257
|
+
</button>
|
|
258
|
+
);
|
|
259
|
+
}`}
|
|
260
|
+
language="tsx"
|
|
261
|
+
showLineNumbers
|
|
262
|
+
startLineNumber={15}
|
|
263
|
+
caption="Lines 15-20 from Counter.tsx"
|
|
264
|
+
/>
|
|
265
|
+
),
|
|
266
|
+
},
|
|
164
267
|
{
|
|
165
268
|
name: 'With Highlighted Lines',
|
|
166
269
|
description: 'Emphasizes specific lines of code',
|
|
@@ -178,6 +281,162 @@ function Counter() {
|
|
|
178
281
|
}`} language="tsx" showLineNumbers highlightLines={[4, '7-9']} />
|
|
179
282
|
),
|
|
180
283
|
},
|
|
284
|
+
{
|
|
285
|
+
name: 'Diff View',
|
|
286
|
+
description: 'Shows added and removed lines in a diff-like format',
|
|
287
|
+
render: () => (
|
|
288
|
+
<CodeBlock
|
|
289
|
+
code={`import { useState } from 'react';
|
|
290
|
+
import { useCallback } from 'react';
|
|
291
|
+
|
|
292
|
+
function Counter() {
|
|
293
|
+
const [count, setCount] = useState(0);
|
|
294
|
+
const increment = () => setCount(c => c + 1);
|
|
295
|
+
const increment = useCallback(() => setCount(c => c + 1), []);
|
|
296
|
+
|
|
297
|
+
return <button onClick={increment}>Count: {count}</button>;
|
|
298
|
+
}`}
|
|
299
|
+
language="tsx"
|
|
300
|
+
showLineNumbers
|
|
301
|
+
removedLines={[6]}
|
|
302
|
+
addedLines={[2, 7]}
|
|
303
|
+
/>
|
|
304
|
+
),
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: 'GitHub Dark Theme',
|
|
308
|
+
description: 'Using the GitHub Dark theme',
|
|
309
|
+
render: () => (
|
|
310
|
+
<CodeBlock
|
|
311
|
+
code={`async function fetchUser(id: string) {
|
|
312
|
+
const response = await fetch(\`/api/users/\${id}\`);
|
|
313
|
+
return response.json();
|
|
314
|
+
}`}
|
|
315
|
+
language="typescript"
|
|
316
|
+
theme="github-dark"
|
|
317
|
+
/>
|
|
318
|
+
),
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: 'GitHub Light Theme',
|
|
322
|
+
description: 'Using the GitHub Light theme for light backgrounds',
|
|
323
|
+
render: () => (
|
|
324
|
+
<CodeBlock
|
|
325
|
+
code={`def fibonacci(n):
|
|
326
|
+
if n <= 1:
|
|
327
|
+
return n
|
|
328
|
+
return fibonacci(n - 1) + fibonacci(n - 2)`}
|
|
329
|
+
language="python"
|
|
330
|
+
theme="github-light"
|
|
331
|
+
/>
|
|
332
|
+
),
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: 'Dracula Theme',
|
|
336
|
+
description: 'Using the popular Dracula theme',
|
|
337
|
+
render: () => (
|
|
338
|
+
<CodeBlock
|
|
339
|
+
code={`fn main() {
|
|
340
|
+
println!("Hello, Rust!");
|
|
341
|
+
}`}
|
|
342
|
+
language="rust"
|
|
343
|
+
theme="dracula"
|
|
344
|
+
/>
|
|
345
|
+
),
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: 'Word Wrap',
|
|
349
|
+
description: 'Long lines wrap instead of scrolling horizontally',
|
|
350
|
+
render: () => (
|
|
351
|
+
<CodeBlock
|
|
352
|
+
code={`const longString = "This is a very long string that would normally cause horizontal scrolling, but with word wrap enabled it will break to the next line instead.";`}
|
|
353
|
+
language="typescript"
|
|
354
|
+
wordWrap
|
|
355
|
+
/>
|
|
356
|
+
),
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: 'Max Height with Scroll',
|
|
360
|
+
description: 'Constrains height with scrollable content',
|
|
361
|
+
render: () => (
|
|
362
|
+
<CodeBlock
|
|
363
|
+
code={`// This code block has a maximum height
|
|
364
|
+
function processItems(items: string[]) {
|
|
365
|
+
const results = [];
|
|
366
|
+
for (const item of items) {
|
|
367
|
+
if (item.startsWith('_')) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const processed = item.trim().toLowerCase();
|
|
371
|
+
if (processed.length > 0) {
|
|
372
|
+
results.push(processed);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return results;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export default processItems;`}
|
|
379
|
+
language="typescript"
|
|
380
|
+
maxHeight={150}
|
|
381
|
+
showLineNumbers
|
|
382
|
+
/>
|
|
383
|
+
),
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: 'Collapsible',
|
|
387
|
+
description: 'Long code that can be expanded/collapsed',
|
|
388
|
+
render: () => (
|
|
389
|
+
<CodeBlock
|
|
390
|
+
code={`import React, { useState, useEffect } from 'react';
|
|
391
|
+
|
|
392
|
+
interface User {
|
|
393
|
+
id: string;
|
|
394
|
+
name: string;
|
|
395
|
+
email: string;
|
|
396
|
+
avatar?: string;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function UserProfile({ userId }: { userId: string }) {
|
|
400
|
+
const [user, setUser] = useState<User | null>(null);
|
|
401
|
+
const [loading, setLoading] = useState(true);
|
|
402
|
+
const [error, setError] = useState<string | null>(null);
|
|
403
|
+
|
|
404
|
+
useEffect(() => {
|
|
405
|
+
async function fetchUser() {
|
|
406
|
+
try {
|
|
407
|
+
setLoading(true);
|
|
408
|
+
const response = await fetch(\`/api/users/\${userId}\`);
|
|
409
|
+
if (!response.ok) throw new Error('Failed to fetch');
|
|
410
|
+
const data = await response.json();
|
|
411
|
+
setUser(data);
|
|
412
|
+
} catch (err) {
|
|
413
|
+
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
414
|
+
} finally {
|
|
415
|
+
setLoading(false);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
fetchUser();
|
|
419
|
+
}, [userId]);
|
|
420
|
+
|
|
421
|
+
if (loading) return <div>Loading...</div>;
|
|
422
|
+
if (error) return <div>Error: {error}</div>;
|
|
423
|
+
if (!user) return null;
|
|
424
|
+
|
|
425
|
+
return (
|
|
426
|
+
<div>
|
|
427
|
+
<h1>{user.name}</h1>
|
|
428
|
+
<p>{user.email}</p>
|
|
429
|
+
</div>
|
|
430
|
+
);
|
|
431
|
+
}`}
|
|
432
|
+
language="tsx"
|
|
433
|
+
showLineNumbers
|
|
434
|
+
collapsible
|
|
435
|
+
defaultCollapsed
|
|
436
|
+
collapsedLines={8}
|
|
437
|
+
/>
|
|
438
|
+
),
|
|
439
|
+
},
|
|
181
440
|
{
|
|
182
441
|
name: 'JSON',
|
|
183
442
|
description: 'Configuration file example',
|