@djangocfg/ui-tools 2.1.297 → 2.1.299

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 (28) hide show
  1. package/README.md +126 -2
  2. package/dist/{DocsLayout-3HNAQQRE.mjs → DocsLayout-MWRKNFXR.mjs} +3 -3
  3. package/dist/{DocsLayout-3HNAQQRE.mjs.map → DocsLayout-MWRKNFXR.mjs.map} +1 -1
  4. package/dist/{DocsLayout-O4ONSD67.cjs → DocsLayout-NWJUF42A.cjs} +48 -48
  5. package/dist/{DocsLayout-O4ONSD67.cjs.map → DocsLayout-NWJUF42A.cjs.map} +1 -1
  6. package/dist/{chunk-DKJTH4GE.mjs → chunk-CKD7GNE5.mjs} +236 -186
  7. package/dist/chunk-CKD7GNE5.mjs.map +1 -0
  8. package/dist/{chunk-QTO5LWMK.cjs → chunk-SEXWBCLX.cjs} +272 -221
  9. package/dist/chunk-SEXWBCLX.cjs.map +1 -0
  10. package/dist/index.cjs +13 -9
  11. package/dist/index.d.cts +82 -59
  12. package/dist/index.d.ts +82 -59
  13. package/dist/index.mjs +4 -4
  14. package/package.json +6 -6
  15. package/src/components/markdown/MarkdownMessage/CodeBlock.tsx +69 -0
  16. package/src/components/markdown/MarkdownMessage/CollapseToggle.tsx +60 -0
  17. package/src/components/markdown/MarkdownMessage/MarkdownMessage.story.tsx +171 -0
  18. package/src/components/markdown/MarkdownMessage/MarkdownMessage.tsx +202 -0
  19. package/src/components/markdown/MarkdownMessage/components.tsx +154 -0
  20. package/src/components/markdown/MarkdownMessage/index.ts +13 -0
  21. package/src/components/markdown/MarkdownMessage/linkRules.ts +83 -0
  22. package/src/components/markdown/MarkdownMessage/plainText.ts +50 -0
  23. package/src/components/markdown/MarkdownMessage/sanitize.ts +78 -0
  24. package/src/components/markdown/MarkdownMessage/types.ts +104 -0
  25. package/src/components/markdown/index.ts +6 -1
  26. package/dist/chunk-DKJTH4GE.mjs.map +0 -1
  27. package/dist/chunk-QTO5LWMK.cjs.map +0 -1
  28. package/src/components/markdown/MarkdownMessage.tsx +0 -686
package/dist/index.d.cts CHANGED
@@ -108,6 +108,47 @@ declare function createLazyComponent<P extends object>(loader: () => Promise<{
108
108
  default: ComponentType<P>;
109
109
  }>, options?: CreateLazyComponentOptions<P>): ComponentType<P>;
110
110
 
111
+ /**
112
+ * Declarative rule for handling a custom URL scheme inside markdown.
113
+ *
114
+ * Each rule owns one href shape (e.g. `cmdop://machine/<uuid>`,
115
+ * `obsidian://open?path=…`) and decides:
116
+ *
117
+ * 1. Whether the URL scheme survives sanitize (`protocols`).
118
+ * 2. How to massage the source markdown before render
119
+ * (`preprocess` — optional; useful for stripping decorative
120
+ * prefixes like the `@` in `@[label](href)` so the chip alone
121
+ * reads as the mention indicator).
122
+ * 3. Which links it owns (`match`), and how to render them
123
+ * (`render`, returning JSX).
124
+ *
125
+ * Rules compose: pass an array, and the renderer iterates top-to-bottom
126
+ * picking the first `match` hit per `<a>`. Anything no rule claims
127
+ * falls through to the built-in chat-link styling (or to a custom
128
+ * `customComponents.a` if both are provided).
129
+ *
130
+ * Prefer `linkRules` over `customComponents` + `extraHrefProtocols` —
131
+ * it's the same pieces with the boilerplate removed.
132
+ */
133
+ interface LinkRule {
134
+ /** Pre-process the raw markdown source before any rendering happens.
135
+ * Pure / synchronous; identity-return when no transform applies. */
136
+ preprocess?: (source: string) => string;
137
+ /** Predicate against the resolved href. */
138
+ match: (href: string) => boolean;
139
+ /** Render the link this rule owns. `children` is the link's label
140
+ * (markdown-rendered React content). */
141
+ render: (props: {
142
+ href: string;
143
+ children: React__default.ReactNode;
144
+ isUser: boolean;
145
+ }) => React__default.ReactNode;
146
+ /** URL scheme(s) to whitelist on the sanitize side, e.g. `['cmdop']`.
147
+ * Bare schemes only — no `://`. Optional; omit for `http(s)` rules. */
148
+ protocols?: readonly string[];
149
+ /** Diagnostic label, surfaced in dev warnings. Optional. */
150
+ name?: string;
151
+ }
111
152
  interface MarkdownMessageProps {
112
153
  /** Markdown content to render */
113
154
  content: string;
@@ -122,26 +163,9 @@ interface MarkdownMessageProps {
122
163
  *
123
164
  * Use this when you need custom rendering for a specific tag without
124
165
  * losing the chat-tuned defaults (links, code blocks with copy, etc).
125
- * Example — render `cmdop://machine/<uuid>` links as a chip:
126
- *
127
- * ```tsx
128
- * <MarkdownMessage
129
- * content={text}
130
- * extraHrefProtocols={['cmdop']}
131
- * customComponents={{
132
- * a: ({ href, children }) =>
133
- * href?.startsWith('cmdop://machine/')
134
- * ? <MachineChip href={href}>{children}</MachineChip>
135
- * : <a href={href}>{children}</a>,
136
- * }}
137
- * />
138
- * ```
139
166
  *
140
- * If you provide a renderer for a tag the built-in version is replaced
141
- * for that tag; other tags keep the defaults.
142
- *
143
- * Important: when `customComponents` is set, the plain-text fast path
144
- * is bypassed for the affected content so your renderers always run.
167
+ * For custom URL-scheme link handling, prefer `linkRules` it's the
168
+ * same ergonomics with the per-consumer boilerplate removed.
145
169
  */
146
170
  customComponents?: Partial<Components>;
147
171
  /**
@@ -156,55 +180,51 @@ interface MarkdownMessageProps {
156
180
  * HTML can do via clicks. Stick to schemes you control.
157
181
  */
158
182
  extraHrefProtocols?: readonly string[];
183
+ /**
184
+ * Declarative link rules — preferred over `customComponents` +
185
+ * `extraHrefProtocols` for chat-style apps that want pluggable
186
+ * URL-scheme handling without re-implementing the custom-`a`
187
+ * boilerplate per consumer.
188
+ *
189
+ * If you provide both `linkRules` and `extraHrefProtocols`, the
190
+ * effective protocol list is the union of the two.
191
+ */
192
+ linkRules?: readonly LinkRule[];
159
193
  /**
160
194
  * Enable collapsible "Read more..." functionality
161
- * When enabled, long content will be truncated with a toggle button
162
195
  * @default false
163
196
  */
164
197
  collapsible?: boolean;
165
- /**
166
- * Maximum character length before showing "Read more..."
167
- * Only applies when collapsible is true
168
- * If both maxLength and maxLines are set, the stricter limit applies
169
- */
198
+ /** Maximum character length before showing "Read more...". */
170
199
  maxLength?: number;
171
- /**
172
- * Maximum number of lines before showing "Read more..."
173
- * Only applies when collapsible is true
174
- * If both maxLength and maxLines are set, the stricter limit applies
175
- */
200
+ /** Maximum number of lines before showing "Read more...". */
176
201
  maxLines?: number;
177
- /**
178
- * Custom "Read more" button text
179
- * @default "Read more..."
180
- */
202
+ /** Custom "Read more" button text. @default "Read more..." */
181
203
  readMoreLabel?: string;
182
- /**
183
- * Custom "Show less" button text
184
- * @default "Show less"
185
- */
204
+ /** Custom "Show less" button text. @default "Show less" */
186
205
  showLessLabel?: string;
187
- /**
188
- * Start expanded (only applies when collapsible is true)
189
- * @default false
190
- */
206
+ /** Start expanded (only when `collapsible` is true). @default false */
191
207
  defaultExpanded?: boolean;
192
- /**
193
- * Callback when collapsed state changes
194
- */
208
+ /** Callback when collapsed state changes */
195
209
  onCollapseChange?: (isCollapsed: boolean) => void;
196
210
  }
211
+
197
212
  /**
198
- * MarkdownMessage - Renders markdown content with syntax highlighting and GFM support
213
+ * MarkdownMessage — chat-tuned markdown renderer.
199
214
  *
200
215
  * Features:
201
- * - GitHub Flavored Markdown (GFM) support
202
- * - Syntax highlighted code blocks with copy button
203
- * - Mermaid diagram rendering
204
- * - Tables, lists, blockquotes
205
- * - User/assistant styling modes
206
- * - Plain text optimization (skips ReactMarkdown for simple text)
207
- * - Collapsible "Read more..." for long messages
216
+ * - GitHub Flavored Markdown (GFM) via remark-gfm
217
+ * - Syntax-highlighted code blocks with Copy button
218
+ * - Mermaid diagram rendering (` ```mermaid ` fence)
219
+ * - Tables, lists, blockquotes scaled for chat density
220
+ * - User vs assistant styling modes (`isUser`)
221
+ * - Plain-text fast path: skips ReactMarkdown when content has no
222
+ * markdown syntax (cheaper render, preserves newlines via CSS)
223
+ * - Optional collapsible "Read more..." for long messages
224
+ *
225
+ * Custom URL schemes (chat mentions, deep-links, custom file viewers)
226
+ * are best handled with the declarative `linkRules` prop — see the
227
+ * type definition in `./types.ts` and the storybook for examples.
208
228
  *
209
229
  * @example
210
230
  * ```tsx
@@ -213,17 +233,20 @@ interface MarkdownMessageProps {
213
233
  * // User message styling
214
234
  * <MarkdownMessage content="Some content" isUser />
215
235
  *
216
- * // Collapsible long content (for chat apps)
236
+ * // Custom URL scheme via linkRules
217
237
  * <MarkdownMessage
218
- * content={longText}
219
- * collapsible
220
- * maxLength={300}
221
- * maxLines={5}
238
+ * content="Talk to [Vps-audi](cmdop://machine/abc-123)"
239
+ * linkRules={[machineMentionRule]}
222
240
  * />
223
241
  * ```
224
242
  */
225
243
  declare const MarkdownMessage: React__default.FC<MarkdownMessageProps>;
226
244
 
245
+ /** Recursively concatenate the text content of a React.ReactNode tree.
246
+ * Used by the markdown renderers (e.g. `<pre>` extracting code) and
247
+ * by consumers that need a plain-string label out of link children. */
248
+ declare function extractTextFromChildren(children: React__default.ReactNode): string;
249
+
227
250
  interface UseCollapsibleContentOptions {
228
251
  /**
229
252
  * Maximum character length before collapsing
@@ -2774,4 +2797,4 @@ declare function useBlobUrlCleanup(key: string | null): void;
2774
2797
  */
2775
2798
  declare function generateContentKey(content: ArrayBuffer): string;
2776
2799
 
2777
- export { type ApiKey, ArrayFieldItemTemplate, ArrayFieldTemplate, type AspectRatioValue, type AudioLevels, AudioReactiveCover, type AudioReactiveCoverProps, BaseInputTemplate, type BlobSource, COLOR_SCHEMES, COLOR_SCHEME_INFO, CardLoadingFallback, CheckboxWidget, ColorWidget, type CreateLazyComponentOptions, type CreateVideoErrorFallbackOptions, CronScheduler, type CronSchedulerContextValue, type CronSchedulerProps, CronSchedulerProvider, type CronSchedulerState, CustomInput, type DASHSource, type DataUrlSource, DayChips, DiffEditor, type DiffEditorProps, EFFECT_ANIMATIONS, Editor, type EditorContextValue, type EditorFile, type EditorOptions, type EditorProps, EditorProvider, type EditorRef, type EffectColorScheme, type EffectColors, type EffectConfig$1 as EffectConfig, type EffectIntensity, type EffectLayer, type EffectVariant, type EqualizerOptions, type ErrorFallbackProps, ErrorListTemplate, FieldTemplate, GlowEffect, type GlowEffectData, type HLSSource, type HybridAudioContextValue, type HybridAudioControls, HybridAudioPlayer, type HybridAudioPlayerProps, HybridAudioProvider, type HybridAudioProviderProps, type HybridAudioState, type HybridCompactPlayerProps, HybridSimplePlayer, type HybridSimplePlayerProps, HybridWaveform, type HybridWaveformProps, type HybridWebAudioAPI, INTENSITY_CONFIG, INTENSITY_INFO, type ImageFile, ImageViewer, type ImageViewerProps, JsonSchemaForm, type JsonSchemaFormProps, JsonTreeComponent as JsonTree, type JsonTreeConfig, type JsonTreeProps, LazyCronScheduler, LazyHybridAudioPlayer, LazyHybridCompactPlayer, LazyHybridSimplePlayer, LazyImageViewer, LazyJsonSchemaForm, LazyJsonTree, LazyLottiePlayer, LazyMapContainer, LazyMapView, LazyMermaid, LazyOpenapiViewer, LazyPrettyCode, LazyVideoPlayer, LazyWrapper, type LazyWrapperProps, LoadingFallback, type LoadingFallbackProps, type LottieDirection, LottiePlayer, type LottiePlayerProps, type LottieSize, type LottieSpeed, type MapContainerProps, MapLoadingFallback, type MapStyleKey, type MapViewport, MarkdownEditor, type MarkdownEditorProps, MarkdownMessage, type MarkdownMessageProps, type MarkerData, type MentionAttrs, type MentionConfig, type MentionItem, type MentionMarkdownRenderer, Mermaid, type MermaidProps, MeshEffect, type MonthDay, MonthDayGrid, NativeProvider, NumberWidget, ObjectFieldTemplate, Playground as OpenapiViewer, OrbsEffect, type PlayerMode, type PlaygroundConfig, type PlaygroundProps$1 as PlaygroundProps, PrettyCode, type PrettyCodeProps$1 as PrettyCodeProps, type ResolveFileSourceOptions, SchedulePreview, type ScheduleType, ScheduleTypeSelector, type SchemaSource, SelectWidget, type SimpleStreamSource, SliderWidget, Spinner, SpotlightEffect, StreamProvider, type StreamSource, SwitchWidget, TextWidget, TimeSelector, type UrlSource, type UseAudioBusReturn, type UseAudioVisualizationReturn, type UseCollapsibleContentOptions, type UseCollapsibleContentResult, type UseEditorReturn, type UseHybridAudioOptions, type UseHybridAudioReturn, type UseLottieOptions, type UseLottieReturn, type UseMonacoReturn, type UseVisualizationReturn, VARIANT_INFO, VideoControls, VideoErrorFallback, type VideoErrorFallbackProps, VideoPlayer, type VideoPlayerContextValue, type VideoPlayerProps, VideoPlayerProvider, type VideoPlayerProviderProps, type VideoPlayerRef, type VideoSourceUnion, VidstackProvider, type VimeoSource, type VisualizationColorScheme, type VisualizationIntensity, VisualizationProvider, type VisualizationProviderProps, type VisualizationSettings, type VisualizationVariant, type WeekDay, type YouTubeSource, buildCron, calculateGlowLayers, calculateMeshGradients, calculateOrbs, calculateSpotlight, createLazyComponent, createVideoErrorFallback, formatTime, generateContentKey, getColors, getEffectConfig, getRequiredFields, hasRequiredFields, humanizeCron, isSimpleStreamSource, isValidCron, mentionPresets, mergeDefaults, normalizeFormData, parseCron, prepareEffectColors, resolveFileSource, resolvePlayerMode, resolveStreamSource, safeJsonParse, safeJsonStringify, useAudioBus, useAudioBusStore, useAudioCache, useAudioVisualization, useBlobUrlCleanup, useCollapsibleContent, useCronCustom, useCronMonthDays, useCronPreview, useCronScheduler, useCronSchedulerContext, useCronTime, useCronType, useCronWeekDays, useEditor, useEditorContext, useHybridAudio, useHybridAudioAnalysis, useHybridAudioContext, useHybridAudioControls, useHybridAudioLevels, useHybridAudioState, useHybridWebAudio, useImageCache, useLanguage, useLottie, useMediaCacheStore, useMonaco, useVideoCache, useVideoPlayerContext, useVideoPlayerSettings, useVisualization, validateRequiredFields, validateSchema };
2800
+ export { type ApiKey, ArrayFieldItemTemplate, ArrayFieldTemplate, type AspectRatioValue, type AudioLevels, AudioReactiveCover, type AudioReactiveCoverProps, BaseInputTemplate, type BlobSource, COLOR_SCHEMES, COLOR_SCHEME_INFO, CardLoadingFallback, CheckboxWidget, ColorWidget, type CreateLazyComponentOptions, type CreateVideoErrorFallbackOptions, CronScheduler, type CronSchedulerContextValue, type CronSchedulerProps, CronSchedulerProvider, type CronSchedulerState, CustomInput, type DASHSource, type DataUrlSource, DayChips, DiffEditor, type DiffEditorProps, EFFECT_ANIMATIONS, Editor, type EditorContextValue, type EditorFile, type EditorOptions, type EditorProps, EditorProvider, type EditorRef, type EffectColorScheme, type EffectColors, type EffectConfig$1 as EffectConfig, type EffectIntensity, type EffectLayer, type EffectVariant, type EqualizerOptions, type ErrorFallbackProps, ErrorListTemplate, FieldTemplate, GlowEffect, type GlowEffectData, type HLSSource, type HybridAudioContextValue, type HybridAudioControls, HybridAudioPlayer, type HybridAudioPlayerProps, HybridAudioProvider, type HybridAudioProviderProps, type HybridAudioState, type HybridCompactPlayerProps, HybridSimplePlayer, type HybridSimplePlayerProps, HybridWaveform, type HybridWaveformProps, type HybridWebAudioAPI, INTENSITY_CONFIG, INTENSITY_INFO, type ImageFile, ImageViewer, type ImageViewerProps, JsonSchemaForm, type JsonSchemaFormProps, JsonTreeComponent as JsonTree, type JsonTreeConfig, type JsonTreeProps, LazyCronScheduler, LazyHybridAudioPlayer, LazyHybridCompactPlayer, LazyHybridSimplePlayer, LazyImageViewer, LazyJsonSchemaForm, LazyJsonTree, LazyLottiePlayer, LazyMapContainer, LazyMapView, LazyMermaid, LazyOpenapiViewer, LazyPrettyCode, LazyVideoPlayer, LazyWrapper, type LazyWrapperProps, type LinkRule, LoadingFallback, type LoadingFallbackProps, type LottieDirection, LottiePlayer, type LottiePlayerProps, type LottieSize, type LottieSpeed, type MapContainerProps, MapLoadingFallback, type MapStyleKey, type MapViewport, MarkdownEditor, type MarkdownEditorProps, MarkdownMessage, type MarkdownMessageProps, type MarkerData, type MentionAttrs, type MentionConfig, type MentionItem, type MentionMarkdownRenderer, Mermaid, type MermaidProps, MeshEffect, type MonthDay, MonthDayGrid, NativeProvider, NumberWidget, ObjectFieldTemplate, Playground as OpenapiViewer, OrbsEffect, type PlayerMode, type PlaygroundConfig, type PlaygroundProps$1 as PlaygroundProps, PrettyCode, type PrettyCodeProps$1 as PrettyCodeProps, type ResolveFileSourceOptions, SchedulePreview, type ScheduleType, ScheduleTypeSelector, type SchemaSource, SelectWidget, type SimpleStreamSource, SliderWidget, Spinner, SpotlightEffect, StreamProvider, type StreamSource, SwitchWidget, TextWidget, TimeSelector, type UrlSource, type UseAudioBusReturn, type UseAudioVisualizationReturn, type UseCollapsibleContentOptions, type UseCollapsibleContentResult, type UseEditorReturn, type UseHybridAudioOptions, type UseHybridAudioReturn, type UseLottieOptions, type UseLottieReturn, type UseMonacoReturn, type UseVisualizationReturn, VARIANT_INFO, VideoControls, VideoErrorFallback, type VideoErrorFallbackProps, VideoPlayer, type VideoPlayerContextValue, type VideoPlayerProps, VideoPlayerProvider, type VideoPlayerProviderProps, type VideoPlayerRef, type VideoSourceUnion, VidstackProvider, type VimeoSource, type VisualizationColorScheme, type VisualizationIntensity, VisualizationProvider, type VisualizationProviderProps, type VisualizationSettings, type VisualizationVariant, type WeekDay, type YouTubeSource, buildCron, calculateGlowLayers, calculateMeshGradients, calculateOrbs, calculateSpotlight, createLazyComponent, createVideoErrorFallback, extractTextFromChildren, formatTime, generateContentKey, getColors, getEffectConfig, getRequiredFields, hasRequiredFields, humanizeCron, isSimpleStreamSource, isValidCron, mentionPresets, mergeDefaults, normalizeFormData, parseCron, prepareEffectColors, resolveFileSource, resolvePlayerMode, resolveStreamSource, safeJsonParse, safeJsonStringify, useAudioBus, useAudioBusStore, useAudioCache, useAudioVisualization, useBlobUrlCleanup, useCollapsibleContent, useCronCustom, useCronMonthDays, useCronPreview, useCronScheduler, useCronSchedulerContext, useCronTime, useCronType, useCronWeekDays, useEditor, useEditorContext, useHybridAudio, useHybridAudioAnalysis, useHybridAudioContext, useHybridAudioControls, useHybridAudioLevels, useHybridAudioState, useHybridWebAudio, useImageCache, useLanguage, useLottie, useMediaCacheStore, useMonaco, useVideoCache, useVideoPlayerContext, useVideoPlayerSettings, useVisualization, validateRequiredFields, validateSchema };
package/dist/index.d.ts CHANGED
@@ -108,6 +108,47 @@ declare function createLazyComponent<P extends object>(loader: () => Promise<{
108
108
  default: ComponentType<P>;
109
109
  }>, options?: CreateLazyComponentOptions<P>): ComponentType<P>;
110
110
 
111
+ /**
112
+ * Declarative rule for handling a custom URL scheme inside markdown.
113
+ *
114
+ * Each rule owns one href shape (e.g. `cmdop://machine/<uuid>`,
115
+ * `obsidian://open?path=…`) and decides:
116
+ *
117
+ * 1. Whether the URL scheme survives sanitize (`protocols`).
118
+ * 2. How to massage the source markdown before render
119
+ * (`preprocess` — optional; useful for stripping decorative
120
+ * prefixes like the `@` in `@[label](href)` so the chip alone
121
+ * reads as the mention indicator).
122
+ * 3. Which links it owns (`match`), and how to render them
123
+ * (`render`, returning JSX).
124
+ *
125
+ * Rules compose: pass an array, and the renderer iterates top-to-bottom
126
+ * picking the first `match` hit per `<a>`. Anything no rule claims
127
+ * falls through to the built-in chat-link styling (or to a custom
128
+ * `customComponents.a` if both are provided).
129
+ *
130
+ * Prefer `linkRules` over `customComponents` + `extraHrefProtocols` —
131
+ * it's the same pieces with the boilerplate removed.
132
+ */
133
+ interface LinkRule {
134
+ /** Pre-process the raw markdown source before any rendering happens.
135
+ * Pure / synchronous; identity-return when no transform applies. */
136
+ preprocess?: (source: string) => string;
137
+ /** Predicate against the resolved href. */
138
+ match: (href: string) => boolean;
139
+ /** Render the link this rule owns. `children` is the link's label
140
+ * (markdown-rendered React content). */
141
+ render: (props: {
142
+ href: string;
143
+ children: React__default.ReactNode;
144
+ isUser: boolean;
145
+ }) => React__default.ReactNode;
146
+ /** URL scheme(s) to whitelist on the sanitize side, e.g. `['cmdop']`.
147
+ * Bare schemes only — no `://`. Optional; omit for `http(s)` rules. */
148
+ protocols?: readonly string[];
149
+ /** Diagnostic label, surfaced in dev warnings. Optional. */
150
+ name?: string;
151
+ }
111
152
  interface MarkdownMessageProps {
112
153
  /** Markdown content to render */
113
154
  content: string;
@@ -122,26 +163,9 @@ interface MarkdownMessageProps {
122
163
  *
123
164
  * Use this when you need custom rendering for a specific tag without
124
165
  * losing the chat-tuned defaults (links, code blocks with copy, etc).
125
- * Example — render `cmdop://machine/<uuid>` links as a chip:
126
- *
127
- * ```tsx
128
- * <MarkdownMessage
129
- * content={text}
130
- * extraHrefProtocols={['cmdop']}
131
- * customComponents={{
132
- * a: ({ href, children }) =>
133
- * href?.startsWith('cmdop://machine/')
134
- * ? <MachineChip href={href}>{children}</MachineChip>
135
- * : <a href={href}>{children}</a>,
136
- * }}
137
- * />
138
- * ```
139
166
  *
140
- * If you provide a renderer for a tag the built-in version is replaced
141
- * for that tag; other tags keep the defaults.
142
- *
143
- * Important: when `customComponents` is set, the plain-text fast path
144
- * is bypassed for the affected content so your renderers always run.
167
+ * For custom URL-scheme link handling, prefer `linkRules` it's the
168
+ * same ergonomics with the per-consumer boilerplate removed.
145
169
  */
146
170
  customComponents?: Partial<Components>;
147
171
  /**
@@ -156,55 +180,51 @@ interface MarkdownMessageProps {
156
180
  * HTML can do via clicks. Stick to schemes you control.
157
181
  */
158
182
  extraHrefProtocols?: readonly string[];
183
+ /**
184
+ * Declarative link rules — preferred over `customComponents` +
185
+ * `extraHrefProtocols` for chat-style apps that want pluggable
186
+ * URL-scheme handling without re-implementing the custom-`a`
187
+ * boilerplate per consumer.
188
+ *
189
+ * If you provide both `linkRules` and `extraHrefProtocols`, the
190
+ * effective protocol list is the union of the two.
191
+ */
192
+ linkRules?: readonly LinkRule[];
159
193
  /**
160
194
  * Enable collapsible "Read more..." functionality
161
- * When enabled, long content will be truncated with a toggle button
162
195
  * @default false
163
196
  */
164
197
  collapsible?: boolean;
165
- /**
166
- * Maximum character length before showing "Read more..."
167
- * Only applies when collapsible is true
168
- * If both maxLength and maxLines are set, the stricter limit applies
169
- */
198
+ /** Maximum character length before showing "Read more...". */
170
199
  maxLength?: number;
171
- /**
172
- * Maximum number of lines before showing "Read more..."
173
- * Only applies when collapsible is true
174
- * If both maxLength and maxLines are set, the stricter limit applies
175
- */
200
+ /** Maximum number of lines before showing "Read more...". */
176
201
  maxLines?: number;
177
- /**
178
- * Custom "Read more" button text
179
- * @default "Read more..."
180
- */
202
+ /** Custom "Read more" button text. @default "Read more..." */
181
203
  readMoreLabel?: string;
182
- /**
183
- * Custom "Show less" button text
184
- * @default "Show less"
185
- */
204
+ /** Custom "Show less" button text. @default "Show less" */
186
205
  showLessLabel?: string;
187
- /**
188
- * Start expanded (only applies when collapsible is true)
189
- * @default false
190
- */
206
+ /** Start expanded (only when `collapsible` is true). @default false */
191
207
  defaultExpanded?: boolean;
192
- /**
193
- * Callback when collapsed state changes
194
- */
208
+ /** Callback when collapsed state changes */
195
209
  onCollapseChange?: (isCollapsed: boolean) => void;
196
210
  }
211
+
197
212
  /**
198
- * MarkdownMessage - Renders markdown content with syntax highlighting and GFM support
213
+ * MarkdownMessage — chat-tuned markdown renderer.
199
214
  *
200
215
  * Features:
201
- * - GitHub Flavored Markdown (GFM) support
202
- * - Syntax highlighted code blocks with copy button
203
- * - Mermaid diagram rendering
204
- * - Tables, lists, blockquotes
205
- * - User/assistant styling modes
206
- * - Plain text optimization (skips ReactMarkdown for simple text)
207
- * - Collapsible "Read more..." for long messages
216
+ * - GitHub Flavored Markdown (GFM) via remark-gfm
217
+ * - Syntax-highlighted code blocks with Copy button
218
+ * - Mermaid diagram rendering (` ```mermaid ` fence)
219
+ * - Tables, lists, blockquotes scaled for chat density
220
+ * - User vs assistant styling modes (`isUser`)
221
+ * - Plain-text fast path: skips ReactMarkdown when content has no
222
+ * markdown syntax (cheaper render, preserves newlines via CSS)
223
+ * - Optional collapsible "Read more..." for long messages
224
+ *
225
+ * Custom URL schemes (chat mentions, deep-links, custom file viewers)
226
+ * are best handled with the declarative `linkRules` prop — see the
227
+ * type definition in `./types.ts` and the storybook for examples.
208
228
  *
209
229
  * @example
210
230
  * ```tsx
@@ -213,17 +233,20 @@ interface MarkdownMessageProps {
213
233
  * // User message styling
214
234
  * <MarkdownMessage content="Some content" isUser />
215
235
  *
216
- * // Collapsible long content (for chat apps)
236
+ * // Custom URL scheme via linkRules
217
237
  * <MarkdownMessage
218
- * content={longText}
219
- * collapsible
220
- * maxLength={300}
221
- * maxLines={5}
238
+ * content="Talk to [Vps-audi](cmdop://machine/abc-123)"
239
+ * linkRules={[machineMentionRule]}
222
240
  * />
223
241
  * ```
224
242
  */
225
243
  declare const MarkdownMessage: React__default.FC<MarkdownMessageProps>;
226
244
 
245
+ /** Recursively concatenate the text content of a React.ReactNode tree.
246
+ * Used by the markdown renderers (e.g. `<pre>` extracting code) and
247
+ * by consumers that need a plain-string label out of link children. */
248
+ declare function extractTextFromChildren(children: React__default.ReactNode): string;
249
+
227
250
  interface UseCollapsibleContentOptions {
228
251
  /**
229
252
  * Maximum character length before collapsing
@@ -2774,4 +2797,4 @@ declare function useBlobUrlCleanup(key: string | null): void;
2774
2797
  */
2775
2798
  declare function generateContentKey(content: ArrayBuffer): string;
2776
2799
 
2777
- export { type ApiKey, ArrayFieldItemTemplate, ArrayFieldTemplate, type AspectRatioValue, type AudioLevels, AudioReactiveCover, type AudioReactiveCoverProps, BaseInputTemplate, type BlobSource, COLOR_SCHEMES, COLOR_SCHEME_INFO, CardLoadingFallback, CheckboxWidget, ColorWidget, type CreateLazyComponentOptions, type CreateVideoErrorFallbackOptions, CronScheduler, type CronSchedulerContextValue, type CronSchedulerProps, CronSchedulerProvider, type CronSchedulerState, CustomInput, type DASHSource, type DataUrlSource, DayChips, DiffEditor, type DiffEditorProps, EFFECT_ANIMATIONS, Editor, type EditorContextValue, type EditorFile, type EditorOptions, type EditorProps, EditorProvider, type EditorRef, type EffectColorScheme, type EffectColors, type EffectConfig$1 as EffectConfig, type EffectIntensity, type EffectLayer, type EffectVariant, type EqualizerOptions, type ErrorFallbackProps, ErrorListTemplate, FieldTemplate, GlowEffect, type GlowEffectData, type HLSSource, type HybridAudioContextValue, type HybridAudioControls, HybridAudioPlayer, type HybridAudioPlayerProps, HybridAudioProvider, type HybridAudioProviderProps, type HybridAudioState, type HybridCompactPlayerProps, HybridSimplePlayer, type HybridSimplePlayerProps, HybridWaveform, type HybridWaveformProps, type HybridWebAudioAPI, INTENSITY_CONFIG, INTENSITY_INFO, type ImageFile, ImageViewer, type ImageViewerProps, JsonSchemaForm, type JsonSchemaFormProps, JsonTreeComponent as JsonTree, type JsonTreeConfig, type JsonTreeProps, LazyCronScheduler, LazyHybridAudioPlayer, LazyHybridCompactPlayer, LazyHybridSimplePlayer, LazyImageViewer, LazyJsonSchemaForm, LazyJsonTree, LazyLottiePlayer, LazyMapContainer, LazyMapView, LazyMermaid, LazyOpenapiViewer, LazyPrettyCode, LazyVideoPlayer, LazyWrapper, type LazyWrapperProps, LoadingFallback, type LoadingFallbackProps, type LottieDirection, LottiePlayer, type LottiePlayerProps, type LottieSize, type LottieSpeed, type MapContainerProps, MapLoadingFallback, type MapStyleKey, type MapViewport, MarkdownEditor, type MarkdownEditorProps, MarkdownMessage, type MarkdownMessageProps, type MarkerData, type MentionAttrs, type MentionConfig, type MentionItem, type MentionMarkdownRenderer, Mermaid, type MermaidProps, MeshEffect, type MonthDay, MonthDayGrid, NativeProvider, NumberWidget, ObjectFieldTemplate, Playground as OpenapiViewer, OrbsEffect, type PlayerMode, type PlaygroundConfig, type PlaygroundProps$1 as PlaygroundProps, PrettyCode, type PrettyCodeProps$1 as PrettyCodeProps, type ResolveFileSourceOptions, SchedulePreview, type ScheduleType, ScheduleTypeSelector, type SchemaSource, SelectWidget, type SimpleStreamSource, SliderWidget, Spinner, SpotlightEffect, StreamProvider, type StreamSource, SwitchWidget, TextWidget, TimeSelector, type UrlSource, type UseAudioBusReturn, type UseAudioVisualizationReturn, type UseCollapsibleContentOptions, type UseCollapsibleContentResult, type UseEditorReturn, type UseHybridAudioOptions, type UseHybridAudioReturn, type UseLottieOptions, type UseLottieReturn, type UseMonacoReturn, type UseVisualizationReturn, VARIANT_INFO, VideoControls, VideoErrorFallback, type VideoErrorFallbackProps, VideoPlayer, type VideoPlayerContextValue, type VideoPlayerProps, VideoPlayerProvider, type VideoPlayerProviderProps, type VideoPlayerRef, type VideoSourceUnion, VidstackProvider, type VimeoSource, type VisualizationColorScheme, type VisualizationIntensity, VisualizationProvider, type VisualizationProviderProps, type VisualizationSettings, type VisualizationVariant, type WeekDay, type YouTubeSource, buildCron, calculateGlowLayers, calculateMeshGradients, calculateOrbs, calculateSpotlight, createLazyComponent, createVideoErrorFallback, formatTime, generateContentKey, getColors, getEffectConfig, getRequiredFields, hasRequiredFields, humanizeCron, isSimpleStreamSource, isValidCron, mentionPresets, mergeDefaults, normalizeFormData, parseCron, prepareEffectColors, resolveFileSource, resolvePlayerMode, resolveStreamSource, safeJsonParse, safeJsonStringify, useAudioBus, useAudioBusStore, useAudioCache, useAudioVisualization, useBlobUrlCleanup, useCollapsibleContent, useCronCustom, useCronMonthDays, useCronPreview, useCronScheduler, useCronSchedulerContext, useCronTime, useCronType, useCronWeekDays, useEditor, useEditorContext, useHybridAudio, useHybridAudioAnalysis, useHybridAudioContext, useHybridAudioControls, useHybridAudioLevels, useHybridAudioState, useHybridWebAudio, useImageCache, useLanguage, useLottie, useMediaCacheStore, useMonaco, useVideoCache, useVideoPlayerContext, useVideoPlayerSettings, useVisualization, validateRequiredFields, validateSchema };
2800
+ export { type ApiKey, ArrayFieldItemTemplate, ArrayFieldTemplate, type AspectRatioValue, type AudioLevels, AudioReactiveCover, type AudioReactiveCoverProps, BaseInputTemplate, type BlobSource, COLOR_SCHEMES, COLOR_SCHEME_INFO, CardLoadingFallback, CheckboxWidget, ColorWidget, type CreateLazyComponentOptions, type CreateVideoErrorFallbackOptions, CronScheduler, type CronSchedulerContextValue, type CronSchedulerProps, CronSchedulerProvider, type CronSchedulerState, CustomInput, type DASHSource, type DataUrlSource, DayChips, DiffEditor, type DiffEditorProps, EFFECT_ANIMATIONS, Editor, type EditorContextValue, type EditorFile, type EditorOptions, type EditorProps, EditorProvider, type EditorRef, type EffectColorScheme, type EffectColors, type EffectConfig$1 as EffectConfig, type EffectIntensity, type EffectLayer, type EffectVariant, type EqualizerOptions, type ErrorFallbackProps, ErrorListTemplate, FieldTemplate, GlowEffect, type GlowEffectData, type HLSSource, type HybridAudioContextValue, type HybridAudioControls, HybridAudioPlayer, type HybridAudioPlayerProps, HybridAudioProvider, type HybridAudioProviderProps, type HybridAudioState, type HybridCompactPlayerProps, HybridSimplePlayer, type HybridSimplePlayerProps, HybridWaveform, type HybridWaveformProps, type HybridWebAudioAPI, INTENSITY_CONFIG, INTENSITY_INFO, type ImageFile, ImageViewer, type ImageViewerProps, JsonSchemaForm, type JsonSchemaFormProps, JsonTreeComponent as JsonTree, type JsonTreeConfig, type JsonTreeProps, LazyCronScheduler, LazyHybridAudioPlayer, LazyHybridCompactPlayer, LazyHybridSimplePlayer, LazyImageViewer, LazyJsonSchemaForm, LazyJsonTree, LazyLottiePlayer, LazyMapContainer, LazyMapView, LazyMermaid, LazyOpenapiViewer, LazyPrettyCode, LazyVideoPlayer, LazyWrapper, type LazyWrapperProps, type LinkRule, LoadingFallback, type LoadingFallbackProps, type LottieDirection, LottiePlayer, type LottiePlayerProps, type LottieSize, type LottieSpeed, type MapContainerProps, MapLoadingFallback, type MapStyleKey, type MapViewport, MarkdownEditor, type MarkdownEditorProps, MarkdownMessage, type MarkdownMessageProps, type MarkerData, type MentionAttrs, type MentionConfig, type MentionItem, type MentionMarkdownRenderer, Mermaid, type MermaidProps, MeshEffect, type MonthDay, MonthDayGrid, NativeProvider, NumberWidget, ObjectFieldTemplate, Playground as OpenapiViewer, OrbsEffect, type PlayerMode, type PlaygroundConfig, type PlaygroundProps$1 as PlaygroundProps, PrettyCode, type PrettyCodeProps$1 as PrettyCodeProps, type ResolveFileSourceOptions, SchedulePreview, type ScheduleType, ScheduleTypeSelector, type SchemaSource, SelectWidget, type SimpleStreamSource, SliderWidget, Spinner, SpotlightEffect, StreamProvider, type StreamSource, SwitchWidget, TextWidget, TimeSelector, type UrlSource, type UseAudioBusReturn, type UseAudioVisualizationReturn, type UseCollapsibleContentOptions, type UseCollapsibleContentResult, type UseEditorReturn, type UseHybridAudioOptions, type UseHybridAudioReturn, type UseLottieOptions, type UseLottieReturn, type UseMonacoReturn, type UseVisualizationReturn, VARIANT_INFO, VideoControls, VideoErrorFallback, type VideoErrorFallbackProps, VideoPlayer, type VideoPlayerContextValue, type VideoPlayerProps, VideoPlayerProvider, type VideoPlayerProviderProps, type VideoPlayerRef, type VideoSourceUnion, VidstackProvider, type VimeoSource, type VisualizationColorScheme, type VisualizationIntensity, VisualizationProvider, type VisualizationProviderProps, type VisualizationSettings, type VisualizationVariant, type WeekDay, type YouTubeSource, buildCron, calculateGlowLayers, calculateMeshGradients, calculateOrbs, calculateSpotlight, createLazyComponent, createVideoErrorFallback, extractTextFromChildren, formatTime, generateContentKey, getColors, getEffectConfig, getRequiredFields, hasRequiredFields, humanizeCron, isSimpleStreamSource, isValidCron, mentionPresets, mergeDefaults, normalizeFormData, parseCron, prepareEffectColors, resolveFileSource, resolvePlayerMode, resolveStreamSource, safeJsonParse, safeJsonStringify, useAudioBus, useAudioBusStore, useAudioCache, useAudioVisualization, useBlobUrlCleanup, useCollapsibleContent, useCronCustom, useCronMonthDays, useCronPreview, useCronScheduler, useCronSchedulerContext, useCronTime, useCronType, useCronWeekDays, useEditor, useEditorContext, useHybridAudio, useHybridAudioAnalysis, useHybridAudioContext, useHybridAudioControls, useHybridAudioLevels, useHybridAudioState, useHybridWebAudio, useImageCache, useLanguage, useLottie, useMediaCacheStore, useMonaco, useVideoCache, useVideoPlayerContext, useVideoPlayerSettings, useVisualization, validateRequiredFields, validateSchema };
package/dist/index.mjs CHANGED
@@ -4,8 +4,8 @@ import './chunk-JWB2EWQO.mjs';
4
4
  export { ImageViewer } from './chunk-GGKGH5PM.mjs';
5
5
  export { generateContentKey, useAudioCache, useBlobUrlCleanup, useImageCache, useMediaCacheStore, useVideoCache, useVideoPlayerSettings } from './chunk-5LBDYFWH.mjs';
6
6
  export { CronSchedulerProvider, CustomInput, DayChips, MonthDayGrid, SchedulePreview, ScheduleTypeSelector, TimeSelector, buildCron, humanizeCron, isValidCron, parseCron, useCronCustom, useCronMonthDays, useCronPreview, useCronScheduler, useCronSchedulerContext, useCronTime, useCronType, useCronWeekDays } from './chunk-PZKAH7WQ.mjs';
7
- import { PlaygroundProvider } from './chunk-DKJTH4GE.mjs';
8
- export { MarkdownMessage, Mermaid_default as Mermaid, PrettyCode_default as PrettyCode, useCollapsibleContent } from './chunk-DKJTH4GE.mjs';
7
+ import { PlaygroundProvider } from './chunk-CKD7GNE5.mjs';
8
+ export { MarkdownMessage, Mermaid_default as Mermaid, PrettyCode_default as PrettyCode, extractTextFromChildren, useCollapsibleContent } from './chunk-CKD7GNE5.mjs';
9
9
  export { JsonTree_default as JsonTree } from './chunk-LFWQ36LJ.mjs';
10
10
  import './chunk-SSUOENAZ.mjs';
11
11
  export { ArrayFieldItemTemplate, ArrayFieldTemplate, BaseInputTemplate, CheckboxWidget, ColorWidget, ErrorListTemplate, FieldTemplate, JsonSchemaForm, NumberWidget, ObjectFieldTemplate, SelectWidget, SliderWidget, SwitchWidget, TextWidget, getRequiredFields, hasRequiredFields, mergeDefaults, normalizeFormData, safeJsonParse, safeJsonStringify, validateRequiredFields, validateSchema } from './chunk-JUGQNNDC.mjs';
@@ -238,7 +238,7 @@ function OpenapiLoadingFallback() {
238
238
  }
239
239
  __name(OpenapiLoadingFallback, "OpenapiLoadingFallback");
240
240
  var LazyDocsLayout = createLazyComponent(
241
- () => import('./DocsLayout-3HNAQQRE.mjs').then((mod) => ({ default: mod.DocsLayout })),
241
+ () => import('./DocsLayout-MWRKNFXR.mjs').then((mod) => ({ default: mod.DocsLayout })),
242
242
  {
243
243
  displayName: "LazyDocsLayout",
244
244
  fallback: /* @__PURE__ */ jsx(OpenapiLoadingFallback, {})
@@ -393,7 +393,7 @@ function LottiePlayer(props) {
393
393
  }
394
394
  __name(LottiePlayer, "LottiePlayer");
395
395
  var DocsLayout = lazy(
396
- () => import('./DocsLayout-3HNAQQRE.mjs').then((mod) => ({ default: mod.DocsLayout }))
396
+ () => import('./DocsLayout-MWRKNFXR.mjs').then((mod) => ({ default: mod.DocsLayout }))
397
397
  );
398
398
  var LoadingFallback7 = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: "Loading API Playground..." }) }), "LoadingFallback");
399
399
  var Playground = /* @__PURE__ */ __name(({ config }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-tools",
3
- "version": "2.1.297",
3
+ "version": "2.1.299",
4
4
  "description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
5
5
  "keywords": [
6
6
  "ui-tools",
@@ -91,8 +91,8 @@
91
91
  "check": "tsc --noEmit"
92
92
  },
93
93
  "peerDependencies": {
94
- "@djangocfg/i18n": "^2.1.297",
95
- "@djangocfg/ui-core": "^2.1.297",
94
+ "@djangocfg/i18n": "^2.1.299",
95
+ "@djangocfg/ui-core": "^2.1.299",
96
96
  "consola": "^3.4.2",
97
97
  "lodash-es": "^4.18.1",
98
98
  "lucide-react": "^0.545.0",
@@ -140,10 +140,10 @@
140
140
  "@maplibre/maplibre-gl-geocoder": "^1.7.0"
141
141
  },
142
142
  "devDependencies": {
143
- "@djangocfg/i18n": "^2.1.297",
143
+ "@djangocfg/i18n": "^2.1.299",
144
144
  "@djangocfg/playground": "workspace:*",
145
- "@djangocfg/typescript-config": "^2.1.297",
146
- "@djangocfg/ui-core": "^2.1.297",
145
+ "@djangocfg/typescript-config": "^2.1.299",
146
+ "@djangocfg/ui-core": "^2.1.299",
147
147
  "@types/lodash-es": "^4.17.12",
148
148
  "@types/mapbox__mapbox-gl-draw": "^1.4.8",
149
149
  "@types/node": "^24.7.2",
@@ -0,0 +1,69 @@
1
+ import React from 'react';
2
+ import { CopyButton } from '@djangocfg/ui-core/components';
3
+ import { useResolvedTheme } from '@djangocfg/ui-core/hooks';
4
+ import PrettyCode from '../../../tools/PrettyCode';
5
+
6
+ interface CodeBlockProps {
7
+ code: string;
8
+ language: string;
9
+ isUser: boolean;
10
+ isCompact?: boolean;
11
+ }
12
+
13
+ /** Code block with a hover-revealed Copy button on top of PrettyCode. */
14
+ export const CodeBlock: React.FC<CodeBlockProps> = ({ code, language, isUser, isCompact = false }) => {
15
+ const theme = useResolvedTheme();
16
+
17
+ return (
18
+ <div className="relative group my-3">
19
+ <CopyButton
20
+ value={code}
21
+ variant="ghost"
22
+ className={`
23
+ absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity
24
+ h-8 w-8
25
+ ${isUser
26
+ ? 'hover:bg-white/20 text-white'
27
+ : 'hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground'
28
+ }
29
+ `}
30
+ title="Copy code"
31
+ />
32
+
33
+ <PrettyCode
34
+ data={code}
35
+ language={language}
36
+ className={isCompact ? 'text-xs' : 'text-sm'}
37
+ customBg={isUser ? 'bg-white/10' : 'bg-muted dark:bg-muted'}
38
+ mode={theme}
39
+ isCompact={isCompact}
40
+ />
41
+ </div>
42
+ );
43
+ };
44
+
45
+ /** Simple `<pre>` fallback used when CodeBlock throws (lazy module
46
+ * failure, missing PrettyCode peer, etc). */
47
+ export const CodeBlockFallback: React.FC<CodeBlockProps> = ({ code, isUser }) => (
48
+ <div className="relative group my-3">
49
+ <CopyButton
50
+ value={code}
51
+ variant="ghost"
52
+ className={`
53
+ absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity
54
+ h-8 w-8
55
+ ${isUser
56
+ ? 'hover:bg-white/20 text-white'
57
+ : 'hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground'
58
+ }
59
+ `}
60
+ title="Copy code"
61
+ />
62
+ <pre className={`
63
+ p-3 rounded text-xs font-mono overflow-x-auto
64
+ ${isUser ? 'bg-white/10 text-white' : 'bg-muted text-foreground'}
65
+ `}>
66
+ <code>{code}</code>
67
+ </pre>
68
+ </div>
69
+ );
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+
3
+ interface CollapseToggleProps {
4
+ isCollapsed: boolean;
5
+ onClick: () => void;
6
+ readMoreLabel: string;
7
+ showLessLabel: string;
8
+ isUser: boolean;
9
+ isCompact: boolean;
10
+ }
11
+
12
+ /** "Read more..." / "Show less" button. */
13
+ export const CollapseToggle: React.FC<CollapseToggleProps> = ({
14
+ isCollapsed,
15
+ onClick,
16
+ readMoreLabel,
17
+ showLessLabel,
18
+ isUser,
19
+ isCompact,
20
+ }) => {
21
+ const textSize = isCompact ? 'text-xs' : 'text-sm';
22
+ return (
23
+ <button
24
+ type="button"
25
+ onClick={onClick}
26
+ className={`
27
+ ${textSize} font-medium cursor-pointer
28
+ transition-colors duration-200
29
+ ${isUser ? 'text-white/80 hover:text-white' : 'text-primary hover:text-primary/80'}
30
+ inline-flex items-center gap-1
31
+ mt-1
32
+ `}
33
+ >
34
+ {isCollapsed ? (
35
+ <>
36
+ {readMoreLabel}
37
+ <Chevron direction="down" />
38
+ </>
39
+ ) : (
40
+ <>
41
+ {showLessLabel}
42
+ <Chevron direction="up" />
43
+ </>
44
+ )}
45
+ </button>
46
+ );
47
+ };
48
+
49
+ function Chevron({ direction }: { direction: 'up' | 'down' }) {
50
+ return (
51
+ <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52
+ <path
53
+ strokeLinecap="round"
54
+ strokeLinejoin="round"
55
+ strokeWidth={2}
56
+ d={direction === 'down' ? 'M19 9l-7 7-7-7' : 'M5 15l7-7 7 7'}
57
+ />
58
+ </svg>
59
+ );
60
+ }