Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { StorybookConfig } from '@storybook/react-vite';
import path from 'path';
import fs from 'fs';

const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
Expand All @@ -16,6 +18,64 @@ const config: StorybookConfig = {
reactDocgen: 'react-docgen-typescript',
},
staticDirs: ['./public'],
viteFinal: async (config) => {
const dataVisDir = path.resolve(process.cwd(), 'src/components/DataVis');
const wcdatavisSrc = path.resolve(process.cwd(), 'node_modules/wcdatavis/src');
const wcdatavisAvailable = fs.existsSync(wcdatavisSrc);

config.resolve = config.resolve || {};
const existingAliases = Array.isArray(config.resolve.alias)
? config.resolve.alias
: Object.entries(config.resolve.alias || {}).map(([find, replacement]) => ({ find, replacement }));

if (wcdatavisAvailable) {
// Full wcdatavis integration — shim loads jQuery globals + plugins,
// then re-exports wcdatavis classes for the DataVis component.
const wcdatavisNodeModules = path.resolve(process.cwd(), 'node_modules/wcdatavis/node_modules');

config.resolve.alias = [
...existingAliases,
{
find: /^wcdatavis$/,
replacement: path.resolve(dataVisDir, 'wcdatavis-shim.js'),
},
{
find: /^@mieweb\/wcdatavis$/,
replacement: path.resolve(dataVisDir, 'wcdatavis-shim.js'),
},
// Resolve wcdatavis's jQuery plugins from its nested node_modules so they
// extend the same jQuery instance the source files import
{ find: /^jquery-ui(.*)$/, replacement: path.join(wcdatavisNodeModules, 'jquery-ui$1') },
{ find: /^jquery-contextmenu$/, replacement: path.join(wcdatavisNodeModules, 'jquery-contextmenu') },
{ find: /^sumoselect$/, replacement: path.join(wcdatavisNodeModules, 'sumoselect') },
{ find: /^flatpickr$/, replacement: path.join(wcdatavisNodeModules, 'flatpickr') },
{ find: /^jquery$/, replacement: path.join(wcdatavisNodeModules, 'jquery') },
];

// Pre-bundle wcdatavis and its jQuery plugins
config.optimizeDeps = config.optimizeDeps || {};
config.optimizeDeps.include = [
...(config.optimizeDeps.include || []),
'wcdatavis/src/source.js',
'wcdatavis/src/computed_view.js',
'wcdatavis/src/grid.js',
'wcdatavis/src/graph.js',
'wcdatavis/src/prefs.js',
];
} else {
// wcdatavis not installed (CI, fresh checkouts) — alias to lightweight
// stubs so the Storybook build completes. DataVis stories will render
// an error state instead of the live grid.
config.resolve.alias = [
...existingAliases,
{ find: /^wcdatavis\/dist\/wcdatavis\.css/, replacement: path.resolve(dataVisDir, 'wcdatavis-stub.css') },
{ find: /^wcdatavis$/, replacement: path.resolve(dataVisDir, 'wcdatavis-stub.js') },
{ find: /^@mieweb\/wcdatavis$/, replacement: path.resolve(dataVisDir, 'wcdatavis-stub.js') },
];
}

return config;
},
};

export default config;
8 changes: 8 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
<!-- FontAwesome 4.7 — required by wcdatavis icons -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0="
crossorigin="anonymous"
/>

<!-- Microsoft Clarity Analytics - Production only -->
<script type="text/javascript">
// Only load Clarity in production (when not on localhost)
Expand Down
59 changes: 37 additions & 22 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,41 @@ const brands: Record<string, BrandConfig> = {
webchart: webchartBrand,
};

// Helper to generate semantic color variable declarations
function semanticVarBlock(sc: BrandConfig['colors']['light']) {
return `
--mieweb-background: ${sc.background} !important;
--mieweb-foreground: ${sc.foreground} !important;
--mieweb-card: ${sc.card} !important;
--mieweb-card-foreground: ${sc.cardForeground} !important;
--mieweb-muted: ${sc.muted} !important;
--mieweb-muted-foreground: ${sc.mutedForeground} !important;
--mieweb-border: ${sc.border} !important;
--mieweb-input: ${sc.input} !important;
--mieweb-ring: ${sc.ring} !important;
--mieweb-destructive: ${sc.destructive} !important;
--mieweb-destructive-foreground: ${sc.destructiveForeground} !important;
--mieweb-success: ${sc.success} !important;
--mieweb-success-foreground: ${sc.successForeground} !important;
--mieweb-warning: ${sc.warning} !important;
--mieweb-warning-foreground: ${sc.warningForeground} !important;`;
}

// Function to apply brand CSS variables to document
function applyBrandStyles(brand: BrandConfig, isDark: boolean) {
const root = document.documentElement;
const colors = brand.colors;
const semanticColors = isDark ? colors.dark : colors.light;
const lightColors = colors.light;
const darkColors = colors.dark;

// Remove any existing brand style tag
const existingStyle = document.getElementById('mieweb-brand-styles');
if (existingStyle) {
existingStyle.remove();
}

// Create a style tag with high specificity to override base.css
const styleTag = document.createElement('style');
styleTag.id = 'mieweb-brand-styles';
styleTag.textContent = `
:root, [data-theme="light"], [data-theme="dark"] {
// Shared tokens (primary scale, typography, radius, shadows) — same in both modes
const sharedVars = `
--mieweb-primary-50: ${colors.primary[50]} !important;
--mieweb-primary-100: ${colors.primary[100]} !important;
--mieweb-primary-200: ${colors.primary[200]} !important;
Expand All @@ -48,21 +66,6 @@ function applyBrandStyles(brand: BrandConfig, isDark: boolean) {
--mieweb-primary-800: ${colors.primary[800]} !important;
--mieweb-primary-900: ${colors.primary[900]} !important;
--mieweb-primary-950: ${colors.primary[950]} !important;
--mieweb-background: ${semanticColors.background} !important;
--mieweb-foreground: ${semanticColors.foreground} !important;
--mieweb-card: ${semanticColors.card} !important;
--mieweb-card-foreground: ${semanticColors.cardForeground} !important;
--mieweb-muted: ${semanticColors.muted} !important;
--mieweb-muted-foreground: ${semanticColors.mutedForeground} !important;
--mieweb-border: ${semanticColors.border} !important;
--mieweb-input: ${semanticColors.input} !important;
--mieweb-ring: ${semanticColors.ring} !important;
--mieweb-destructive: ${semanticColors.destructive} !important;
--mieweb-destructive-foreground: ${semanticColors.destructiveForeground} !important;
--mieweb-success: ${semanticColors.success} !important;
--mieweb-success-foreground: ${semanticColors.successForeground} !important;
--mieweb-warning: ${semanticColors.warning} !important;
--mieweb-warning-foreground: ${semanticColors.warningForeground} !important;
--mieweb-font-sans: ${brand.typography.fontFamily.sans.map((f) => (f.includes(' ') ? `"${f}"` : f)).join(', ')} !important;
${brand.typography.fontFamily.mono ? `--mieweb-font-mono: ${brand.typography.fontFamily.mono.map((f) => (f.includes(' ') ? `"${f}"` : f)).join(', ')} !important;` : ''}
--mieweb-radius-none: ${brand.borderRadius.none} !important;
Expand All @@ -74,7 +77,19 @@ function applyBrandStyles(brand: BrandConfig, isDark: boolean) {
--mieweb-radius-full: ${brand.borderRadius.full} !important;
--mieweb-shadow-card: ${brand.boxShadow.card} !important;
--mieweb-shadow-dropdown: ${brand.boxShadow.dropdown} !important;
--mieweb-shadow-modal: ${brand.boxShadow.modal} !important;
--mieweb-shadow-modal: ${brand.boxShadow.modal} !important;`;

// Build style tag with separate light & dark blocks so inline .dark wrappers work
const styleTag = document.createElement('style');
styleTag.id = 'mieweb-brand-styles';
styleTag.textContent = `
:root {
${sharedVars}
${semanticVarBlock(isDark ? darkColors : lightColors)}
}
.dark:not(:root), [data-theme="dark"]:not(:root) {
${sharedVars}
${semanticVarBlock(darkColors)}
}
`;
document.head.appendChild(styleTag);
Expand Down
11 changes: 11 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export default [
requestAnimationFrame: 'readonly',
cancelAnimationFrame: 'readonly',
getComputedStyle: 'readonly',
MutationObserver: 'readonly',
// File APIs
File: 'readonly',
FileList: 'readonly',
Expand Down Expand Up @@ -124,6 +125,16 @@ export default [
'no-console': 'off',
},
},
// DataVis shim files run in browser context and need browser globals
{
files: ['src/components/DataVis/*.js'],
languageOptions: {
globals: {
window: 'readonly',
console: 'readonly',
},
},
},
{
ignores: ['dist/**', 'node_modules/**', '*.config.*', '.storybook/**'],
},
Expand Down
55 changes: 54 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,10 @@
"prepublishOnly": "npm run build"
},
"peerDependencies": {
"@mieweb/wcdatavis": ">=3.0.0",
"ag-grid-community": ">=32.0.0",
"ag-grid-react": ">=32.0.0",
"font-awesome": ">=4.7.0",
"react": ">=18.0.0",
"react-dom": ">=18.0.0",
"wavesurfer.js": ">=7.0.0"
Expand All @@ -157,6 +159,12 @@
},
"wavesurfer.js": {
"optional": true
},
"@mieweb/wcdatavis": {
"optional": true
},
"font-awesome": {
"optional": true
}
},
"dependencies": {
Expand Down Expand Up @@ -204,7 +212,8 @@
"typescript": "^5.7.3",
"vite": "^7.3.1",
"vitest": "^3.0.4",
"wavesurfer.js": "^7.8.17"
"wavesurfer.js": "^7.8.17",
"wcdatavis": "file:../../datavis/wcdatavis"
},
"engines": {
"node": ">=18.0.0"
Expand Down
Loading
Loading