feat(dss-ui): Button component with design tokens only
Some checks failed
DSS Project Analysis / dss-context-update (push) Has been cancelled

- Button.css uses only CSS custom properties, no fallbacks
- Token validator now blocks hardcoded values (strict_mode: true)
- Hook scripts converted from ESM (.js) to CommonJS (.cjs)
- Storybook unified config with HMR disabled for nginx proxy
- Added dss-ui package with Figma-synced components and stories

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
DSS
2025-12-11 18:47:57 -03:00
parent 44cea9443b
commit 09b234a07f
82 changed files with 4847 additions and 9 deletions

94
.storybook/main.ts Normal file
View File

@@ -0,0 +1,94 @@
import type { StorybookConfig } from '@storybook/preact-vite';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { readFileSync, existsSync } from 'fs';
// ESM-compatible __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* DSS Unified Storybook Configuration
*
* Loads stories from:
* 1. DSS Core (packages/dss-ui/stories/) - atoms, molecules, organisms
* 2. Active project stories - templates, pages, project-specific components
*
* Project selection is controlled via:
* 1. STORYBOOK_PROJECT env var (highest priority)
* 2. .dss/state.json activeProject field (managed by Claude/admin-ui)
* 3. Default: 'admin-ui'
*/
// Read active project from DSS state
function getActiveProject(): string {
// Env var takes priority
if (process.env.STORYBOOK_PROJECT) {
return process.env.STORYBOOK_PROJECT;
}
// Try reading from DSS state
const statePath = join(__dirname, '../.dss/state.json');
if (existsSync(statePath)) {
try {
const state = JSON.parse(readFileSync(statePath, 'utf-8'));
if (state.activeProject) {
return state.activeProject;
}
} catch (e) {
console.warn('Failed to read DSS state, using default project');
}
}
return 'admin-ui';
}
const activeProject = getActiveProject();
console.log(`[DSS] Active project: ${activeProject}`);
const config: StorybookConfig = {
stories: [
// DSS Core stories (always loaded)
'../packages/dss-ui/stories/**/*.stories.@(js|jsx|ts|tsx)',
'../packages/dss-ui/stories/**/*.mdx',
// Project-specific stories
`../${activeProject}/src/**/*.stories.@(js|jsx|ts|tsx)`,
`../${activeProject}/src/**/*.mdx`,
],
addons: [
'@storybook/addon-docs',
'@storybook/addon-a11y',
],
framework: {
name: '@storybook/preact-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
staticDirs: [
'../packages/dss-ui/src/primitives',
],
viteFinal: async (config) => {
config.server = config.server || {};
config.server.allowedHosts = [
'localhost',
'storybook.dss.overbits.luz.uy'
];
// HMR configuration
// For local development: HMR works normally on localhost:6006
// For external access (storybook.dss.overbits.luz.uy): HMR is disabled
// because Vite uses a separate port (24678) that isn't proxied through nginx
// Disable HMR for external access - Vite's HMR websocket isn't proxied through nginx
config.server.hmr = false;
// Resolve packages for proper imports
config.resolve = config.resolve || {};
config.resolve.alias = {
...config.resolve.alias,
'@dss/ui': join(__dirname, '../packages/dss-ui/src'),
};
return config;
},
};
export default config;

238
.storybook/preview.jsx Normal file
View File

@@ -0,0 +1,238 @@
/**
* DSS Unified Storybook Preview
*
* Single Storybook instance that loads:
* - DSS Core components (atoms, molecules, organisms)
* - Project-specific stories (templates, pages)
*
* Toolbar controls:
* - Skin: shadcn, material, heroui
* - Mode: light/dark
*/
import { h } from 'preact';
// Import all component CSS from DSS Core
const coreStyles = import.meta.glob('../packages/dss-ui/src/atoms/*.css', { eager: true });
// Import design tokens
import '../packages/dss-ui/src/primitives/tokens.css';
// Skin CSS variables - generated from skin dictionaries
const skinStyles = {
shadcn: {
'--color-background': 'hsl(0 0% 100%)',
'--color-foreground': 'hsl(240 10% 3.9%)',
'--color-card': 'hsl(0 0% 100%)',
'--color-card-foreground': 'hsl(240 10% 3.9%)',
'--color-popover': 'hsl(0 0% 100%)',
'--color-popover-foreground': 'hsl(240 10% 3.9%)',
'--color-primary': 'hsl(240 5.9% 10%)',
'--color-primary-foreground': 'hsl(0 0% 98%)',
'--color-secondary': 'hsl(240 4.8% 95.9%)',
'--color-secondary-foreground': 'hsl(240 5.9% 10%)',
'--color-muted': 'hsl(240 4.8% 95.9%)',
'--color-muted-foreground': 'hsl(240 3.8% 46.1%)',
'--color-accent': 'hsl(240 4.8% 95.9%)',
'--color-accent-foreground': 'hsl(240 5.9% 10%)',
'--color-destructive': 'hsl(0 84.2% 60.2%)',
'--color-destructive-foreground': 'hsl(0 0% 98%)',
'--color-border': 'hsl(240 5.9% 90%)',
'--color-input': 'hsl(240 5.9% 90%)',
'--color-ring': 'hsl(240 5.9% 10%)',
'--radius': '0.5rem',
},
material: {
'--color-background': 'hsl(0 0% 99%)',
'--color-foreground': 'hsl(0 0% 10%)',
'--color-card': 'hsl(0 0% 100%)',
'--color-card-foreground': 'hsl(0 0% 10%)',
'--color-popover': 'hsl(0 0% 100%)',
'--color-popover-foreground': 'hsl(0 0% 10%)',
'--color-primary': 'hsl(262 80% 50%)',
'--color-primary-foreground': 'hsl(0 0% 100%)',
'--color-secondary': 'hsl(262 20% 90%)',
'--color-secondary-foreground': 'hsl(262 80% 30%)',
'--color-muted': 'hsl(0 0% 96%)',
'--color-muted-foreground': 'hsl(0 0% 45%)',
'--color-accent': 'hsl(262 20% 95%)',
'--color-accent-foreground': 'hsl(262 80% 40%)',
'--color-destructive': 'hsl(0 75% 42%)',
'--color-destructive-foreground': 'hsl(0 0% 100%)',
'--color-border': 'hsl(0 0% 88%)',
'--color-input': 'hsl(0 0% 88%)',
'--color-ring': 'hsl(262 80% 50%)',
'--radius': '1rem',
},
heroui: {
'--color-background': 'hsl(0 0% 100%)',
'--color-foreground': 'hsl(240 6% 10%)',
'--color-card': 'hsl(0 0% 100%)',
'--color-card-foreground': 'hsl(240 6% 10%)',
'--color-popover': 'hsl(0 0% 100%)',
'--color-popover-foreground': 'hsl(240 6% 10%)',
'--color-primary': 'hsl(212 100% 48%)',
'--color-primary-foreground': 'hsl(0 0% 100%)',
'--color-secondary': 'hsl(270 60% 52%)',
'--color-secondary-foreground': 'hsl(0 0% 100%)',
'--color-muted': 'hsl(240 5% 96%)',
'--color-muted-foreground': 'hsl(240 4% 46%)',
'--color-accent': 'hsl(212 100% 95%)',
'--color-accent-foreground': 'hsl(212 100% 40%)',
'--color-destructive': 'hsl(0 72% 51%)',
'--color-destructive-foreground': 'hsl(0 0% 100%)',
'--color-border': 'hsl(240 6% 90%)',
'--color-input': 'hsl(240 6% 90%)',
'--color-ring': 'hsl(212 100% 48%)',
'--radius': '0.75rem',
},
};
// Dark mode overrides
const darkModeStyles = {
shadcn: {
'--color-background': 'hsl(240 10% 3.9%)',
'--color-foreground': 'hsl(0 0% 98%)',
'--color-card': 'hsl(240 10% 3.9%)',
'--color-card-foreground': 'hsl(0 0% 98%)',
'--color-popover': 'hsl(240 10% 3.9%)',
'--color-popover-foreground': 'hsl(0 0% 98%)',
'--color-primary': 'hsl(0 0% 98%)',
'--color-primary-foreground': 'hsl(240 5.9% 10%)',
'--color-secondary': 'hsl(240 3.7% 15.9%)',
'--color-secondary-foreground': 'hsl(0 0% 98%)',
'--color-muted': 'hsl(240 3.7% 15.9%)',
'--color-muted-foreground': 'hsl(240 5% 64.9%)',
'--color-accent': 'hsl(240 3.7% 15.9%)',
'--color-accent-foreground': 'hsl(0 0% 98%)',
'--color-destructive': 'hsl(0 62.8% 30.6%)',
'--color-destructive-foreground': 'hsl(0 0% 98%)',
'--color-border': 'hsl(240 3.7% 15.9%)',
'--color-input': 'hsl(240 3.7% 15.9%)',
'--color-ring': 'hsl(240 4.9% 83.9%)',
},
material: {
'--color-background': 'hsl(280 10% 8%)',
'--color-foreground': 'hsl(280 5% 90%)',
'--color-card': 'hsl(280 10% 12%)',
'--color-card-foreground': 'hsl(280 5% 90%)',
'--color-popover': 'hsl(280 10% 12%)',
'--color-popover-foreground': 'hsl(280 5% 90%)',
'--color-primary': 'hsl(262 80% 70%)',
'--color-primary-foreground': 'hsl(280 10% 8%)',
'--color-secondary': 'hsl(262 20% 25%)',
'--color-secondary-foreground': 'hsl(262 50% 85%)',
'--color-muted': 'hsl(280 10% 15%)',
'--color-muted-foreground': 'hsl(280 5% 60%)',
'--color-accent': 'hsl(262 20% 20%)',
'--color-accent-foreground': 'hsl(262 80% 80%)',
'--color-destructive': 'hsl(0 60% 50%)',
'--color-destructive-foreground': 'hsl(0 0% 100%)',
'--color-border': 'hsl(280 10% 20%)',
'--color-input': 'hsl(280 10% 20%)',
'--color-ring': 'hsl(262 80% 70%)',
},
heroui: {
'--color-background': 'hsl(240 10% 4%)',
'--color-foreground': 'hsl(0 0% 95%)',
'--color-card': 'hsl(240 10% 8%)',
'--color-card-foreground': 'hsl(0 0% 95%)',
'--color-popover': 'hsl(240 10% 8%)',
'--color-popover-foreground': 'hsl(0 0% 95%)',
'--color-primary': 'hsl(212 100% 55%)',
'--color-primary-foreground': 'hsl(0 0% 100%)',
'--color-secondary': 'hsl(270 60% 60%)',
'--color-secondary-foreground': 'hsl(0 0% 100%)',
'--color-muted': 'hsl(240 5% 15%)',
'--color-muted-foreground': 'hsl(240 4% 60%)',
'--color-accent': 'hsl(212 50% 15%)',
'--color-accent-foreground': 'hsl(212 100% 70%)',
'--color-destructive': 'hsl(0 72% 55%)',
'--color-destructive-foreground': 'hsl(0 0% 100%)',
'--color-border': 'hsl(240 6% 20%)',
'--color-input': 'hsl(240 6% 20%)',
'--color-ring': 'hsl(212 100% 55%)',
},
};
// Apply theme to document root
const applyTheme = (skin, mode) => {
const root = document.documentElement;
const styles = mode === 'dark'
? { ...skinStyles[skin], ...darkModeStyles[skin] }
: skinStyles[skin];
Object.entries(styles).forEach(([prop, value]) => {
root.style.setProperty(prop, value);
});
// Set background color on body for Storybook canvas
document.body.style.backgroundColor = styles['--color-background'];
document.body.style.color = styles['--color-foreground'];
};
/** @type { import('@storybook/preact').Preview } */
const preview = {
globalTypes: {
skin: {
description: 'Design system skin',
defaultValue: 'shadcn',
toolbar: {
title: 'Skin',
icon: 'paintbrush',
items: [
{ value: 'shadcn', title: 'shadcn/ui (Default)' },
{ value: 'material', title: 'Material Design' },
{ value: 'heroui', title: 'HeroUI' },
],
dynamicTitle: true,
},
},
mode: {
description: 'Color mode',
defaultValue: 'light',
toolbar: {
title: 'Mode',
icon: 'circlehollow',
items: [
{ value: 'light', title: 'Light', icon: 'sun' },
{ value: 'dark', title: 'Dark', icon: 'moon' },
],
dynamicTitle: true,
},
},
},
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: {
disable: true, // Use our custom theme system instead
},
options: {
storySort: {
order: [
'0. Overview',
'1. Primitives',
'2. Atoms',
'3. Molecules',
'4. Organisms',
'5. Templates', // Project-specific
'6. Pages', // Project-specific
],
},
},
},
decorators: [
(Story, context) => {
const { skin, mode } = context.globals;
applyTheme(skin || 'shadcn', mode || 'light');
return h(Story, null);
},
],
};
export default preview;