diff --git a/.storybook/github-source.ts b/.storybook/github-source.ts new file mode 100644 index 000000000..3d38dc882 --- /dev/null +++ b/.storybook/github-source.ts @@ -0,0 +1,28 @@ +// Submodule story roots live in their own repos; GitHub can't browse into a gitlink. +const SUBMODULE_REPOS: Record = { + 'packages/datavis/': 'mieweb/datavis', +}; + +/** GitHub blob URL for a path relative to the ui repo root. */ +export function githubSourceUrl(repoPath: string): string { + const prefix = Object.keys(SUBMODULE_REPOS).find((p) => + repoPath.startsWith(p) + ); + const [repo, path] = prefix + ? [SUBMODULE_REPOS[prefix], repoPath.slice(prefix.length)] + : ['mieweb/ui', repoPath]; + return `https://github.com/${repo}/blob/main/${path + .split('/') + .map(encodeURIComponent) + .join('/')}`; +} + +/** Repo-relative path of an absolute story file path, if it is in a known root. */ +export function repoRelativeStoryPath(fileName: string): string | null { + const roots = [...Object.keys(SUBMODULE_REPOS), 'src/']; + for (const root of roots) { + const index = fileName.indexOf(`/${root}`); + if (index >= 0) return fileName.slice(index + 1); + } + return null; +} diff --git a/.storybook/main.ts b/.storybook/main.ts index aceb02bc0..7cada28d1 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -204,7 +204,11 @@ function ychartGitInfoPlugin(): Plugin { const ychartDependencyNames = readDependencyNames('ychart'); const config: StorybookConfig = { - stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + stories: [ + '../src/**/*.mdx', + '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)', + '../packages/datavis/stories/**/*.stories.@(js|jsx|mjs|ts|tsx)', + ], // Deprecated/retired entries are hidden from both the sidebar tree and // search (one filtered dataset drives both — there is no search-only mode). // Their docs pages stay built and reachable by direct URL; the Deprecated diff --git a/.storybook/manager.ts b/.storybook/manager.ts index 651cf20b5..56a41b9d3 100644 --- a/.storybook/manager.ts +++ b/.storybook/manager.ts @@ -9,6 +9,7 @@ import { create } from 'storybook/theming/create'; import { Button } from 'storybook/internal/components'; import { GithubIcon, MobileIcon, ShareAltIcon } from '@storybook/icons'; import { buildArgsParam } from 'storybook/internal/router'; +import { githubSourceUrl } from './github-source'; import { serializeProjectGlobals } from './mobile-preview'; // Brand theme configurations for the Storybook manager UI @@ -667,10 +668,7 @@ function GitHubSourceTool() { if (!sourcePath) return null; - const githubUrl = `https://github.com/mieweb/ui/blob/main/${sourcePath - .split('/') - .map(encodeURIComponent) - .join('/')}`; + const githubUrl = githubSourceUrl(sourcePath); return React.createElement( Button, diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 1e61086f7..4f361a7bd 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -25,6 +25,7 @@ import { } from '../src/components/CodeLookup/context'; import { isRtlLocale } from '../src/hooks/useDirection'; import { CatalogDocsPage } from './CatalogDocsPage'; +import { githubSourceUrl, repoRelativeStoryPath } from './github-source'; import { withMobilePreview } from './MobilePreview'; import { mobilePreviewMode } from './mobile-preview'; @@ -205,18 +206,19 @@ const withGitHubSource: Decorator = (Story, context) => { const rawFileName = context.parameters?.fileName as string | undefined; // Normalize Windows backslashes to forward slashes before any path operations const fileName = rawFileName ? rawFileName.replace(/\\/g, '/') : undefined; - const srcIndex = fileName ? fileName.indexOf('/src/') : -1; + const relPath = fileName ? repoRelativeStoryPath(fileName) : null; const githubUrl = (() => { - if (srcIndex < 0 || !fileName) return null; - const relPath = fileName.slice(srcIndex + 1); + if (!relPath) return null; + // Submodule stories have no sibling component file; link the story itself. + if (!relPath.startsWith('src/')) return githubSourceUrl(relPath); const basename = relPath.split('/').pop() ?? ''; // Only strip `.stories` when the basename is strictly `Name.stories.(ts|tsx|js|jsx)` // (no extra dot-segments before `.stories`). Otherwise link to the stories file itself. const stripped = /^[^.]+\.stories\.(tsx?|jsx?)$/.test(basename) ? relPath.replace(/\.stories(\.[^.]+)$/, '$1') : relPath; - return `https://github.com/mieweb/ui/blob/main/${stripped}`; + return githubSourceUrl(stripped); })(); // Full-height stories (parameters.githubSourceFooter === false) fill the diff --git a/ComponentDocsAudit.md b/ComponentDocsAudit.md index 0166b0c54..60a820434 100644 --- a/ComponentDocsAudit.md +++ b/ComponentDocsAudit.md @@ -84,15 +84,17 @@ Reported, not resolved: ## 2. Grade table Scores are `P S A R C L`. Evidence points at `description.component` or records its absence. +DataVis NITRO stories now live in [mieweb/datavis](https://github.com/mieweb/datavis/tree/main/stories); +their line-level evidence is pinned to the audited snapshot (`a8ca1ad`). ### Calibration set -| Component | P S A R C L | Total | Grade | Evidence | -| -------------------------------------------------------------------------------------- | ----------- | ----- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| [ReadingProgressBar](src/components/ReadingProgressBar/ReadingProgressBar.stories.tsx) | 2 1 0 0 2 1 | 6 | D | [stories:12-14](src/components/ReadingProgressBar/ReadingProgressBar.stories.tsx#L12-L14); no link to Progress/TOC/SectionSpyNav | -| [TableOfContents](src/components/TableOfContents/TableOfContents.stories.tsx) | 1 0 0 0 2 1 | 4 | F | no `description.component` ([stories:9-24](src/components/TableOfContents/TableOfContents.stories.tsx#L9-L24)); a11y claims only in demo prose | -| [Table](src/components/Table/Table.stories.tsx) | 2 2 2 2 2 1 | 11 | C | [stories:39](src/components/Table/Table.stories.tsx#L39) one-way verified link to NITRO | -| [DataVis NITRO (Grid)](src/components/DataVisNITRO/DataVisNITRO.stories.tsx) | 2 1 0 0 2 1 | 7 | D | [stories:63-66](src/components/DataVisNITRO/DataVisNITRO.stories.tsx#L63-L66) describes the wrapper only | +| Component | P S A R C L | Total | Grade | Evidence | +| ---------------------------------------------------------------------------------------------------- | ----------- | ----- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| [ReadingProgressBar](src/components/ReadingProgressBar/ReadingProgressBar.stories.tsx) | 2 1 0 0 2 1 | 6 | D | [stories:12-14](src/components/ReadingProgressBar/ReadingProgressBar.stories.tsx#L12-L14); no link to Progress/TOC/SectionSpyNav | +| [TableOfContents](src/components/TableOfContents/TableOfContents.stories.tsx) | 1 0 0 0 2 1 | 4 | F | no `description.component` ([stories:9-24](src/components/TableOfContents/TableOfContents.stories.tsx#L9-L24)); a11y claims only in demo prose | +| [Table](src/components/Table/Table.stories.tsx) | 2 2 2 2 2 1 | 11 | C | [stories:39](src/components/Table/Table.stories.tsx#L39) one-way verified link to NITRO | +| [DataVis NITRO (Grid)](https://github.com/mieweb/datavis/blob/main/stories/DataVisNITRO.stories.tsx) | 2 1 0 0 2 1 | 7 | D | [stories:63-66](https://github.com/mieweb/ui/blob/a8ca1ad/src/components/DataVisNITRO/DataVisNITRO.stories.tsx#L63-L66) describes the wrapper only | ### A (16–18) @@ -134,7 +136,7 @@ Scores are `P S A R C L`. Evidence points at `description.component` or records | [SourceTip](src/components/SourceTip/SourceTip.stories.tsx) | 10 | R corrected to 0 — does not mention GlossaryTooltip | | [VisuallyHidden](src/components/VisuallyHidden/VisuallyHidden.stories.tsx) | 10 | strong a11y use cases | | [ClampedText](src/components/ClampedText/ClampedText.stories.tsx) | 9 | no Text link | -| [DataVisNitroGraph](src/components/DataVisNITRO/DataVisNitroGraph.stories.tsx) | 9 | peer deps/subpath not stated; Sparkline not linked | +| [DataVisNitroGraph](https://github.com/mieweb/datavis/blob/main/stories/DataVisNitroGraph.stories.tsx) | 9 | peer deps/subpath not stated; Sparkline not linked | | [ErrorPage](src/components/ErrorPage/ErrorPage.stories.tsx) | 9 | no Alert/LoadingPage comparison | | [FreshnessBadge](src/components/FreshnessBadge/FreshnessBadge.stories.tsx) | 9 | no Badge/CountBadge comparison | | [HandsFreeChat](src/components/AI/HeyOzwell/HandsFreeChat.stories.tsx) | 9 | parent/sibling relationships unstated | @@ -147,7 +149,7 @@ Scores are `P S A R C L`. Evidence points at `description.component` or records | Total | Components | | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 8 | [Accordion](src/components/Accordion/Accordion.stories.tsx)\* · [CopyButton](src/components/CopyButton/CopyButton.stories.tsx) · [Modal](src/components/Modal/Modal.stories.tsx) · [RowActionToolbar](src/components/RowActionToolbar/RowActionToolbar.tsx) (JSDoc only, no story) | -| 7 | [AlertDialog](src/components/AlertDialog/AlertDialog.stories.tsx) · [AIMessage](src/components/AI/AIMessage.stories.tsx) · [DataVis NITRO](src/components/DataVisNITRO/DataVisNITRO.stories.tsx) · [ESheet Builder](src/components/ESheet/EsheetBuilder.stories.tsx) · [FilterSummaryBar](src/components/FilterSummaryBar/FilterSummaryBar.stories.tsx) · [Label](src/components/Label/Label.stories.tsx) · [Markdown](src/components/Markdown/MarkdownRenderer.stories.tsx) · [ServicePicker](src/components/ServicePicker/ServicePicker.stories.tsx) · [Sparkline](src/components/Sparkline/Sparkline.stories.tsx) · [SuperChat panel](src/components/SuperChat/SuperChat.stories.tsx) · [SuperChatConversations](src/components/SuperChat/SuperChatConversations.stories.tsx) | +| 7 | [AlertDialog](src/components/AlertDialog/AlertDialog.stories.tsx) · [AIMessage](src/components/AI/AIMessage.stories.tsx) · [DataVis NITRO](https://github.com/mieweb/datavis/blob/main/stories/DataVisNITRO.stories.tsx) · [ESheet Builder](src/components/ESheet/EsheetBuilder.stories.tsx) · [FilterSummaryBar](src/components/FilterSummaryBar/FilterSummaryBar.stories.tsx) · [Label](src/components/Label/Label.stories.tsx) · [Markdown](src/components/Markdown/MarkdownRenderer.stories.tsx) · [ServicePicker](src/components/ServicePicker/ServicePicker.stories.tsx) · [Sparkline](src/components/Sparkline/Sparkline.stories.tsx) · [SuperChat panel](src/components/SuperChat/SuperChat.stories.tsx) · [SuperChatConversations](src/components/SuperChat/SuperChatConversations.stories.tsx) | | 6 | [ReadingProgressBar](src/components/ReadingProgressBar/ReadingProgressBar.stories.tsx) · [AGGrid](src/components/AGGrid/AGGrid.stories.tsx) · [AIChat (Voice)](src/components/AI/AIChatVoice.stories.tsx) · [Q](src/components/Q/Q.stories.tsx) · [SuperChatInbox](src/components/SuperChat/SuperChatInbox.stories.tsx) · [Text](src/components/Text/Text.stories.tsx) · [ThemeProvider](src/components/ThemeProvider/ThemeProvider.stories.tsx) · [Timeline](src/components/Timeline/Timeline.stories.tsx) · [Tooltip](src/components/Tooltip/Tooltip.stories.tsx) · [TranscriptView](src/components/TranscriptView/TranscriptView.stories.tsx) · [VisitScribe](src/components/AI/HeyOzwell/VisitScribe.stories.tsx) · [WakeWord](src/components/AI/HeyOzwell/WakeWord/WakeWord.stories.tsx) | | 5 | [AddressForm](src/components/Address/AddressForm.stories.tsx) · [AudioPlayer](src/components/AudioPlayer/AudioPlayer.stories.tsx) · [BusinessHoursEditor](src/components/BusinessHoursEditor/BusinessHoursEditor.stories.tsx) · [Collapsible](src/components/Collapsible/Collapsible.stories.tsx) · [DropzoneOverlay](src/components/DropzoneOverlay/DropzoneOverlay.stories.tsx) · [Icons](src/components/Icons/Icons.stories.tsx) · [OrderList](src/components/OrderList/OrderList.stories.tsx) · [OzwellChat](src/components/AI/OzwellChat.stories.tsx) · [PatientHeader](src/components/PatientHeader/PatientHeader.stories.tsx) · [PermissionsEditor](src/components/PermissionsEditor/PermissionsEditor.stories.tsx) · [PhoneInputGroup](src/components/PhoneInput/PhoneInputGroup.stories.tsx) · [ResultsEntryForm](src/components/ResultsEntryForm/ResultsEntryForm.stories.tsx) · [StepIndicator](src/components/StepIndicator/StepIndicator.stories.tsx) · [VoiceSetup](src/components/AI/HeyOzwell/VoiceSetup.stories.tsx) | @@ -219,11 +221,11 @@ Nearly all lack `description.component`. Number after each name is the total sco ## 3. Highest-priority findings 1. **The mandated default grid has the weakest selection guidance of the trio.** - [DataVisNITRO.stories.tsx:63-66](src/components/DataVisNITRO/DataVisNITRO.stories.tsx#L63-L66) + [DataVisNITRO.stories.tsx:63-66](https://github.com/mieweb/ui/blob/a8ca1ad/src/components/DataVisNITRO/DataVisNITRO.stories.tsx#L63-L66) only says it wraps `@mieweb/datavis`. It never states it is the default for all tables, never links to `Table` or `AGGrid`, never names the peers (`@mieweb/datavis`, `datavis-ace`) or the `@mieweb/ui/datavis` subpath, and the material a11y limitation is only a code comment - ([stories:54-59](src/components/DataVisNITRO/DataVisNITRO.stories.tsx#L54-L59), + ([stories:54-59](https://github.com/mieweb/ui/blob/a8ca1ad/src/components/DataVisNITRO/DataVisNITRO.stories.tsx#L54-L59), `a11y: { disable: true }`). _Effect:_ a developer on the NITRO page cannot tell why it is preferred or what it costs. _Correction:_ add purpose/selection paragraph, reciprocal link to Table, install block, and an explicit accessibility limitation. diff --git a/eslint.config.js b/eslint.config.js index 2a31609e8..8493259ed 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,7 +8,11 @@ import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; export default [ eslint.configs.recommended, { - files: ['src/**/*.{ts,tsx}'], + files: [ + 'src/**/*.{ts,tsx}', + // DataVis NITRO stories live in the datavis submodule but are linted here. + 'packages/datavis/stories/**/*.{ts,tsx}', + ], languageOptions: { parser: tsparser, parserOptions: { @@ -209,7 +213,10 @@ export default [ }, // Allow console logs in Storybook stories for development/testing { - files: ['src/**/*.stories.{ts,tsx}'], + files: [ + 'src/**/*.stories.{ts,tsx}', + 'packages/datavis/stories/**/*.stories.{ts,tsx}', + ], rules: { 'no-console': 'off', }, diff --git a/package.json b/package.json index 9c6f27d5d..7f50b4d0a 100644 --- a/package.json +++ b/package.json @@ -291,8 +291,8 @@ "copy:markdown-css": "mkdir -p dist/components/Markdown && cp src/components/Markdown/styles.css dist/components/Markdown/ 2>/dev/null || true", "copy:kerebron-css": "mkdir -p dist && cp src/styles/kerebron.css dist/kerebron.css", "typecheck": "tsc --noEmit && tsc -p .storybook/tsconfig.mobile.json", - "lint": "eslint \"src/**/*.{ts,tsx}\"", - "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix", + "lint": "eslint \"src/**/*.{ts,tsx}\" \"packages/datavis/stories/**/*.{ts,tsx}\"", + "lint:fix": "eslint \"src/**/*.{ts,tsx}\" \"packages/datavis/stories/**/*.{ts,tsx}\" --fix", "rtl:scan": "node scripts/rtl-scan.mjs", "check:templates-rsc": "node --conditions=react-server scripts/check-templates-rsc.mjs", "rtl:scan:update": "node scripts/rtl-scan.mjs --update", @@ -302,8 +302,8 @@ "catalog:check": "node scripts/catalog-check.mjs", "catalog:manifest": "node scripts/catalog-check.mjs --manifest storybook-static/catalog-manifest.json", "catalog:baseline": "node scripts/catalog-check.mjs --update-baseline", - "format": "prettier --check \"src/**/*.{ts,tsx,css}\"", - "format:fix": "prettier --write \"src/**/*.{ts,tsx,css}\"", + "format": "prettier --check \"src/**/*.{ts,tsx,css}\" \"packages/datavis/stories/**/*.{ts,tsx}\"", + "format:fix": "prettier --write \"src/**/*.{ts,tsx,css}\" \"packages/datavis/stories/**/*.{ts,tsx}\"", "test": "vitest run", "test:watch": "vitest", "prestorybook": "npm run build:esheet && npm run build:datavis", diff --git a/packages/datavis b/packages/datavis index 4750e41b3..8c95f15a9 160000 --- a/packages/datavis +++ b/packages/datavis @@ -1 +1 @@ -Subproject commit 4750e41b39bb39ae0d4718978504206243a3fbda +Subproject commit 8c95f15a96fc86b5398cef090be6219d91edf2d4 diff --git a/scripts/catalog-check.mjs b/scripts/catalog-check.mjs index d02385aee..08b124d27 100644 --- a/scripts/catalog-check.mjs +++ b/scripts/catalog-check.mjs @@ -32,6 +32,9 @@ import { getStorySortParameter, loadCsf } from 'storybook/internal/csf-tools'; const root = join(dirname(fileURLToPath(import.meta.url)), '..'); const SRC = join(root, 'src'); +// Story roots must mirror the `stories` globs in .storybook/main.ts. DataVis +// NITRO stories live in the datavis submodule but are catalogued here. +const STORY_ROOTS = [SRC, join(root, 'packages', 'datavis', 'stories')]; const TAXONOMY = JSON.parse( readFileSync(join(root, '.storybook', 'taxonomy.json'), 'utf8') ); @@ -107,6 +110,11 @@ function walk(dir, out = []) { return out; } +function walkStoryRoots(out = []) { + for (const dir of STORY_ROOTS) walk(dir, out); + return out; +} + // --------------------------------------------------------------------------- // Parse // --------------------------------------------------------------------------- @@ -145,7 +153,7 @@ function parseTitle(title, file) { } const entries = []; -for (const file of walk(SRC)) { +for (const file of walkStoryRoots()) { const code = readFileSync(file, 'utf8'); if (file.endsWith('.mdx')) { const m = code.match(/ e.kind === 'mdx') .map((e) => e.title.toLowerCase().replace(/[^a-z0-9]+/g, '-')) ); - for (const file of walk(SRC)) { + for (const file of walkStoryRoots()) { const code = readFileSync(file, 'utf8'); for (const [, id] of code.matchAll(/\?path=\/docs\/([a-z0-9-]+)--docs/g)) { if (!byId.has(id) && !pageIds.has(id)) diff --git a/src/components/DataVisNITRO/DataVisNITRO.demo.stories.tsx b/src/components/DataVisNITRO/DataVisNITRO.demo.stories.tsx deleted file mode 100644 index 9304af364..000000000 --- a/src/components/DataVisNITRO/DataVisNITRO.demo.stories.tsx +++ /dev/null @@ -1,395 +0,0 @@ -import { useEffect, useMemo, useState, type ReactNode } from 'react'; -import type { Meta, StoryObj } from '@storybook/react-vite'; - -import { - DataVisNitroGraph, - DataVisNitroGrid, - DataVisNitroSource, - type DataVisNitroAggregate, - type DataVisNitroColumn, - type DataVisNitroGraphProps, - type DataVisNitroGroupField, -} from '../../datavis'; - -type DataRow = Record; - -interface LocalFieldInfo { - field: string; - type: string; -} - -interface LocalDatasetProps { - data: DataRow[]; - typeInfo: LocalFieldInfo[]; - children: ReactNode; - groupBy?: Array; - aggregates?: DataVisNitroAggregate[]; -} - -function LocalDataset({ - data, - typeInfo, - children, - groupBy, - aggregates, -}: LocalDatasetProps) { - const varName = useMemo(() => { - const nextVarName = `__datavis_story_${Math.random().toString(36).slice(2)}`; - (window as unknown as Record)[nextVarName] = { - typeInfo, - data, - }; - return nextVarName; - }, [data, typeInfo]); - - useEffect( - () => () => { - delete (window as unknown as Record)[varName]; - }, - [varName] - ); - - return ( - - {children} - - ); -} - -const INTERACTIVE_FEATURES = { - columnResize: true, - columnReorder: true, - stickyHeaders: true, - zebraStripe: true, - keyboardNav: true, - headerContextMenu: true, -} as const; - -const WIDE_FIELDS = Array.from({ length: 50 }, (_, index) => - index === 0 ? 'recordId' : `field${String(index).padStart(2, '0')}` -); - -const WIDE_COLUMNS: DataVisNitroColumn[] = WIDE_FIELDS.map((field, index) => ({ - field, - header: index === 0 ? 'Record ID' : `Field ${String(index).padStart(2, '0')}`, - width: index === 0 ? 90 : 140, - sortable: true, - resizable: true, - reorderable: true, - typeInfo: { type: index === 0 ? 'number' : 'string' }, -})); - -const WIDE_TYPE_INFO: LocalFieldInfo[] = WIDE_FIELDS.map((field, index) => ({ - field, - type: index === 0 ? 'number' : 'string', -})); - -const WIDE_ROWS: DataRow[] = Array.from({ length: 20 }, (_, rowIndex) => - Object.fromEntries( - WIDE_FIELDS.map((field, columnIndex) => [ - field, - columnIndex === 0 - ? rowIndex + 1 - : `R${String(rowIndex + 1).padStart(2, '0')} · C${String(columnIndex).padStart(2, '0')}`, - ]) - ) -); - -const LEDGER_COLUMNS: DataVisNitroColumn[] = [ - { field: 'transactionId', header: 'Transaction ID', width: 120 }, - { field: 'date', header: 'Date', width: 110 }, - { field: 'account', header: 'Account', width: 150 }, - { field: 'department', header: 'Department', width: 130 }, - { field: 'status', header: 'Status', width: 100 }, - { - field: 'debit', - header: 'Debit', - width: 110, - align: 'right', - typeInfo: { type: 'currency' }, - }, - { - field: 'credit', - header: 'Credit', - width: 110, - align: 'right', - typeInfo: { type: 'currency' }, - }, - { - field: 'balance', - header: 'Balance', - width: 120, - align: 'right', - typeInfo: { type: 'currency' }, - }, - { field: 'reference', header: 'Reference', width: 140 }, -]; - -const LEDGER_TYPE_INFO: LocalFieldInfo[] = [ - { field: 'transactionId', type: 'number' }, - { field: 'date', type: 'date' }, - { field: 'account', type: 'string' }, - { field: 'department', type: 'string' }, - { field: 'status', type: 'string' }, - { field: 'debit', type: 'currency' }, - { field: 'credit', type: 'currency' }, - { field: 'balance', type: 'currency' }, - { field: 'reference', type: 'string' }, -]; - -const ACCOUNTS = ['Cash', 'Receivables', 'Revenue', 'Payroll', 'Inventory']; -const DEPARTMENTS = ['Engineering', 'Finance', 'Operations', 'Sales']; -const STATUSES = ['Posted', 'Pending', 'Reconciled']; - -const LEDGER_ROWS: DataRow[] = Array.from({ length: 5000 }, (_, index) => { - const debit = index % 2 === 0 ? (index % 250) * 17.25 : 0; - const credit = index % 2 === 1 ? (index % 200) * 13.5 : 0; - - return { - transactionId: 100000 + index, - date: `2026-${String((index % 12) + 1).padStart(2, '0')}-${String((index % 28) + 1).padStart(2, '0')}`, - account: ACCOUNTS[index % ACCOUNTS.length], - department: DEPARTMENTS[index % DEPARTMENTS.length], - status: STATUSES[index % STATUSES.length], - debit, - credit, - balance: 250000 + debit - credit, - reference: `REF-${String(index + 1).padStart(6, '0')}`, - }; -}); - -const EXPLORER_COLUMNS: DataVisNitroColumn[] = [ - { field: 'month', header: 'Month', width: 100 }, - { field: 'region', header: 'Region', width: 100 }, - { field: 'product', header: 'Product', width: 120 }, - { - field: 'revenue', - header: 'Revenue', - width: 110, - typeInfo: { type: 'currency' }, - }, - { - field: 'expenses', - header: 'Expenses', - width: 110, - typeInfo: { type: 'currency' }, - }, - { - field: 'profit', - header: 'Profit', - width: 110, - typeInfo: { type: 'currency' }, - }, -]; - -const EXPLORER_TYPE_INFO: LocalFieldInfo[] = [ - { field: 'month', type: 'string' }, - { field: 'region', type: 'string' }, - { field: 'product', type: 'string' }, - { field: 'revenue', type: 'currency' }, - { field: 'expenses', type: 'currency' }, - { field: 'profit', type: 'currency' }, -]; - -const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; -const REGIONS = ['North', 'South', 'East', 'West']; -const PRODUCTS = ['Clinical', 'Portal', 'Analytics']; - -const EXPLORER_ROWS: DataRow[] = Array.from({ length: 48 }, (_, index) => { - const revenue = 24000 + (index % 8) * 4200; - const expenses = 14000 + (index % 5) * 2600; - return { - month: MONTHS[index % MONTHS.length], - region: REGIONS[index % REGIONS.length], - product: PRODUCTS[index % PRODUCTS.length], - revenue, - expenses, - profit: revenue - expenses, - }; -}); - -const EMPLOYEE_TYPE_INFO: LocalFieldInfo[] = [ - { field: 'name', type: 'string' }, - { field: 'department', type: 'string' }, - { field: 'salary', type: 'currency' }, -]; - -const EMPLOYEE_ROWS: DataRow[] = [ - { name: 'Alice', department: 'Engineering', salary: 125000 }, - { name: 'Bob', department: 'Marketing', salary: 95000 }, - { name: 'Charlie', department: 'Engineering', salary: 140000 }, - { name: 'Diana', department: 'Design', salary: 110000 }, - { name: 'Eve', department: 'Marketing', salary: 88000 }, - { name: 'Frank', department: 'Finance', salary: 132000 }, - { name: 'Grace', department: 'Engineering', salary: 105000 }, - { name: 'Hank', department: 'Finance', salary: 115000 }, -]; - -const GROUP_BY_DEPARTMENT: DataVisNitroGroupField[] = [{ field: 'department' }]; - -const AVERAGE_SALARY: DataVisNitroAggregate[] = [ - { functionName: 'average', fields: ['salary'] }, -]; - -const meta: Meta = { - id: 'grids-datavis-nitro-demos', - title: 'Components/Grids/DataVis NITRO Demos', - component: DataVisNitroGrid, - tags: ['autodocs', 'scope:general-purpose', 'maturity:stable'], - parameters: { - layout: 'fullscreen', - a11y: { disable: true }, - docs: { - description: { - component: - 'Standalone demo coverage for DataVis NITRO: wide column sets, grouping and aggregates rendered from local datasets so the declarative demo APIs stay exercised in Storybook. See **Components/Grids/DataVis NITRO** for the primary component documentation and selection guidance.', - }, - }, - }, - decorators: [ - (Story) => ( -
- -
- ), - ], -}; - -export default meta; - -type Story = StoryObj; - -export const Wide50Columns: Story = { - parameters: { - docs: { - description: { - story: - 'Matches the standalone Wide demo: 20 rows across 50 resizable and reorderable columns. Scroll horizontally, drag headers, or open a header context menu.', - }, - }, - }, - render: () => ( - - - - ), -}; - -export const Large5000Rows: Story = { - parameters: { - docs: { - description: { - story: - 'Matches the standalone Large demo with 5,000 deterministic ledger rows. Use this story to exercise rendering, sorting, filtering, keyboard navigation, and scrolling at production-like volume.', - }, - }, - }, - render: () => ( - - - - ), -}; - -export const ConstrainedStickyContainer: Story = { - parameters: { - docs: { - description: { - story: - 'Matches the standalone Constrained demo: the 5,000-row ledger is held to a 500px table viewport so sticky headers and internal scrolling can be verified.', - }, - }, - }, - render: () => ( - - - - ), -}; - -export const GroupedAverageGraph: Story = { - parameters: { - docs: { - description: { - story: - 'Matches the standalone Graph Only demo. Declarative `groupBy` and `aggregates` props configure the shared source to group employees by department and graph average salary without exposing the internal view.', - }, - }, - }, - render: () => ( - - - - ), -}; - -function CombinedExplorerDemo() { - const [graphConfig, setGraphConfig] = useState< - NonNullable - >({ - chartType: 'bar', - xField: 'region', - yFields: ['revenue'], - }); - - return ( - -
- - -
-
- ); -} - -export const CombinedGraphAndGrid: Story = { - parameters: { - docs: { - description: { - story: - 'Matches the primary standalone demo layout: graph controls and a full-featured grid share one source/view, so filters, grouping, pivoting, aggregates, and chart exploration can be exercised together. Saved perspectives require a bound `Prefs` instance — see the **DataVis NITRO** perspectives story.', - }, - }, - }, - render: () => , -}; diff --git a/src/components/DataVisNITRO/DataVisNITRO.stories.tsx b/src/components/DataVisNITRO/DataVisNITRO.stories.tsx deleted file mode 100644 index 928e0030d..000000000 --- a/src/components/DataVisNITRO/DataVisNITRO.stories.tsx +++ /dev/null @@ -1,607 +0,0 @@ -import { useContext, useEffect, useMemo, useState } from 'react'; -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Download, Pencil, Trash2 } from 'lucide-react'; -import { - GridAssistant, - type GridAssistantColumn, - type PrefsInstance, -} from '@mieweb/datavis'; -import { Prefs } from 'datavis-ace'; -import { - DataVisNitroContext, - DataVisNitroGrid, - DataVisNitroLanguageSelector, - DataVisNitroSource, - type DataVisNitroGridProps, -} from './DataVisNITRO'; - -// datavis-ace is untyped JS (`Prefs` is built with a runtime `makeSubclass` -// helper), so give it an explicit constructor signature for use below. -const PrefsConstructor = Prefs as unknown as new ( - name: string, - moduleBindings: unknown, - opts: Record -) => PrefsInstance; - -const PREFS_STORAGE_KEY = 'mieweb-ui-storybook:datavis-prefs'; -// Pinned-perspective names are stored by DataVis under a key derived from the -// Prefs instance name (see PinnedPerspectivePills getStorageKey). -const PINNED_STORAGE_KEY = - 'mieweb-datavis:pinned-perspectives:mieweb-ui-storybook:employees'; - -// Clears saved perspectives in automated runs (test runner, visual -// regression) so stories render deterministically, while keeping -// localStorage persistence for normal browsing. localStorage access -// can throw in restricted contexts, so fall back to defaults quietly. -const clearSavedPerspectives = () => { - try { - if (typeof navigator !== 'undefined' && navigator.webdriver) { - window.localStorage.removeItem(PREFS_STORAGE_KEY); - window.localStorage.removeItem(PINNED_STORAGE_KEY); - } - } catch { - // Storage unavailable — the story just renders its defaults. - } - return {}; -}; - -const EMPLOYEE_COLUMNS = [ - 'id', - 'name', - 'email', - 'department', - 'status', - 'start_date', - 'manager', -]; - -const meta: Meta = { - id: 'grids-datavis-nitro', - title: 'Components/Grids/DataVis NITRO', - component: DataVisNitroGrid, - parameters: { - layout: 'fullscreen', - // DataVis NITRO is a third-party charting/grid library (datavis/wcdatavis-lib) - // whose internal DOM renders invalid ARIA attributes, nested interactive elements, - // and non-conforming contrast ratios that we cannot fix without upstream changes. - a11y: { - disable: true, - }, - docs: { - description: { - component: `> **Tables and data grids start with DataVis NITRO.** Use \`Table\` only for a few static rows the user will not sort, filter, page or export. Never hand-roll grid features on a plain table; \`AGGrid\` is deprecated. - -Source: [mieweb/datavis](https://github.com/mieweb/datavis) (\`@mieweb/datavis\`), built on the [mieweb/wcdatavis](https://github.com/mieweb/wcdatavis) DataVis ACE engine (\`datavis-ace\`). - -### What it's for - -Browsing, sorting, filtering, grouping and exporting record sets without writing grid logic. \`\` creates a datavis source/view pair (HTTP, local array or file) and \`\` renders that view through DataVis NITRO's React \`DataGrid\`. Column menus, pinned columns, aggregates and saved perspectives come from the engine, so a product gets the same grid behaviour everywhere. - -### Use it when - -- Users need to browse or work with records: any list that may grow, be sorted, filtered or exported. -- You would otherwise hand-roll sorting, filtering or column menus on top of a plain \`\`. -- Row actions are needed: render a \`RowActionToolbar\` with \`group="grid"\` from \`formatCell\`. - -### Don't use it when - -- The data is a handful of static rows shown for reading (a summary block, a definition list) — use \`Table\`. -- You need a chart rather than rows — use \`DataVisNitroGraph\` on the same source, or \`Sparkline\` for an inline strip. -- You're on a size-critical surface that must never load the grid engine — NITRO is a heavy opt-in chunk behind the \`@mieweb/ui/datavis\` entry (\`@mieweb/datavis\` and \`recharts\` are bundled in; \`datavis-ace\` is a dependency), so import it only where a grid is actually rendered. - -### Example - -\`\`\`tsx -import { DataVisNitroGrid, DataVisNitroSource } from '@mieweb/ui/datavis'; - - - openEmployee(row.id)} - /> - -\`\`\` - -The source owns the data lifecycle; the grid is presentational. Keep application state (selected id, route) in the host and react to \`onRowClick\` / \`onSelectionChange\`. - -### Limitations - -- **Accessibility:** the engine's DOM emits invalid ARIA attributes, nested interactive elements and some non-conforming contrast; automated a11y checks are disabled for this page. Provide a keyboard-reachable alternative for critical actions until upstream fixes land. -- **Theming:** the grid follows the active brand and dark mode through the DataVis colour scheme; per-cell styling goes through \`formatCell\`. -- **\`formatCell\` must return the value itself for columns it does not handle** — returning \`undefined\` blanks the cell. -- Saved perspectives persist in \`localStorage\` only on trusted workstations; public kiosks keep them in memory until refresh. -- Renders inline (no portal); the host controls height and scrolling.`, - }, - }, - catalog: { - entry: '@mieweb/ui/datavis', - peers: [], - relationships: [ - { - type: 'alternative to', - target: 'views-listview', - why: 'ListView is a read-down layout of the same records; NITRO whenever they must be sorted, filtered, paged or exported.', - }, - { - type: 'alternative to', - target: 'grids-table', - why: 'Table is for a few static rows; NITRO whenever users browse, sort, filter or export records.', - }, - { - type: 'alternative to', - target: 'encounter-orders-webchartreportviewer', - why: 'NITRO is the interactive grid for sorting, grouping and pivoting a dataset; WebChartReportViewer picks a backend-run report and shows its rows in a static Table.', - }, - { - type: 'composes with', - target: 'grids-datavis-nitro-graph', - why: 'Both render the same DataVisNitroSource; switch between rows and a chart without refetching.', - }, - { - type: 'composes with', - target: 'actions-rowactiontoolbar', - why: 'Render per-row actions inside a grid cell with group="grid" so they reveal on row hover.', - }, - { - type: 'composes with', - target: 'data-display-filtersummarybar', - why: 'FilterSummaryBar above the grid shows filtered-of-total counts for host-side filters and search, with one Clear all.', - }, - { - type: 'supersedes', - target: 'deprecated-aggrid', - why: 'AGGrid is deprecated; NITRO ships brand theming and perspectives without the ag-grid peers.', - }, - { - type: 'supersedes', - target: 'deprecated-aggrid-enhanced', - why: 'The enhanced cell renderers are covered by formatCell and the engine column types.', - }, - ], - }, - }, - tags: ['autodocs', 'scope:general-purpose', 'maturity:stable'], - decorators: [ - (Story) => ( -
- -
- ), - ], -}; - -export default meta; - -type Story = StoryObj; - -type DefaultArgs = DataVisNitroGridProps & { - perspectives?: boolean; -}; - -export const Default: StoryObj = { - args: { - perspectives: true, - }, - argTypes: { - perspectives: { - control: 'boolean', - description: - 'Enable the perspective variant: binds a `Prefs` module so the "Main Perspective" toolbar renders. Persistence follows the public kiosk / trusted workstation toolbar setting. See the With Perspectives story for details.', - }, - }, - loaders: [clearSavedPerspectives], - render: ({ perspectives }, { globals }) => { - const grid = { - title: 'Employees', - columns: EMPLOYEE_COLUMNS, - height: '420px', - }; - return ( - - {perspectives ? ( - - ) : ( - - )} - - ); - }, -}; - -export const WithControls: Story = { - render: () => ( - - - - ), -}; - -export const LocalizedGrid: Story = { - parameters: { - docs: { - description: { - story: - 'The public `DataVisNitroLanguageSelector` exposes the same locale picker as the standalone demo. Its selected locale updates translated DataVis labels, while the grid `locale` prop localizes date, number, and currency formatting.', - }, - }, - }, - render: () => { - const LocalizedGridDemo = () => { - const [locale, setLocale] = useState('es-MX'); - - return ( -
-
- -
- - - -
- ); - }; - - return ; - }, -}; - -export const Operations: Story = { - parameters: { - docs: { - description: { - story: - 'Passing an `operations` array surfaces the operations palette in the controls region. Each entry has a `label`, optional `icon` and `category`, and a `callback` that receives the current selection context (`ctx.rows`). Use it to wire row actions such as edit, delete, or export.', - }, - }, - }, - render: () => ( - - , - callback: (ctx) => window.alert(`Edit ${ctx.rows.length} row(s)`), - }, - { - label: 'Delete', - category: 'Actions', - icon: , - callback: (ctx) => window.alert(`Delete ${ctx.rows.length} row(s)`), - }, - { - label: 'Export', - category: 'Export', - icon: , - callback: () => window.alert('Exporting…'), - }, - ]} - /> - - ), -}; - -export const DerivedColumns: Story = { - render: () => ( - - - - ), -}; - -export const MinimalMode: Story = { - parameters: { - docs: { - description: { - story: - 'Minimal mode hides the title bar and instead overlays a floating hamburger button on the grid (partially transparent, fully opaque on hover). Opening it reveals a menu with the title bar actions (download, copy, refresh, show controls) plus the perspective dropdown and its buttons. Ideal for compact or embedded layouts where the full header would take up too much space. Use the "show controls" action in the menu to reveal the filter/group/pivot/aggregate panels.', - }, - }, - }, - render: () => ( - - - - ), -}; - -export const DetailRows: Story = { - parameters: { - docs: { - description: { - story: - 'New in `@mieweb/datavis` 1.3.0. Passing `renderDetailRow` adds a leading disclosure-toggle column to the plain table. Expanding a row inserts a full-width detail row rendered by the callback — ideal for drill-in content such as notes, documents, or summaries. The callback receives the row and can render any React content.', - }, - }, - }, - render: () => ( - - ( -
-
- {String(row.data.name)} -
-

- {String(row.data.name)} works in {String(row.data.department)} as{' '} - {String(row.data.status)}, reporting to {String(row.data.manager)} - . Started on {String(row.data.start_date)}. Contact:{' '} - {String(row.data.email)}. -

-
- )} - /> -
- ), -}; - -export const ExpandAllDetailRows: Story = { - parameters: { - docs: { - description: { - story: - 'New in `@mieweb/datavis` 1.3.0. The `detailRowsExpanded` prop drives expand-all / collapse-all across every detail row. Changing the value overrides individual toggles; leave it `undefined` for per-row control only. Here a button toggles the value so all detail rows open or close at once.', - }, - }, - }, - render: () => { - const AllDetailRowsDemo = () => { - const [expanded, setExpanded] = useState(false); - - return ( -
- - - ( -
-
- {String(row.data.name)} -
-

- {String(row.data.department)} · {String(row.data.status)} · - manager {String(row.data.manager)} -

-
- )} - /> -
-
- ); - }; - - return ; - }, -}; - -export const TitleActions: Story = { - parameters: { - docs: { - description: { - story: - 'New in `@mieweb/datavis` 1.3.0. The `titleActions` slot renders custom consumer actions inline in the title bar, alongside the built-in controls. Pass any React node — buttons, menus, or badges — to extend the header without replacing the toolbar.', - }, - }, - }, - render: () => ( - - - - - - } - /> - - ), -}; - -const ASSISTANT_COLUMNS: GridAssistantColumn[] = EMPLOYEE_COLUMNS.map( - (field) => ({ - field, - header: field, - type: field === 'id' ? 'number' : 'string', - }) -); - -/** Reads the shared view from DataVisNitroContext and connects the assistant to it. */ -const ConnectedGridAssistant = () => { - const view = useContext(DataVisNitroContext); - if (!view) return null; - - return ( - - ); -}; - -export const OzwellAssistant: Story = { - parameters: { - docs: { - description: { - story: - 'New in `@mieweb/datavis` 1.6.0. `` is a "Hey Ozwell" chat panel that controls the grid in natural language — sorting, filtering, grouping, pivoting, aggregates, global search, and perspectives — and answers questions about the data ("how many rows are there?", "what is the average of X for department Y?") with exact results computed from the visible rows. It shares the view from `` via `DataVisNitroContext`. Requires an Ozwell backend: set `window.__ozwell = { apiKey: "…", baseURL: "…" }` (or `localStorage["ozwellConfig"]`) in the browser console before chatting.', - }, - }, - }, - render: () => ( - -
- - -
-
- ), -}; - -/** - * Reads the shared view from DataVisNitroContext, binds a Prefs module to it, - * and passes the module to the grid so the perspective toolbar ("Main - * Perspective" dropdown, save/reset/history buttons) renders. - */ -const PerspectivesGrid = ({ - trustedDevice, - ...props -}: DataVisNitroGridProps & { trustedDevice: boolean }) => { - const view = useContext(DataVisNitroContext); - - const prefs = useMemo(() => { - if (!view) return null; - - return new PrefsConstructor('mieweb-ui-storybook:employees', null, { - autoSave: true, - backend: trustedDevice - ? { - type: 'localStorage', - localStorage: { - key: PREFS_STORAGE_KEY, - }, - } - : { type: 'temporary' }, - }); - }, [view, trustedDevice]); - - useEffect(() => { - if (!view || !prefs) return; - view.setPrefs(prefs); - prefs.prime?.(); - }, [view, prefs]); - - if (!prefs) return null; - - // Pins follow the same device-trust decision as the Prefs backend: public - // kiosks keep them in memory only. - return ( - - ); -}; - -export const WithPerspectives: Story = { - loaders: [clearSavedPerspectives], - parameters: { - docs: { - description: { - story: - 'Passing a `prefs` module (a `PrefsInstance` from `datavis-ace`) enables the perspective toolbar: the "Main Perspective" dropdown, save / save-as / reset buttons, and undo/redo history. Perspectives capture the grid configuration (sort, filter, group, pivot, aggregate, column layout). The device toolbar controls persistence: trusted workstations save to `localStorage`, while public kiosks keep perspectives in memory only and discard them on refresh. Create the `Prefs` instance, bind it to the shared view with `view.setPrefs(prefs)`, and pass it to ``. In minimal mode the same toolbar appears inside the hamburger menu.', - }, - }, - }, - render: (_args, { globals }) => ( - - - - ), -}; diff --git a/src/components/DataVisNITRO/DataVisNitroGraph.stories.tsx b/src/components/DataVisNITRO/DataVisNitroGraph.stories.tsx deleted file mode 100644 index a5913f427..000000000 --- a/src/components/DataVisNITRO/DataVisNitroGraph.stories.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { DataVisNitroGraph } from './DataVisNitroGraph'; -import { DataVisNitroSource } from './DataVisNITRO'; - -const meta: Meta = { - id: 'grids-datavis-nitro-graph', - title: 'Components/Grids/DataVis NITRO Graph', - component: DataVisNitroGraph, - parameters: { - layout: 'fullscreen', - a11y: { - disable: true, - }, - docs: { - description: { - component: `### What it's for - -Charting the same record set a NITRO grid shows. Wraps \`@mieweb/datavis\`'s \`GraphView\` and supports bar, line, area and pie charts driven by a \`config\` (\`Partial\`). Every field of the source is offered in the X/Y axis pickers, so users can explore combinations without a developer pre-defining each chart. - -### Use it when - -- A dashboard or report needs an interactive chart over data that already lives in a \`DataVisNitroSource\`. -- Users should be able to change axes or chart type themselves (\`onConfigChange\` lets the host persist their choice). - -### Don't use it when - -- You need a tiny inline trend (a cell, a header strip) — use \`Sparkline\`, which takes pre-bucketed points and no peers. -- The chart is decorative and static; an image or SVG avoids loading the datavis engine. -- You need rows, not a picture — use \`DataVisNitroGrid\`. - -### Example - -\`\`\`tsx - - - -\`\`\` - -### Limitations - -- Same accessibility caveats as the grid: the engine's SVG/DOM is not screen-reader friendly and automated a11y checks are disabled here. Pair charts with a grid or a textual summary of the numbers. -- Colours come from the DataVis scheme, which follows brand and dark mode; individual series colours are not brand tokens. -- \`@mieweb/datavis\` is bundled into the \`@mieweb/ui/datavis\` entry and \`datavis-ace\` ships as a dependency of \`@mieweb/ui\`; charts are only available from that entry, so apps without grids/charts do not pay for it.`, - }, - }, - catalog: { - entry: '@mieweb/ui/datavis', - peers: [], - relationships: [ - { - type: 'composes with', - target: 'grids-datavis-nitro', - why: 'Share one DataVisNitroSource between a grid and a graph of the same records.', - }, - { - type: 'alternative to', - target: 'grids-sparkline', - why: 'Sparkline is a dependency-free inline strip over pre-bucketed points; the graph is a full interactive chart.', - }, - { - type: 'alternative to', - target: 'data-display-ychart', - why: 'DataVisNitroGraph is the shipped, token-themed chart; YChart is an unexported org-chart demo from the ychart submodule.', - }, - ], - }, - }, - tags: ['autodocs', 'scope:general-purpose', 'maturity:stable'], - decorators: [ - (Story) => ( -
- -
- ), - ], -}; - -export default meta; - -type Story = StoryObj; - -/** Revenue by region — a classic bar chart comparison. */ -export const BarChart: Story = { - render: () => ( - - - - ), -}; - -/** Revenue trend over months — shows progression over time. */ -export const LineChart: Story = { - render: () => ( - - - - ), -}; - -/** Units sold over time — filled area highlights volume. */ -export const AreaChart: Story = { - render: () => ( - - - - ), -}; - -/** Revenue share by product — proportional breakdown. */ -export const PieChart: Story = { - render: () => ( - - - - ), -}; - -/** Revenue vs. expenses stacked by month — compare totals at a glance. */ -export const StackedBar: Story = { - render: () => ( - - - - ), -}; - -/** No pre-selected config — pick any chart type and axes to explore. */ -export const DefaultConfig: Story = { - render: () => ( - - - - ), -}; diff --git a/src/components/DataVisNITRO/MAINTAINERS.md b/src/components/DataVisNITRO/MAINTAINERS.md index 4bd7d5811..523626ca4 100644 --- a/src/components/DataVisNITRO/MAINTAINERS.md +++ b/src/components/DataVisNITRO/MAINTAINERS.md @@ -6,7 +6,7 @@ ## What's in here -Compatibility re-exports and Storybook examples for MIE's **datavis** engine, exported from a **separate entry** ([src/datavis.ts](../../datavis.ts) → `@mieweb/ui/datavis`): +Compatibility re-exports for MIE's **datavis** engine, exported from a **separate entry** ([src/datavis.ts](../../datavis.ts) → `@mieweb/ui/datavis`). The Storybook examples live in the datavis submodule at [packages/datavis/stories/](https://github.com/mieweb/datavis/tree/main/stories) and are globbed into this repo's Storybook by [.storybook/main.ts](../../../.storybook/main.ts): - `DataVisNitroSource` — declares a data source (`type: 'http' | 'local' | 'file'`) and publishes a view through `DataVisNitroContext`. diff --git a/src/components/SuperChat/SuperChat.stories.tsx b/src/components/SuperChat/SuperChat.stories.tsx index 4268ea368..62e713427 100644 --- a/src/components/SuperChat/SuperChat.stories.tsx +++ b/src/components/SuperChat/SuperChat.stories.tsx @@ -92,6 +92,7 @@ const meta: Meta = { - You want the list and the panel together with selection handled — \`SuperChatInbox\`. - One user talks to one assistant and you do not need Markdown, participants or a conversation model — \`AIChat\` is lighter (no \`react-markdown\` peers). - Plain human-to-human messaging with delivery states and read receipts — the Messaging module; SuperChat has no \`status\` icons or read receipts. +- The messages are rows in a DataVis view (an exported thread, an audit log) that users only read — [DataVisNitroChat](?path=/docs/grids-datavis-chat--docs) maps sender / message / time columns onto this panel in read-only mode, alongside the NITRO grid over the same view. - The host cannot install the Markdown-core peers (\`react-markdown\`, \`remark-gfm\`, \`rehype-sanitize\`) — they are required for the default renderer. ### Example