@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.
Files changed (94) hide show
  1. package/README.md +98 -2
  2. package/fragments.json +1 -1
  3. package/package.json +3 -2
  4. package/src/components/Accordion/Accordion.fragment.tsx +1 -1
  5. package/src/components/Alert/Alert.fragment.tsx +1 -1
  6. package/src/components/AppShell/AppShell.fragment.tsx +4 -4
  7. package/src/components/Avatar/Avatar.fragment.tsx +2 -2
  8. package/src/components/Badge/Badge.fragment.tsx +2 -2
  9. package/src/components/Badge/Badge.module.scss +1 -1
  10. package/src/components/Box/Box.fragment.tsx +1 -1
  11. package/src/components/Button/Button.fragment.tsx +2 -2
  12. package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +153 -0
  13. package/src/components/Card/Card.fragment.tsx +1 -1
  14. package/src/components/Chart/Chart.fragment.tsx +213 -0
  15. package/src/components/Chart/Chart.module.scss +123 -0
  16. package/src/components/Chart/index.tsx +267 -0
  17. package/src/components/Checkbox/Checkbox.fragment.tsx +1 -1
  18. package/src/components/CodeBlock/CodeBlock.fragment.tsx +265 -6
  19. package/src/components/CodeBlock/CodeBlock.module.scss +141 -3
  20. package/src/components/CodeBlock/index.tsx +250 -36
  21. package/src/components/Collapsible/Collapsible.fragment.tsx +199 -0
  22. package/src/components/Collapsible/Collapsible.module.scss +117 -0
  23. package/src/components/Collapsible/index.tsx +219 -0
  24. package/src/components/ColorPicker/ColorPicker.fragment.tsx +196 -0
  25. package/src/components/ColorPicker/ColorPicker.module.scss +33 -23
  26. package/src/components/ColorPicker/index.tsx +34 -12
  27. package/src/components/ConversationList/ConversationList.fragment.tsx +202 -0
  28. package/src/components/ConversationList/ConversationList.module.scss +160 -0
  29. package/src/components/ConversationList/index.tsx +254 -0
  30. package/src/components/Dialog/Dialog.fragment.tsx +3 -3
  31. package/src/components/EmptyState/EmptyState.fragment.tsx +2 -2
  32. package/src/components/Field/Field.fragment.tsx +3 -3
  33. package/src/components/Fieldset/Fieldset.fragment.tsx +7 -7
  34. package/src/components/Form/Form.fragment.tsx +11 -11
  35. package/src/components/Grid/Grid.fragment.tsx +1 -1
  36. package/src/components/Header/Header.fragment.tsx +4 -4
  37. package/src/components/Header/Header.module.scss +9 -10
  38. package/src/components/Icon/Icon.fragment.tsx +2 -2
  39. package/src/components/Image/Image.fragment.tsx +2 -2
  40. package/src/components/Input/Input.fragment.tsx +1 -1
  41. package/src/components/Input/Input.module.scss +2 -2
  42. package/src/components/Link/Link.fragment.tsx +1 -1
  43. package/src/components/List/List.fragment.tsx +2 -2
  44. package/src/components/Listbox/Listbox.fragment.tsx +1 -1
  45. package/src/components/Loading/Loading.fragment.tsx +153 -0
  46. package/src/components/Loading/Loading.module.scss +256 -0
  47. package/src/components/Loading/index.tsx +236 -0
  48. package/src/components/Menu/Menu.fragment.tsx +3 -3
  49. package/src/components/Message/Message.fragment.tsx +200 -0
  50. package/src/components/Message/Message.module.scss +224 -0
  51. package/src/components/Message/index.tsx +278 -0
  52. package/src/components/Popover/Popover.fragment.tsx +4 -4
  53. package/src/components/Progress/Progress.fragment.tsx +1 -1
  54. package/src/components/Prompt/Prompt.fragment.tsx +2 -2
  55. package/src/components/RadioGroup/RadioGroup.fragment.tsx +1 -1
  56. package/src/components/RadioGroup/RadioGroup.module.scss +7 -4
  57. package/src/components/Select/Select.fragment.tsx +1 -1
  58. package/src/components/Select/Select.module.scss +8 -0
  59. package/src/components/Select/index.tsx +85 -5
  60. package/src/components/Separator/Separator.fragment.tsx +1 -1
  61. package/src/components/Sidebar/Sidebar.fragment.tsx +2 -2
  62. package/src/components/Sidebar/Sidebar.module.scss +19 -0
  63. package/src/components/Sidebar/index.tsx +52 -11
  64. package/src/components/Skeleton/Skeleton.fragment.tsx +1 -1
  65. package/src/components/Slider/Slider.fragment.tsx +201 -0
  66. package/src/components/Stack/Stack.fragment.tsx +194 -0
  67. package/src/components/Table/Table.fragment.tsx +3 -3
  68. package/src/components/Tabs/Tabs.fragment.tsx +1 -1
  69. package/src/components/Tabs/Tabs.module.scss +2 -2
  70. package/src/components/Text/Text.fragment.tsx +188 -0
  71. package/src/components/Textarea/Textarea.fragment.tsx +1 -1
  72. package/src/components/Theme/Theme.fragment.tsx +2 -2
  73. package/src/components/Theme/ThemeToggle.module.scss +13 -13
  74. package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +182 -0
  75. package/src/components/ThinkingIndicator/ThinkingIndicator.module.scss +226 -0
  76. package/src/components/ThinkingIndicator/index.tsx +258 -0
  77. package/src/components/Toast/Toast.fragment.tsx +1 -1
  78. package/src/components/Toggle/Toggle.fragment.tsx +1 -1
  79. package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +207 -0
  80. package/src/components/Tooltip/Tooltip.fragment.tsx +3 -3
  81. package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +2 -2
  82. package/src/index.ts +86 -3
  83. package/src/recipes/AIChat.recipe.ts +266 -0
  84. package/src/tokens/_computed.scss +212 -0
  85. package/src/tokens/_density.scss +171 -0
  86. package/src/tokens/_derive.scss +287 -0
  87. package/src/tokens/_index.scss +39 -1
  88. package/src/tokens/_mixins.scss +41 -0
  89. package/src/tokens/_palettes.scss +185 -0
  90. package/src/tokens/_radius.scss +107 -0
  91. package/src/tokens/_seeds.scss +59 -0
  92. package/src/tokens/_variables.scss +171 -130
  93. package/src/components/ColorChip/ColorChip.module.scss +0 -165
  94. 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 './index.js';
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 './index.js';
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: 'content',
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: ['tsx', 'typescript', 'javascript', 'bash', 'css', 'scss', 'json', 'html'],
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',