@miethe/ui 0.3.0 → 0.6.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/CHANGELOG.md +51 -0
- package/README.md +776 -9
- package/dist/components/content-viewer/ArticleViewer.d.ts +42 -0
- package/dist/components/content-viewer/ArticleViewer.d.ts.map +1 -0
- package/dist/components/content-viewer/ArticleViewer.js +321 -0
- package/dist/components/content-viewer/ArticleViewer.js.map +1 -0
- package/dist/components/content-viewer/FrontmatterHeader.d.ts +32 -0
- package/dist/components/content-viewer/FrontmatterHeader.d.ts.map +1 -0
- package/dist/components/content-viewer/FrontmatterHeader.js +95 -0
- package/dist/components/content-viewer/FrontmatterHeader.js.map +1 -0
- package/dist/components/content-viewer/callouts/Callout.d.ts +43 -0
- package/dist/components/content-viewer/callouts/Callout.d.ts.map +1 -0
- package/dist/components/content-viewer/callouts/Callout.js +86 -0
- package/dist/components/content-viewer/callouts/Callout.js.map +1 -0
- package/dist/components/content-viewer/callouts/index.d.ts +2 -0
- package/dist/components/content-viewer/callouts/index.d.ts.map +1 -0
- package/dist/components/content-viewer/callouts/index.js +2 -0
- package/dist/components/content-viewer/callouts/index.js.map +1 -0
- package/dist/components/content-viewer/index.d.ts +21 -0
- package/dist/components/content-viewer/index.d.ts.map +1 -0
- package/dist/components/content-viewer/index.js +29 -0
- package/dist/components/content-viewer/index.js.map +1 -0
- package/dist/components/content-viewer/plugins/index.d.ts +5 -0
- package/dist/components/content-viewer/plugins/index.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/index.js +5 -0
- package/dist/components/content-viewer/plugins/index.js.map +1 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.d.ts +63 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.js +120 -0
- package/dist/components/content-viewer/plugins/lowlightLoader.js.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.d.ts +44 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.js +122 -0
- package/dist/components/content-viewer/plugins/rehypeCodeHighlight.js.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.d.ts +59 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.js +79 -0
- package/dist/components/content-viewer/plugins/rehypeExternalLinks.js.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.d.ts +37 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.js +82 -0
- package/dist/components/content-viewer/plugins/rehypeHeadingIds.js.map +1 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.d.ts +39 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.js +77 -0
- package/dist/components/content-viewer/plugins/remarkCallouts.js.map +1 -0
- package/dist/components/content-viewer/plugins/slugify.d.ts +24 -0
- package/dist/components/content-viewer/plugins/slugify.d.ts.map +1 -0
- package/dist/components/content-viewer/plugins/slugify.js +31 -0
- package/dist/components/content-viewer/plugins/slugify.js.map +1 -0
- package/dist/components/content-viewer/sanitize.d.ts +75 -0
- package/dist/components/content-viewer/sanitize.d.ts.map +1 -0
- package/dist/components/content-viewer/sanitize.js +252 -0
- package/dist/components/content-viewer/sanitize.js.map +1 -0
- package/dist/components/content-viewer/types.d.ts +315 -0
- package/dist/components/content-viewer/types.d.ts.map +1 -0
- package/dist/components/content-viewer/types.js +8 -0
- package/dist/components/content-viewer/types.js.map +1 -0
- package/dist/components/content-viewer/variants.d.ts +71 -0
- package/dist/components/content-viewer/variants.d.ts.map +1 -0
- package/dist/components/content-viewer/variants.js +105 -0
- package/dist/components/content-viewer/variants.js.map +1 -0
- package/dist/content-viewer/ContentPane.d.ts +44 -1
- package/dist/content-viewer/ContentPane.d.ts.map +1 -1
- package/dist/content-viewer/ContentPane.js +139 -5
- package/dist/content-viewer/ContentPane.js.map +1 -1
- package/dist/content-viewer/FileTree.d.ts +23 -1
- package/dist/content-viewer/FileTree.d.ts.map +1 -1
- package/dist/content-viewer/FileTree.js +20 -5
- package/dist/content-viewer/FileTree.js.map +1 -1
- package/dist/content-viewer/index.d.ts +2 -0
- package/dist/content-viewer/index.d.ts.map +1 -1
- package/dist/content-viewer/index.js +2 -0
- package/dist/content-viewer/index.js.map +1 -1
- package/dist/diff/DiffViewer.js +3 -3
- package/dist/diff/DiffViewer.js.map +1 -1
- package/dist/discovery/discovery-card.d.ts +25 -0
- package/dist/discovery/discovery-card.d.ts.map +1 -0
- package/dist/discovery/discovery-card.js +265 -0
- package/dist/discovery/discovery-card.js.map +1 -0
- package/dist/discovery/index.d.ts +3 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +3 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/display/ContextInfoCard.d.ts +61 -0
- package/dist/display/ContextInfoCard.d.ts.map +1 -0
- package/dist/display/ContextInfoCard.js +45 -0
- package/dist/display/ContextInfoCard.js.map +1 -0
- package/dist/display/index.d.ts +2 -0
- package/dist/display/index.d.ts.map +1 -1
- package/dist/display/index.js +1 -0
- package/dist/display/index.js.map +1 -1
- package/dist/editor/CodeEditor.d.ts +39 -0
- package/dist/editor/CodeEditor.d.ts.map +1 -0
- package/dist/editor/CodeEditor.js +114 -0
- package/dist/editor/CodeEditor.js.map +1 -0
- package/dist/editor/MarkdownEditor.d.ts +3 -2
- package/dist/editor/MarkdownEditor.d.ts.map +1 -1
- package/dist/editor/MarkdownEditor.js +32 -80
- package/dist/editor/MarkdownEditor.js.map +1 -1
- package/dist/editor/SplitPreview.d.ts +10 -1
- package/dist/editor/SplitPreview.d.ts.map +1 -1
- package/dist/editor/SplitPreview.js +4 -2
- package/dist/editor/SplitPreview.js.map +1 -1
- package/dist/editor/codeLanguages.d.ts +28 -0
- package/dist/editor/codeLanguages.d.ts.map +1 -0
- package/dist/editor/codeLanguages.js +54 -0
- package/dist/editor/codeLanguages.js.map +1 -0
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +6 -0
- package/dist/editor/index.js.map +1 -1
- package/dist/editor/theme.d.ts +16 -0
- package/dist/editor/theme.d.ts.map +1 -0
- package/dist/editor/theme.js +82 -0
- package/dist/editor/theme.js.map +1 -0
- package/dist/filters/filter-bar.d.ts +14 -0
- package/dist/filters/filter-bar.d.ts.map +1 -0
- package/dist/filters/filter-bar.js +47 -0
- package/dist/filters/filter-bar.js.map +1 -0
- package/dist/filters/filter-slot-config.d.ts +239 -0
- package/dist/filters/filter-slot-config.d.ts.map +1 -0
- package/dist/filters/filter-slot-config.js +24 -0
- package/dist/filters/filter-slot-config.js.map +1 -0
- package/dist/filters/index.d.ts +2 -0
- package/dist/filters/index.d.ts.map +1 -1
- package/dist/filters/index.js +1 -0
- package/dist/filters/index.js.map +1 -1
- package/dist/primitives/Card.d.ts +28 -0
- package/dist/primitives/Card.d.ts.map +1 -0
- package/dist/primitives/Card.js +30 -0
- package/dist/primitives/Card.js.map +1 -0
- package/dist/primitives/CollectionPicker.d.ts +47 -0
- package/dist/primitives/CollectionPicker.d.ts.map +1 -0
- package/dist/primitives/CollectionPicker.js +105 -0
- package/dist/primitives/CollectionPicker.js.map +1 -0
- package/dist/primitives/CreateEntityDialog.d.ts +144 -0
- package/dist/primitives/CreateEntityDialog.d.ts.map +1 -0
- package/dist/primitives/CreateEntityDialog.js +379 -0
- package/dist/primitives/CreateEntityDialog.js.map +1 -0
- package/dist/primitives/FormField.d.ts +29 -0
- package/dist/primitives/FormField.d.ts.map +1 -0
- package/dist/primitives/FormField.js +27 -0
- package/dist/primitives/FormField.js.map +1 -0
- package/dist/primitives/Label.d.ts +20 -0
- package/dist/primitives/Label.d.ts.map +1 -0
- package/dist/primitives/Label.js +21 -0
- package/dist/primitives/Label.js.map +1 -0
- package/dist/primitives/SecretField.d.ts +28 -0
- package/dist/primitives/SecretField.d.ts.map +1 -0
- package/dist/primitives/SecretField.js +65 -0
- package/dist/primitives/SecretField.js.map +1 -0
- package/dist/primitives/Spinner.d.ts +16 -0
- package/dist/primitives/Spinner.d.ts.map +1 -0
- package/dist/primitives/Spinner.js +34 -0
- package/dist/primitives/Spinner.js.map +1 -0
- package/dist/primitives/Switch.d.ts +32 -0
- package/dist/primitives/Switch.d.ts.map +1 -0
- package/dist/primitives/Switch.js +43 -0
- package/dist/primitives/Switch.js.map +1 -0
- package/dist/primitives/index.d.ts +16 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +9 -0
- package/dist/primitives/index.js.map +1 -1
- package/dist/utils/type-colors.d.ts.map +1 -1
- package/dist/utils/type-colors.js +4 -0
- package/dist/utils/type-colors.js.map +1 -1
- package/package.json +40 -6
package/README.md
CHANGED
|
@@ -71,17 +71,37 @@ Or reference the workspace package directly in `package.json`:
|
|
|
71
71
|
|
|
72
72
|
## Subpath Imports
|
|
73
73
|
|
|
74
|
-
The package is organized into
|
|
74
|
+
The package is organized into eight submodules with tree-shakeable exports.
|
|
75
|
+
|
|
76
|
+
**IMPORTANT: Server Component Rule**
|
|
77
|
+
|
|
78
|
+
Never import from the root barrel (`@miethe/ui`) in a React Server Component. All re-exports lack a `'use client'` directive and will fail in server-component-only files. Use subpath imports instead:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// ✗ Server component — do not use root barrel
|
|
82
|
+
import { FileTree, ContentPane } from '@miethe/ui';
|
|
83
|
+
|
|
84
|
+
// ✓ Server component — use subpath imports
|
|
85
|
+
import { FileTree, ContentPane } from '@miethe/ui/content-viewer';
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Client components can safely use root barrel imports (the `'use client'` directive in submodules propagates to consumers).
|
|
89
|
+
|
|
90
|
+
**Available Subpaths**:
|
|
75
91
|
|
|
76
92
|
```typescript
|
|
77
93
|
// Content viewer components
|
|
78
|
-
import { FileTree, ContentPane, ContentViewerProvider } from '@miethe/ui/content-viewer';
|
|
94
|
+
import { FileTree, ContentPane, ArticleViewer, ContentViewerProvider } from '@miethe/ui/content-viewer';
|
|
79
95
|
|
|
80
96
|
// Diff viewing components
|
|
81
97
|
import { DiffViewer, DiffViewerSkeleton } from '@miethe/ui/diff';
|
|
82
98
|
|
|
99
|
+
// Discovery card (compact artifact discovery / search contexts)
|
|
100
|
+
import { DiscoveryCard } from '@miethe/ui/discovery';
|
|
101
|
+
import type { DiscoveryCardProps, AgentDiscoveryCandidate } from '@miethe/ui/discovery';
|
|
102
|
+
|
|
83
103
|
// Editor components
|
|
84
|
-
import { MarkdownEditor, SplitPreview } from '@miethe/ui/editor';
|
|
104
|
+
import { MarkdownEditor, SplitPreview, CodeEditor } from '@miethe/ui/editor';
|
|
85
105
|
|
|
86
106
|
// Display components
|
|
87
107
|
import { FrontmatterDisplay, FilePreviewPane } from '@miethe/ui/display';
|
|
@@ -136,6 +156,52 @@ module.exports = {
|
|
|
136
156
|
|
|
137
157
|
**Important**: Without Tailwind CSS configured, components will render with no styles. Components use semantic Tailwind classes like `text-muted-foreground` and `bg-background` — ensure your Tailwind config includes these classes (standard in shadcn/ui projects).
|
|
138
158
|
|
|
159
|
+
### Required shadcn-Compatible CSS Variables
|
|
160
|
+
|
|
161
|
+
Define these CSS variables at `:root` or an appropriate ancestor element. These variables control the color palette for all UI components:
|
|
162
|
+
|
|
163
|
+
```css
|
|
164
|
+
:root {
|
|
165
|
+
--background: 0 0% 100%;
|
|
166
|
+
--foreground: 0 0% 3.6%;
|
|
167
|
+
--muted: 0 0% 96.1%;
|
|
168
|
+
--muted-foreground: 0 0% 45.1%;
|
|
169
|
+
--card: 0 0% 100%;
|
|
170
|
+
--card-foreground: 0 0% 3.6%;
|
|
171
|
+
--border: 0 0% 89.8%;
|
|
172
|
+
--ring: 0 0% 3.6%;
|
|
173
|
+
--primary: 0 0% 9%;
|
|
174
|
+
--accent: 0 84.6% 60.2%;
|
|
175
|
+
--accent-foreground: 0 0% 100%;
|
|
176
|
+
--secondary: 0 0% 14.9%;
|
|
177
|
+
--secondary-foreground: 0 0% 100%;
|
|
178
|
+
--destructive: 0 84.3% 60.2%;
|
|
179
|
+
--input: 0 0% 89.8%;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@media (prefers-color-scheme: dark) {
|
|
183
|
+
:root {
|
|
184
|
+
--background: 0 0% 3.6%;
|
|
185
|
+
--foreground: 0 0% 98%;
|
|
186
|
+
--muted: 0 0% 14.9%;
|
|
187
|
+
--muted-foreground: 0 0% 63.9%;
|
|
188
|
+
--card: 0 0% 3.6%;
|
|
189
|
+
--card-foreground: 0 0% 98%;
|
|
190
|
+
--border: 0 0% 14.9%;
|
|
191
|
+
--ring: 0 84.6% 60.2%;
|
|
192
|
+
--primary: 0 0% 98%;
|
|
193
|
+
--accent: 0 84.6% 60.2%;
|
|
194
|
+
--accent-foreground: 0 0% 9%;
|
|
195
|
+
--secondary: 0 0% 85.1%;
|
|
196
|
+
--secondary-foreground: 0 0% 9%;
|
|
197
|
+
--destructive: 0 84.3% 60.2%;
|
|
198
|
+
--input: 0 0% 14.9%;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
All components degrade gracefully if some variables are missing — the browser will use default colors. Define only the ones you need to customize.
|
|
204
|
+
|
|
139
205
|
### Dark Mode
|
|
140
206
|
|
|
141
207
|
Components support dark mode via the Tailwind `dark:` variant. Enable dark mode in your Tailwind config:
|
|
@@ -163,6 +229,54 @@ Then add the `dark` class to your root HTML element to activate dark mode styles
|
|
|
163
229
|
|
|
164
230
|
Or use a theme provider component that toggles the class dynamically based on user preference.
|
|
165
231
|
|
|
232
|
+
### CodeMirror Deduplication
|
|
233
|
+
|
|
234
|
+
CodeMirror 6 requires exactly **one** `@codemirror/state` instance in the dependency tree. If multiple versions are installed, the editor will fail silently or behave unpredictably.
|
|
235
|
+
|
|
236
|
+
**Verify Installation**:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
npm ls @codemirror/state
|
|
240
|
+
# or
|
|
241
|
+
yarn why @codemirror/state
|
|
242
|
+
# or
|
|
243
|
+
pnpm ls @codemirror/state
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
If you see multiple versions, force deduplication in `package.json`:
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"pnpm": {
|
|
251
|
+
"overrides": {
|
|
252
|
+
"@codemirror/state": "^6.4.0"
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
For yarn, use `resolutions`:
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"resolutions": {
|
|
263
|
+
"@codemirror/state": "^6.4.0"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
For npm, add an `overrides` field (npm v8.3.0+):
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"overrides": {
|
|
273
|
+
"@codemirror/state": "^6.4.0"
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
After updating, reinstall and verify the deduplicated list shows only one entry.
|
|
279
|
+
|
|
166
280
|
## Quick Start
|
|
167
281
|
|
|
168
282
|
### 1. Create an Adapter
|
|
@@ -283,6 +397,67 @@ export function FilePreviewExample() {
|
|
|
283
397
|
|
|
284
398
|
## Components API
|
|
285
399
|
|
|
400
|
+
### DiscoveryCard
|
|
401
|
+
|
|
402
|
+
Compact single-tier artifact card for discovery and search result contexts. Designed for reuse across `/discover` and marketplace search results pages.
|
|
403
|
+
|
|
404
|
+
**Props:**
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
interface DiscoveryCardProps {
|
|
408
|
+
candidate: AgentDiscoveryCandidate; // The discovery candidate to display
|
|
409
|
+
isSelected: boolean; // Selection state (affects styling)
|
|
410
|
+
onToggleSelect: (candidate) => void; // Called when checkbox is toggled
|
|
411
|
+
onOpenDetail: (candidate) => void; // Called when card is clicked
|
|
412
|
+
onImport: (candidate) => void; // Called when Import button is clicked
|
|
413
|
+
onDeploy: (candidate) => void; // Called when Deploy button is clicked
|
|
414
|
+
isImporting?: boolean; // Show loading state on Import
|
|
415
|
+
isDeploying?: boolean; // Show loading state on Deploy
|
|
416
|
+
importDisabled?: boolean; // Disable Import button
|
|
417
|
+
deployDisabled?: boolean; // Disable Deploy button
|
|
418
|
+
className?: string; // Additional className
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Features:**
|
|
423
|
+
|
|
424
|
+
- Fixed-height card (min 160px) with left-border type color from `typeBarColors`
|
|
425
|
+
- Three zones: header (type icon + name + badges) | content (description + marketplace source + score bar) | actions (checkbox + import/deploy buttons)
|
|
426
|
+
- Source-aware rendering: shows collection/marketplace/web badges and source links
|
|
427
|
+
- Trust tier badge with icon (trusted, candidate, unverified, quarantined)
|
|
428
|
+
- Relevance score bar (0.0–1.0 displayed as 0–100%)
|
|
429
|
+
- Selection state with ring styling
|
|
430
|
+
- No app-hook coupling: all data and handlers passed as props
|
|
431
|
+
|
|
432
|
+
**Design Contract:**
|
|
433
|
+
|
|
434
|
+
- Parent grid must use `auto-rows-fr` for uniform row heights
|
|
435
|
+
- Type colors from `@miethe/ui/utils` (`typeBarColors`, `TYPE_BAR_FALLBACK`)
|
|
436
|
+
- Icon colors from component-local `TYPE_ICON_*` maps (semi-transparent variants)
|
|
437
|
+
|
|
438
|
+
**Example:**
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import { DiscoveryCard } from '@miethe/ui/discovery';
|
|
442
|
+
|
|
443
|
+
<div className="grid grid-cols-3 auto-rows-fr gap-4">
|
|
444
|
+
{candidates.map((candidate) => (
|
|
445
|
+
<DiscoveryCard
|
|
446
|
+
key={candidate.name}
|
|
447
|
+
candidate={candidate}
|
|
448
|
+
isSelected={selected.has(candidate.name)}
|
|
449
|
+
onToggleSelect={handleSelect}
|
|
450
|
+
onOpenDetail={handleDetail}
|
|
451
|
+
onImport={handleImport}
|
|
452
|
+
onDeploy={handleDeploy}
|
|
453
|
+
isImporting={importing === candidate.name}
|
|
454
|
+
/>
|
|
455
|
+
))}
|
|
456
|
+
</div>
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**Full Documentation:** See `src/discovery/README.md` for complete API reference, zone architecture, accessibility notes, and marketplace reuse guidance.
|
|
460
|
+
|
|
286
461
|
### DiffViewer
|
|
287
462
|
|
|
288
463
|
A side-by-side unified diff viewer with file browser sidebar and optional sync conflict resolution actions.
|
|
@@ -485,6 +660,252 @@ interface FileTreeProps {
|
|
|
485
660
|
/>
|
|
486
661
|
```
|
|
487
662
|
|
|
663
|
+
### ArticleViewer
|
|
664
|
+
|
|
665
|
+
Read-only markdown and HTML renderer with frontmatter support, callouts, and typography variants.
|
|
666
|
+
|
|
667
|
+
**Props:**
|
|
668
|
+
|
|
669
|
+
```typescript
|
|
670
|
+
interface ArticleViewerProps {
|
|
671
|
+
/**
|
|
672
|
+
* The raw content string to render.
|
|
673
|
+
* For markdown, the full remark pipeline (GFM + directives) is applied.
|
|
674
|
+
* YAML frontmatter (if present) is extracted and not rendered as content.
|
|
675
|
+
*/
|
|
676
|
+
content: string;
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Format of the content. Defaults to `"auto"` which detects markdown
|
|
680
|
+
* by looking for common markdown patterns.
|
|
681
|
+
* @default "auto"
|
|
682
|
+
*/
|
|
683
|
+
format?: 'markdown' | 'html' | 'auto';
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Typography variant. Applies a CSS class (`cv-variant-{name}`) to the root
|
|
687
|
+
* element. Each variant relies on CSS custom properties at document root.
|
|
688
|
+
* See the CSS-Variable Contract below for the full list of expected variables.
|
|
689
|
+
* @default undefined (no variant class applied)
|
|
690
|
+
*/
|
|
691
|
+
variant?: 'editorial' | 'compact' | 'technical';
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Controls visibility of the YAML frontmatter header above article content.
|
|
695
|
+
* - `"show"` — header rendered, expanded by default
|
|
696
|
+
* - `"collapse"` — header rendered, collapsed by default
|
|
697
|
+
* - `"hide"` — header omitted entirely
|
|
698
|
+
* Has no effect if the content has no frontmatter.
|
|
699
|
+
* @default "hide"
|
|
700
|
+
*/
|
|
701
|
+
frontmatter?: 'show' | 'collapse' | 'hide';
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Override map for sub-components rendered by ArticleViewer.
|
|
705
|
+
* - Callout keys (`note`, `reference`, `warning`, `info`): override default callout renderers
|
|
706
|
+
* - `FrontmatterHeader`: override the default frontmatter header component
|
|
707
|
+
*
|
|
708
|
+
* Any key not provided falls back to the default implementation.
|
|
709
|
+
*/
|
|
710
|
+
components?: {
|
|
711
|
+
note?: React.ComponentType<CalloutProps>;
|
|
712
|
+
reference?: React.ComponentType<CalloutProps>;
|
|
713
|
+
warning?: React.ComponentType<CalloutProps>;
|
|
714
|
+
info?: React.ComponentType<CalloutProps>;
|
|
715
|
+
FrontmatterHeader?: React.ComponentType<FrontmatterHeaderProps>;
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Whether to sanitize HTML content (applies to `format="html"` and auto-detected HTML).
|
|
720
|
+
* When `true` (default for HTML input), `rehype-sanitize@6` strips XSS vectors:
|
|
721
|
+
* `<script>`, event handlers (`onclick`, `onerror`, …), `javascript:` and unsafe
|
|
722
|
+
* `data:` URLs, `<iframe>`, `<object>`, `<embed>`, and `<svg>` containing scripts.
|
|
723
|
+
*
|
|
724
|
+
* Set to `false` **only** when the HTML source is fully trusted (e.g., content
|
|
725
|
+
* compiled by the MeatyWiki engine through Portal's controlled pipeline).
|
|
726
|
+
* Never set `false` for user-supplied or third-party content.
|
|
727
|
+
*
|
|
728
|
+
* @default true (for format="html" / auto-detected HTML), false (for format="markdown")
|
|
729
|
+
*/
|
|
730
|
+
sanitize?: boolean;
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Enable opt-in syntax highlighting for fenced code blocks via lowlight.
|
|
734
|
+
* When `false` (default), code blocks render as styled monospace plain text.
|
|
735
|
+
* When `true`, dynamically imports lowlight (~15KB gzip) and applies highlighting.
|
|
736
|
+
*
|
|
737
|
+
* @default false
|
|
738
|
+
*/
|
|
739
|
+
codeHighlight?: boolean;
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Automatically generate `id` attributes on heading elements (h1–h6)
|
|
743
|
+
* using a GitHub-compatible slug algorithm.
|
|
744
|
+
* @default true
|
|
745
|
+
*/
|
|
746
|
+
generateHeadingIds?: boolean;
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* When `true`, suppress all content rendering and show an accessible
|
|
750
|
+
* skeleton/placeholder instead.
|
|
751
|
+
* @default false
|
|
752
|
+
*/
|
|
753
|
+
isLoading?: boolean;
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Render an accessible error message instead of content.
|
|
757
|
+
* Accepts either a `string` message or an `Error` object.
|
|
758
|
+
* @default undefined
|
|
759
|
+
*/
|
|
760
|
+
error?: string | Error | null;
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Callback invoked when a rendering error occurs.
|
|
764
|
+
*/
|
|
765
|
+
onError?: (error: Error) => void;
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Additional CSS class names applied to the root wrapper element.
|
|
769
|
+
*/
|
|
770
|
+
className?: string;
|
|
771
|
+
}
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
**Features:**
|
|
775
|
+
|
|
776
|
+
- Full CommonMark + GitHub-Flavored Markdown (tables, task lists, strikethrough, blockquotes, code blocks, links, lists)
|
|
777
|
+
- Callout directives: `::: note`, `::: reference`, `::: warning`, `::: info` with customizable components
|
|
778
|
+
- YAML frontmatter parsing and optional display via collapsible header
|
|
779
|
+
- HTML input support with XSS sanitization (default ON; opt-out for trusted sources)
|
|
780
|
+
- Typography variants (CSS-variable-driven): editorial, compact, technical
|
|
781
|
+
- Syntax highlighting (opt-in, lowlight-based)
|
|
782
|
+
- Heading anchor links (auto-generated IDs)
|
|
783
|
+
- Loading and error states
|
|
784
|
+
- Fully typed TypeScript exports
|
|
785
|
+
- Zero React Markdown coupling (pure remark pipeline)
|
|
786
|
+
|
|
787
|
+
**CSS-Variable Contract:**
|
|
788
|
+
|
|
789
|
+
When using `variant="editorial"`, define these variables at `:root` or a suitable ancestor:
|
|
790
|
+
|
|
791
|
+
```css
|
|
792
|
+
:root {
|
|
793
|
+
/* Typography */
|
|
794
|
+
--cv-editorial-h1-font: Fraunces, Georgia, serif;
|
|
795
|
+
--cv-editorial-h1-size: 2.25rem;
|
|
796
|
+
--cv-editorial-h2-font: Fraunces, Georgia, serif;
|
|
797
|
+
--cv-editorial-h2-size: 1.875rem;
|
|
798
|
+
--cv-editorial-body-font: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
799
|
+
--cv-editorial-body-size: 1rem;
|
|
800
|
+
--cv-editorial-body-line-height: 1.75;
|
|
801
|
+
|
|
802
|
+
/* Quotes & blockquotes */
|
|
803
|
+
--cv-editorial-quote-color: #64748b;
|
|
804
|
+
--cv-editorial-quote-font-style: italic;
|
|
805
|
+
|
|
806
|
+
/* Callout colors */
|
|
807
|
+
--cv-callout-note-accent: #0ea5e9;
|
|
808
|
+
--cv-callout-note-bg: #f0f9ff;
|
|
809
|
+
--cv-callout-reference-accent: #64748b;
|
|
810
|
+
--cv-callout-reference-bg: #f1f5f9;
|
|
811
|
+
--cv-callout-warning-accent: #f59e0b;
|
|
812
|
+
--cv-callout-warning-bg: #fffbeb;
|
|
813
|
+
--cv-callout-info-accent: #0ea5e9;
|
|
814
|
+
--cv-callout-info-bg: #f0f9ff;
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
Analogous `--cv-compact-*` and `--cv-technical-*` sets follow the same pattern. Missing variables are silently ignored — browser defaults apply.
|
|
819
|
+
|
|
820
|
+
**Examples:**
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
import { ArticleViewer } from '@miethe/ui/content-viewer';
|
|
824
|
+
|
|
825
|
+
// Basic markdown rendering
|
|
826
|
+
<ArticleViewer
|
|
827
|
+
content="# Hello\n\nThis is **bold** and *italic*."
|
|
828
|
+
format="markdown"
|
|
829
|
+
/>
|
|
830
|
+
|
|
831
|
+
// With frontmatter display
|
|
832
|
+
<ArticleViewer
|
|
833
|
+
content={`---
|
|
834
|
+
title: My Article
|
|
835
|
+
date: 2026-04-24
|
|
836
|
+
tags: [markdown, ui]
|
|
837
|
+
---
|
|
838
|
+
|
|
839
|
+
# Article title
|
|
840
|
+
|
|
841
|
+
Content here...`}
|
|
842
|
+
frontmatter="show"
|
|
843
|
+
variant="editorial"
|
|
844
|
+
/>
|
|
845
|
+
|
|
846
|
+
// With callouts
|
|
847
|
+
<ArticleViewer
|
|
848
|
+
content={`# Document
|
|
849
|
+
|
|
850
|
+
::: note
|
|
851
|
+
This is a note callout.
|
|
852
|
+
:::
|
|
853
|
+
|
|
854
|
+
::: warning
|
|
855
|
+
This is a warning.
|
|
856
|
+
:::`}
|
|
857
|
+
format="markdown"
|
|
858
|
+
/>
|
|
859
|
+
|
|
860
|
+
// With callout override
|
|
861
|
+
<ArticleViewer
|
|
862
|
+
content={markdown}
|
|
863
|
+
components={{
|
|
864
|
+
note: CustomNoteCallout,
|
|
865
|
+
warning: CustomWarningCallout,
|
|
866
|
+
}}
|
|
867
|
+
/>
|
|
868
|
+
|
|
869
|
+
// HTML input with sanitization (default ON)
|
|
870
|
+
<ArticleViewer
|
|
871
|
+
content="<p>This <script>alert('xss')</script> is <strong>safe</strong>.</p>"
|
|
872
|
+
format="html"
|
|
873
|
+
sanitize={true}
|
|
874
|
+
/>
|
|
875
|
+
|
|
876
|
+
// Trusted engine-compiled HTML (sanitization OFF)
|
|
877
|
+
<ArticleViewer
|
|
878
|
+
content={engineOutput}
|
|
879
|
+
format="html"
|
|
880
|
+
sanitize={false}
|
|
881
|
+
/>
|
|
882
|
+
|
|
883
|
+
// With syntax highlighting
|
|
884
|
+
<ArticleViewer
|
|
885
|
+
content={`\`\`\`typescript
|
|
886
|
+
function hello(name: string) {
|
|
887
|
+
console.log('Hello', name);
|
|
888
|
+
}
|
|
889
|
+
\`\`\``}
|
|
890
|
+
codeHighlight={true}
|
|
891
|
+
/>
|
|
892
|
+
|
|
893
|
+
// Typography variant with custom CSS variables
|
|
894
|
+
<ArticleViewer
|
|
895
|
+
content={markdown}
|
|
896
|
+
variant="editorial"
|
|
897
|
+
/>
|
|
898
|
+
{/* In your globals.css: */}
|
|
899
|
+
{/* :root { --cv-editorial-body-font: 'Libre Baskerville', serif; ... } */}
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
**Sanitization Notes:**
|
|
903
|
+
|
|
904
|
+
- By default, HTML input is sanitized via `rehype-sanitize@6`, which strips script tags, event handlers, unsafe URLs, and dangerous elements
|
|
905
|
+
- Markdown input (via `react-markdown`) is inherently safe — the sanitization prop has no effect on markdown
|
|
906
|
+
- For pre-compiled engine output (trusted source), set `sanitize={false}` to skip sanitization overhead
|
|
907
|
+
- For user-supplied or third-party HTML, always use `sanitize={true}` (the default)
|
|
908
|
+
|
|
488
909
|
### ContentPane
|
|
489
910
|
|
|
490
911
|
Display and edit file content with syntax highlighting, markdown preview, and optional editing.
|
|
@@ -499,6 +920,8 @@ interface ContentPaneProps {
|
|
|
499
920
|
error?: string | null; // Error message to display
|
|
500
921
|
readOnly?: boolean; // Hide edit/save buttons (default: false)
|
|
501
922
|
truncationInfo?: TruncationInfo; // Info about truncated files
|
|
923
|
+
codeHighlight?: boolean; // Enable syntax highlighting (opt-in, requires lowlight)
|
|
924
|
+
renderBinaryPreview?: (ext: string, content: string | Uint8Array) => ReactNode | null; // Custom binary preview
|
|
502
925
|
// Lifted edit state
|
|
503
926
|
isEditing?: boolean; // True when in edit mode
|
|
504
927
|
editedContent?: string; // Content being edited
|
|
@@ -513,12 +936,66 @@ interface ContentPaneProps {
|
|
|
513
936
|
**Features:**
|
|
514
937
|
|
|
515
938
|
- Breadcrumb navigation for file paths
|
|
516
|
-
- Syntax highlighting for code files
|
|
939
|
+
- Syntax highlighting for code files (opt-in via `codeHighlight` prop)
|
|
517
940
|
- Markdown split-preview (editor + preview) for `.md` files
|
|
518
941
|
- Optional frontmatter display
|
|
519
|
-
- Edit mode for supported file types
|
|
942
|
+
- Edit mode for supported file types (.md, .ts, .tsx, .js, .jsx, .py, .json, .css)
|
|
520
943
|
- Truncation warning for large files
|
|
521
944
|
- Lazy-loaded CodeMirror editor (bundle cost only on demand)
|
|
945
|
+
- Custom binary/image preview via `renderBinaryPreview` render-prop
|
|
946
|
+
|
|
947
|
+
**Code Highlighting (Optional)**:
|
|
948
|
+
|
|
949
|
+
Enable syntax highlighting for non-markdown code blocks:
|
|
950
|
+
|
|
951
|
+
```typescript
|
|
952
|
+
import { ContentPane } from '@miethe/ui/content-viewer';
|
|
953
|
+
|
|
954
|
+
<ContentPane
|
|
955
|
+
path="src/utils.ts"
|
|
956
|
+
content={tsCode}
|
|
957
|
+
codeHighlight={true}
|
|
958
|
+
/>
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
Requires `lowlight` (v2 or v3) installed:
|
|
962
|
+
|
|
963
|
+
```bash
|
|
964
|
+
npm install lowlight
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
Import a highlight.js theme CSS:
|
|
968
|
+
|
|
969
|
+
```typescript
|
|
970
|
+
// In your app layout or CSS file
|
|
971
|
+
import 'highlight.js/styles/github.css'; // Light mode
|
|
972
|
+
// or
|
|
973
|
+
import 'highlight.js/styles/github-dark.css'; // Dark mode
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
Without lowlight installed, code renders as plain text (no error).
|
|
977
|
+
|
|
978
|
+
**Custom Binary Previews**:
|
|
979
|
+
|
|
980
|
+
Provide a render-prop for custom binary/image/PDF previews:
|
|
981
|
+
|
|
982
|
+
```typescript
|
|
983
|
+
<ContentPane
|
|
984
|
+
path={path}
|
|
985
|
+
content={content}
|
|
986
|
+
renderBinaryPreview={(ext, content) => {
|
|
987
|
+
if (ext === 'pdf') {
|
|
988
|
+
return <PdfViewer content={content} />; // Your custom viewer
|
|
989
|
+
}
|
|
990
|
+
if (ext === 'png' || ext === 'jpg') {
|
|
991
|
+
return <img src={`data:image/${ext};base64,${content}`} />;
|
|
992
|
+
}
|
|
993
|
+
return null; // Fall through to text view
|
|
994
|
+
}}
|
|
995
|
+
/>
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
Returning `null` from the render-prop falls through to the default text display.
|
|
522
999
|
|
|
523
1000
|
**Example:**
|
|
524
1001
|
|
|
@@ -599,7 +1076,7 @@ interface SplitPreviewProps {
|
|
|
599
1076
|
|
|
600
1077
|
### MarkdownEditor
|
|
601
1078
|
|
|
602
|
-
CodeMirror-based markdown editor for editing `.md` files. Also lazy-loaded.
|
|
1079
|
+
CodeMirror-based markdown editor for editing `.md` files. Also lazy-loaded with reactive dark-mode support.
|
|
603
1080
|
|
|
604
1081
|
**Props:**
|
|
605
1082
|
|
|
@@ -611,6 +1088,78 @@ interface MarkdownEditorProps {
|
|
|
611
1088
|
}
|
|
612
1089
|
```
|
|
613
1090
|
|
|
1091
|
+
**Features:**
|
|
1092
|
+
|
|
1093
|
+
- Live theme switching (tracks OS `prefers-color-scheme` changes at runtime)
|
|
1094
|
+
- Full markdown syntax support
|
|
1095
|
+
- Lazy-loaded for optimal bundle size
|
|
1096
|
+
|
|
1097
|
+
### CodeEditor
|
|
1098
|
+
|
|
1099
|
+
CodeMirror 6 editor for non-markdown code files (.ts, .tsx, .js, .jsx, .py, .json, .css). Exported from `@miethe/ui/editor`.
|
|
1100
|
+
|
|
1101
|
+
**Import:**
|
|
1102
|
+
|
|
1103
|
+
```typescript
|
|
1104
|
+
import { CodeEditor } from '@miethe/ui/editor';
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
**Props:**
|
|
1108
|
+
|
|
1109
|
+
```typescript
|
|
1110
|
+
interface CodeEditorProps {
|
|
1111
|
+
initialContent: string; // Initial code content
|
|
1112
|
+
onChange: (content: string) => void; // Called on every keystroke
|
|
1113
|
+
readOnly?: boolean; // Disable editing (default: false)
|
|
1114
|
+
language?: LanguageSupport; // CodeMirror language extension (optional)
|
|
1115
|
+
className?: string; // Additional CSS classes
|
|
1116
|
+
}
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
**Features:**
|
|
1120
|
+
|
|
1121
|
+
- Language detection by file extension
|
|
1122
|
+
- Syntax highlighting (via language extensions)
|
|
1123
|
+
- Reactive dark-mode support
|
|
1124
|
+
- Full keyboard navigation
|
|
1125
|
+
- Lazy-loaded via dynamic import
|
|
1126
|
+
|
|
1127
|
+
**Example:**
|
|
1128
|
+
|
|
1129
|
+
```typescript
|
|
1130
|
+
import { CodeEditor } from '@miethe/ui/editor';
|
|
1131
|
+
import { javascript } from '@codemirror/lang-javascript';
|
|
1132
|
+
import { python } from '@codemirror/lang-python';
|
|
1133
|
+
|
|
1134
|
+
// With auto-detected language
|
|
1135
|
+
<CodeEditor
|
|
1136
|
+
initialContent="const x = 42;"
|
|
1137
|
+
onChange={(content) => console.log(content)}
|
|
1138
|
+
/>
|
|
1139
|
+
|
|
1140
|
+
// With explicit language
|
|
1141
|
+
<CodeEditor
|
|
1142
|
+
initialContent="def hello(): pass"
|
|
1143
|
+
onChange={setCode}
|
|
1144
|
+
language={python()}
|
|
1145
|
+
/>
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
**ContentPane Integration:**
|
|
1149
|
+
|
|
1150
|
+
ContentPane automatically routes non-markdown editable files to CodeEditor:
|
|
1151
|
+
|
|
1152
|
+
```typescript
|
|
1153
|
+
// ContentPane detects .ts/.tsx/.js/.jsx/.py/.json/.css files
|
|
1154
|
+
// and uses CodeEditor instead of MarkdownEditor when editing
|
|
1155
|
+
<ContentPane
|
|
1156
|
+
path="src/utils.ts"
|
|
1157
|
+
content={tsCode}
|
|
1158
|
+
isEditing={true}
|
|
1159
|
+
onEditChange={setCode}
|
|
1160
|
+
/>
|
|
1161
|
+
```
|
|
1162
|
+
|
|
614
1163
|
## Primitives API (`@miethe/ui/primitives`)
|
|
615
1164
|
|
|
616
1165
|
Reusable UI primitives extracted from SkillMeat components. All primitives are production-ready, fully accessible, and compose with shadcn/ui components.
|
|
@@ -701,6 +1250,125 @@ interface ModalHeaderProps {
|
|
|
701
1250
|
- Consistent styling with SkillMeat modals
|
|
702
1251
|
- Accessible heading semantic
|
|
703
1252
|
|
|
1253
|
+
### CreateEntityDialog
|
|
1254
|
+
|
|
1255
|
+
Schema-driven creation dialog for entities. Dynamically renders form fields from an `EntityFormSchema` definition using react-hook-form for state management and per-field validation.
|
|
1256
|
+
|
|
1257
|
+
**Import:**
|
|
1258
|
+
```typescript
|
|
1259
|
+
import { CreateEntityDialog } from '@miethe/ui/primitives';
|
|
1260
|
+
import type { EntityFormSchema, FieldDef } from '@miethe/ui/primitives';
|
|
1261
|
+
```
|
|
1262
|
+
|
|
1263
|
+
**Modes:**
|
|
1264
|
+
|
|
1265
|
+
1. **Simple** — Flat list of fields
|
|
1266
|
+
2. **Tabs** — Fields grouped into tabs
|
|
1267
|
+
3. **Composite** — Tabs + member picker + review step
|
|
1268
|
+
|
|
1269
|
+
**Simple Mode Example:**
|
|
1270
|
+
|
|
1271
|
+
```typescript
|
|
1272
|
+
<CreateEntityDialog
|
|
1273
|
+
open={isOpen}
|
|
1274
|
+
onOpenChange={setIsOpen}
|
|
1275
|
+
title="Create Skill"
|
|
1276
|
+
description="Add a new skill to your collection"
|
|
1277
|
+
schema={{
|
|
1278
|
+
mode: 'simple',
|
|
1279
|
+
fields: [
|
|
1280
|
+
{ name: 'name', label: 'Name', type: 'text', required: true },
|
|
1281
|
+
{ name: 'description', label: 'Description', type: 'textarea' },
|
|
1282
|
+
{ name: 'tags', label: 'Tags', type: 'tags', options: [
|
|
1283
|
+
{ label: 'Important', value: 'important' },
|
|
1284
|
+
]},
|
|
1285
|
+
],
|
|
1286
|
+
collection: { enabled: true, required: true, collapsible: true },
|
|
1287
|
+
}}
|
|
1288
|
+
collections={[
|
|
1289
|
+
{ id: 'col-1', name: 'My Collection' },
|
|
1290
|
+
]}
|
|
1291
|
+
onSubmit={async (values) => {
|
|
1292
|
+
await api.createSkill(values);
|
|
1293
|
+
}}
|
|
1294
|
+
submitLabel="Create"
|
|
1295
|
+
/>
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
**Tabs Mode Example:**
|
|
1299
|
+
|
|
1300
|
+
```typescript
|
|
1301
|
+
<CreateEntityDialog
|
|
1302
|
+
open={isOpen}
|
|
1303
|
+
onOpenChange={setIsOpen}
|
|
1304
|
+
title="Create Project"
|
|
1305
|
+
schema={{
|
|
1306
|
+
mode: 'tabs',
|
|
1307
|
+
tabs: [
|
|
1308
|
+
{
|
|
1309
|
+
id: 'basic',
|
|
1310
|
+
label: 'Basic Info',
|
|
1311
|
+
fields: [
|
|
1312
|
+
{ name: 'name', label: 'Name', type: 'text', required: true },
|
|
1313
|
+
],
|
|
1314
|
+
},
|
|
1315
|
+
{
|
|
1316
|
+
id: 'details',
|
|
1317
|
+
label: 'Details',
|
|
1318
|
+
fields: [
|
|
1319
|
+
{ name: 'description', label: 'Description', type: 'textarea' },
|
|
1320
|
+
],
|
|
1321
|
+
},
|
|
1322
|
+
],
|
|
1323
|
+
collection: { enabled: true },
|
|
1324
|
+
}}
|
|
1325
|
+
collections={collections}
|
|
1326
|
+
onSubmit={async (values) => {
|
|
1327
|
+
await api.createProject(values);
|
|
1328
|
+
}}
|
|
1329
|
+
/>
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
**Composite Mode:**
|
|
1333
|
+
|
|
1334
|
+
Composite mode adds a `members` picker and optional `renderReviewStep`. Selected member IDs are passed to `onMemberIdsChange`.
|
|
1335
|
+
|
|
1336
|
+
```typescript
|
|
1337
|
+
schema={{
|
|
1338
|
+
mode: 'composite',
|
|
1339
|
+
tabs: [...],
|
|
1340
|
+
members: {
|
|
1341
|
+
entityType: 'skill',
|
|
1342
|
+
label: 'Skills',
|
|
1343
|
+
validateMin: 1,
|
|
1344
|
+
validateMax: 10,
|
|
1345
|
+
},
|
|
1346
|
+
renderReviewStep: ({ values, memberIds }) => (
|
|
1347
|
+
<div>Review: {values.name} with {memberIds.length} skills</div>
|
|
1348
|
+
),
|
|
1349
|
+
}}
|
|
1350
|
+
memberIds={selectedIds}
|
|
1351
|
+
onMemberIdsChange={setSelectedIds}
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
**CollectionPicker Sub-component:**
|
|
1355
|
+
|
|
1356
|
+
The `CollectionPicker` component is embedded in simple/tabs/composite modes when `collection.enabled: true`.
|
|
1357
|
+
|
|
1358
|
+
**Props:**
|
|
1359
|
+
```typescript
|
|
1360
|
+
interface CollectionPickerConfig {
|
|
1361
|
+
enabled: boolean; // Show picker
|
|
1362
|
+
required?: boolean; // Mandatory before submit
|
|
1363
|
+
collapsible?: boolean; // User can collapse/expand
|
|
1364
|
+
defaultCollectionId?: string; // Pre-select a collection
|
|
1365
|
+
}
|
|
1366
|
+
```
|
|
1367
|
+
|
|
1368
|
+
**Entity Type Registry:**
|
|
1369
|
+
|
|
1370
|
+
Some entity types are disabled in the internal type registry (see `src/primitives/CreateEntityDialog.tsx`). Check runtime behavior or the source before creating forms for edge-case entity types.
|
|
1371
|
+
|
|
704
1372
|
### TabNavigation
|
|
705
1373
|
|
|
706
1374
|
Horizontal tab list component for BaseArtifactModal and custom tab interfaces. Supports icons and keyboard navigation.
|
|
@@ -978,6 +1646,86 @@ import { SortDropdown } from '@miethe/ui/filters';
|
|
|
978
1646
|
|
|
979
1647
|
Clicking an already-selected field toggles the order.
|
|
980
1648
|
|
|
1649
|
+
### FilterBar & Filter Slot System
|
|
1650
|
+
|
|
1651
|
+
Reusable filter bar with search, sort, view toggle, and pluggable slot registry. Renders a horizontal toolbar with four zones (left → right): search input, conditional filter slots, sort dropdown, and view toggle.
|
|
1652
|
+
|
|
1653
|
+
**Basic Usage:**
|
|
1654
|
+
|
|
1655
|
+
```tsx
|
|
1656
|
+
import { FilterBar } from '@miethe/ui/filters';
|
|
1657
|
+
|
|
1658
|
+
<FilterBar
|
|
1659
|
+
searchValue={searchValue}
|
|
1660
|
+
onSearchChange={setSearchValue}
|
|
1661
|
+
sortField="name"
|
|
1662
|
+
sortOrder="asc"
|
|
1663
|
+
onSortChange={(field, order) => { /* ... */ }}
|
|
1664
|
+
view="grid"
|
|
1665
|
+
onViewChange={(mode) => { /* ... */ }}
|
|
1666
|
+
filterSlots={slots}
|
|
1667
|
+
conditionContext={{ pageId: 'artifacts' }}
|
|
1668
|
+
/>
|
|
1669
|
+
```
|
|
1670
|
+
|
|
1671
|
+
**Slot Registration Example:**
|
|
1672
|
+
|
|
1673
|
+
```tsx
|
|
1674
|
+
import type { FilterSlotConfig } from '@miethe/ui/filters';
|
|
1675
|
+
|
|
1676
|
+
const filterSlots: FilterSlotConfig[] = [
|
|
1677
|
+
{
|
|
1678
|
+
id: 'trust-filter',
|
|
1679
|
+
label: 'Trust Level',
|
|
1680
|
+
component: <TrustLevelSelect selected={trust} onChange={setTrust} />,
|
|
1681
|
+
condition: (ctx) => ctx.pageId === 'marketplace-sources',
|
|
1682
|
+
},
|
|
1683
|
+
{
|
|
1684
|
+
id: 'type-filter',
|
|
1685
|
+
label: 'Type',
|
|
1686
|
+
component: <TypeSelect selected={types} onChange={setTypes} />,
|
|
1687
|
+
},
|
|
1688
|
+
];
|
|
1689
|
+
```
|
|
1690
|
+
|
|
1691
|
+
**Interface Definitions:**
|
|
1692
|
+
|
|
1693
|
+
```typescript
|
|
1694
|
+
export interface FilterSlotConditionContext {
|
|
1695
|
+
pageId: string; // Stable identifier (e.g. "artifacts", "marketplace")
|
|
1696
|
+
edition?: string; // Backend edition: "local" or "enterprise"
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
export interface FilterSlotConfig {
|
|
1700
|
+
id: string; // Unique slot identifier
|
|
1701
|
+
label: string; // Human-readable label for accessibility
|
|
1702
|
+
component: React.ReactNode; // React node to render when slot is active
|
|
1703
|
+
condition?: (ctx: FilterSlotConditionContext) => boolean; // Optional visibility predicate
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
export interface FilterBarProps {
|
|
1707
|
+
searchValue: string;
|
|
1708
|
+
onSearchChange: (value: string) => void;
|
|
1709
|
+
searchPlaceholder?: string;
|
|
1710
|
+
searchAriaLabel?: string;
|
|
1711
|
+
filterSlots?: FilterSlotConfig[];
|
|
1712
|
+
conditionContext?: FilterSlotConditionContext;
|
|
1713
|
+
sort?: FilterBarSortProps;
|
|
1714
|
+
viewToggle?: {
|
|
1715
|
+
value: 'grid' | 'list';
|
|
1716
|
+
onChange: (mode: 'grid' | 'list') => void;
|
|
1717
|
+
enabled?: boolean;
|
|
1718
|
+
};
|
|
1719
|
+
className?: string;
|
|
1720
|
+
}
|
|
1721
|
+
```
|
|
1722
|
+
|
|
1723
|
+
**Accessibility:**
|
|
1724
|
+
|
|
1725
|
+
The FilterBar renders a `role="search"` landmark region with a configurable `aria-label`. Slot authors should include `aria-label` on interactive elements (selects, popovers) to support screen readers. Condition predicates are evaluated synchronously at render time without side effects.
|
|
1726
|
+
|
|
1727
|
+
**Source:** `skillmeat/web/packages/ui/src/filters/`
|
|
1728
|
+
|
|
981
1729
|
## Bulk Actions API (`@miethe/ui/bulk-actions`)
|
|
982
1730
|
|
|
983
1731
|
Floating toolbar component for multi-select interfaces. Displays a bottom-fixed action bar with selection count and action buttons when items are selected.
|
|
@@ -1531,20 +2279,39 @@ The adapter encodes a composite key (sourceId + artifactPath) into a single stri
|
|
|
1531
2279
|
|
|
1532
2280
|
### Lazy-Loaded Editor Bundle
|
|
1533
2281
|
|
|
1534
|
-
The CodeMirror editor (used in `SplitPreview` and `
|
|
2282
|
+
The CodeMirror editor (used in `SplitPreview`, `MarkdownEditor`, and `CodeEditor`) is lazy-loaded and only fetched when needed:
|
|
1535
2283
|
|
|
1536
|
-
- **Markdown files in edit mode**:
|
|
1537
|
-
- **Non-markdown files
|
|
2284
|
+
- **Markdown files in edit mode**: MarkdownEditor chunk downloaded
|
|
2285
|
+
- **Non-markdown files in edit mode** (.ts, .tsx, .js, .jsx, .py, .json, .css): CodeEditor chunk downloaded
|
|
1538
2286
|
- **Read-only mode**: Editor chunk may still load for markdown files (preview uses a lighter markdown renderer)
|
|
1539
2287
|
|
|
1540
2288
|
This significantly reduces the initial bundle size for consumers. If you're only using `FileTree` and `ContentPane` for viewing, you may never download the editor.
|
|
1541
2289
|
|
|
2290
|
+
### Optional Syntax Highlighting
|
|
2291
|
+
|
|
2292
|
+
Syntax highlighting via `lowlight` is completely optional:
|
|
2293
|
+
|
|
2294
|
+
- **ArticleViewer** `codeHighlight` prop: Defaults to `false`; dynamically imports lowlight (~15KB gzip) when enabled
|
|
2295
|
+
- **ContentPane** `codeHighlight` prop: Defaults to `false`; gracefully degrades to plain text if lowlight is not installed
|
|
2296
|
+
|
|
2297
|
+
Both use **lowlight** (the same optional peer dependency) for a single, consistent highlighting engine across the package.
|
|
2298
|
+
|
|
2299
|
+
Install lowlight only if you want to enable code highlighting:
|
|
2300
|
+
|
|
2301
|
+
```bash
|
|
2302
|
+
npm install lowlight
|
|
2303
|
+
```
|
|
2304
|
+
|
|
2305
|
+
Without lowlight, code renders as plain text with no error or warning.
|
|
2306
|
+
|
|
1542
2307
|
### Component Structure
|
|
1543
2308
|
|
|
1544
2309
|
- **`FileTree`** - ~8 KB gzipped (fully bundled, no lazy loading)
|
|
1545
2310
|
- **`ContentPane`** - ~5 KB gzipped (fully bundled)
|
|
1546
2311
|
- **`FrontmatterDisplay`** - ~2 KB gzipped (fully bundled)
|
|
1547
2312
|
- **`SplitPreview` + `MarkdownEditor`** (lazy) - ~50 KB gzipped (on demand)
|
|
2313
|
+
- **`CodeEditor`** (lazy) - ~40 KB gzipped (on demand, only for editing non-markdown files)
|
|
2314
|
+
- **`lowlight`** (optional) - ~15 KB gzipped (only when syntax highlighting is enabled)
|
|
1548
2315
|
|
|
1549
2316
|
## Accessibility
|
|
1550
2317
|
|