@emporix/cockpit-component-library
v1.0.4
Published
A React component library for cockipt projects
Readme
Emporix Cockpit Component Library
A modern React component library built with TypeScript, Vite, and SCSS modules.
Features
- 🚀 Modern Stack: Built with Vite, TypeScript, and React 18
- 🎨 Styled Components: SCSS modules for component styling
- 📚 Storybook: Interactive component documentation
- 🧪 Testing: Comprehensive test suite with Vitest and React Testing Library
- 📦 Library Build: Optimized for npm package distribution
- 🔄 CI/CD: Automated testing on push/PR; npm publish on version tags
- 🎨 CSS Variables: Global theming system with CSS custom properties
Quick Start
Installation
npm install @emporix/cockpit-component-libraryUsage
Important: Import the library styles in your app so component layout and appearance stay consistent. If you skip this step, components may be unstyled or layout may break.
Method 1: Import styles in your main CSS file (Recommended)
/* In your main CSS file (e.g., index.css, App.css) */
@import '@emporix/cockpit-component-library/styles';import { PrimaryButton } from '@emporix/cockpit-component-library'
const MyComponent = () => {
return (
<PrimaryButton onClick={() => console.log('Clicked!')}>
Click me
</PrimaryButton>
)
}Method 2: Import styles in your main JavaScript/TypeScript file
import { PrimaryButton } from '@emporix/cockpit-component-library'
import '@emporix/cockpit-component-library/styles'
const MyComponent = () => {
return (
<PrimaryButton onClick={() => console.log('Clicked!')}>
Click me
</PrimaryButton>
)
}Method 3: Copy CSS file manually (Fallback)
If the above methods don't work, copy the CSS file from node_modules/@emporix/cockpit-component-library/dist/style.css to your project and import it directly.
Components
Current exported components:
PrimaryButtonSecondaryButtonTertiaryButtonContrastButtonTabsRichTextEditorSpinnerBreadcrumbDropdownMultiSelectInputTextSelectButtonCardIconCardFileUploadDropzoneChipAlertBoxSideMenuTopNavToast(plusToastProvideranduseToast)ModalConfirmDialogProductTableProductTableIconAddButtonOrderTableOrderStatusTagWizardTimeline
PrimaryButton
A simple, focused button component for primary actions.
import { PrimaryButton } from '@emporix/cockpit-component-library'
// Basic usage
<PrimaryButton label="Click me" />
// With disabled state
<PrimaryButton label="Disabled Button" disabled />
// With click handler
<PrimaryButton label="Confirm Action" onClick={() => alert('Confirmed!')} />
// Loading state
<PrimaryButton label="Saving..." loading />
// With icon
<PrimaryButton label="Download" icon={<FiDownload />} iconPos="left" />Props
| Prop | Type | Default | Description |
| ----------- | ------------------- | ------- | ---------------------------------------------- |
| label | string | - | Text label displayed inside the button |
| disabled | boolean | false | Whether button is disabled |
| loading | boolean | false | Shows loading spinner and disables the button |
| icon | ReactNode | - | Optional icon shown next to the label or alone |
| iconPos | 'left' \| 'right' | 'left' | Icon position relative to the label |
| onClick | () => void | - | Click handler |
| className | string | - | Additional CSS class |
| data-testid | string | - | Test ID for testing |
Card
White panel styled with design tokens (defaults from src/styles/index.scss). The root uses a 1px border (var(--color-card-border)), corner radius var(--card-radius) (default 8px), padding var(--card-padding) (default 16px), and vertical spacing between direct children var(--card-section-gap) (default 12px). Card.Body stacks title, meta, etc. with var(--card-body-gap) (default 16px). Card.Header, Card.Meta, and Card.MetaRow share var(--card-meta-gap) (default 6px); the header’s leading side (tag + children) also uses a fixed 8px gap between flex items. Card.MetaRow uses a 20×20px icon frame with 18×18px SVGs—pass icons intended for that footprint (or larger icons will scale visually via CSS). Compound parts: Card.Body, Card.Header (optional tag + actions), Card.Title, Card.Meta / Card.MetaRow, Card.Footer, Card.IconButton. Use Chip in the header tag slot for status pills (e.g. <Chip variant="error">Blocker</Chip>). Override the --card-* variables to tune spacing without forking the component (see Card layout tokens under Theming).
import { Card, Chip, TertiaryButton } from '@emporix/cockpit-component-library'
import { FiEdit2, FiTrash2 } from 'react-icons/fi'
// Basic usage
<Card>
<h3>Title</h3>
<p>Description or nested components go here.</p>
</Card>
// Structured card (see Storybook “IssueCard”)
<Card>
<Card.Body>
<Card.Header
tag={<Chip variant="error">Blocker</Chip>}
actions={
<>
<Card.IconButton type="button" aria-label="Edit">
<FiEdit2 />
</Card.IconButton>
<Card.IconButton type="button" aria-label="Delete">
<FiTrash2 />
</Card.IconButton>
</>
}
/>
<Card.Title>Issue headline</Card.Title>
<Card.Meta>
<Card.MetaRow icon={<span />}>Supporting detail line</Card.MetaRow>
</Card.Meta>
</Card.Body>
<Card.Footer>
<TertiaryButton label="Edit" icon={<FiEdit2 />} iconPos="left" />
</Card.Footer>
</Card>
// With custom class and test id
<Card className="my-card" data-testid="details-card">
<p>Content</p>
</Card>Props
| Prop | Type | Default | Description | | ----------- | --------- | ------- | -------------------------------------------- | | children | ReactNode | - | Content displayed inside the card (required) | | className | string | - | Additional CSS class on the root element | | data-testid | string | - | Test ID for the root element |
All standard HTML div attributes (e.g. id, role, aria-*, onClick) are forwarded to the root element via CardProps. Subcomponents accept the usual attributes for their underlying elements (Card.Title → h2, Card.IconButton → button, etc.).
Chip
A compact, pill-shaped label for statuses (e.g. draft, submitted). Four variants use a light tinted background with darker text: success (green), warning (orange), info (blue), error (rose / burgundy — same look as Figma card header labels such as “Blocker”). Use Chip for those header pills instead of a separate card-only primitive. Colors are driven by CSS variables (see Theming with CSS Variables).
import { Chip } from '@emporix/cockpit-component-library'
<Chip variant="success">submitted</Chip>
<Chip variant="warning">draft</Chip>
<Chip variant="info">processed</Chip>
<Chip variant="error">1 issue</Chip>Props
| Prop | Type | Default | Description |
| ----------- | --------------------------------------------- | ------- | ---------------------------------------- |
| variant | 'success' \| 'warning' \| 'info' \| 'error' | info | Visual style / semantic color |
| children | ReactNode | - | Content inside the chip (required) |
| className | string | - | Additional CSS class on the root element |
| data-testid | string | - | Test ID for the root element |
The root element is a <span>. Standard HTML span attributes are forwarded via ChipProps.
InputText
A text input with optional label and tooltip. Hover and focus styles match the Dropdown component.
import { InputText } from '@emporix/cockpit-component-library'
// With label and info tooltip
<InputText
label="Id"
tooltip="Unique identifier for this record"
placeholder="Enter id"
/>
// Required field
<InputText label="Name" required placeholder="Enter name" />
// Disabled
<InputText label="Id" placeholder="Enter id" disabled />
// Multiline (textarea)
<InputText
label="Description"
textarea
rows={4}
placeholder="Enter a longer description"
/>Props
| Prop | Type | Default | Description |
| ----------- | --------- | ------- | ------------------------------------------------------- |
| label | ReactNode | - | Label text above the input |
| tooltip | string | - | If set, shows info icon with this tooltip |
| required | boolean | false | Shows asterisk on label |
| inputId | string | - | id for the input (for label association) |
| textarea | boolean | false | When true, renders a <textarea> instead of <input> |
| rows | number | 3 | Number of visible rows (textarea only) |
| disabled | boolean | false | Whether the input is disabled |
| className | string | - | Additional CSS class for the root |
| data-testid | string | - | Test ID for root and input (input gets suffix -input) |
All standard HTML input/textarea attributes (placeholder, value, onChange, etc.) are supported.
SelectButton
Choose single or multiple options using buttons. API inspired by PrimeReact SelectButton. Selected state uses project primary colors.
import { SelectButton } from '@emporix/cockpit-component-library'
const options = [
{ label: 'Off', value: 'off' },
{ label: 'On', value: 'on' },
]
// Single selection
<SelectButton
value={value}
onChange={(e) => setValue(e.value)}
options={options}
/>
// Multiple selection
<SelectButton
value={selected}
onChange={(e) => setSelected(e.value)}
options={options}
multiple
/>Props
| Prop | Type | Default | Description | | -------------- | ------------------------ | ------- | --------------------------------------- | | value | unknown | - | Selected value (or array when multiple) | | onChange | (e: ChangeEvent) => void | - | Callback when selection changes | | options | T[] | - | Array of options | | optionLabel | string | "label" | Property name for option label | | optionValue | string | "value" | Property name for option value | | optionDisabled | string | - | Property name for disabled flag | | multiple | boolean | false | Allow multiple selection | | itemTemplate | (option) => ReactNode | - | Custom render for each option | | disabled | boolean | false | Disable the whole component | | invalid | boolean | false | Validation invalid state | | className | string | - | Additional CSS class | | data-testid | string | - | Test ID for root and option buttons |
Tabs
A flexible tabs component for organizing content into multiple panels.
import { Tabs, TabItem } from '@emporix/cockpit-component-library'
const tabs: TabItem[] = [
{
id: 'tab1',
label: 'First Tab',
content: <div>First tab content</div>,
},
{
id: 'tab2',
label: 'Second Tab',
content: <div>Second tab content</div>,
},
]
// Basic usage
<Tabs
tabs={tabs}
activeTabId="tab1"
onTabChange={(tabId) => setActiveTab(tabId)}
/>
// With custom styling and test ID
<Tabs
tabs={tabs}
activeTabId="tab1"
onTabChange={(tabId) => setActiveTab(tabId)}
className="custom-tabs"
data-testid="my-tabs"
/>Props
| Prop | Type | Default | Description | | ----------- | ----------------------- | ------- | ------------------------------ | | tabs | TabItem[] | - | Array of tab items | | activeTabId | string | - | ID of the currently active tab | | onTabChange | (tabId: string) => void | - | Callback when tab is changed | | className | string | - | Additional CSS class | | data-testid | string | - | Test ID for testing |
TabItem Interface
| Prop | Type | Description | | ------- | --------- | ------------------------------ | | id | string | Unique identifier for the tab | | label | string | Display label for the tab | | content | ReactNode | Content to display when active |
SecondaryButton
Secondary action button variant with API similar to PrimaryButton.
import { SecondaryButton } from '@emporix/cockpit-component-library'
;<SecondaryButton label='Cancel' />TertiaryButton
Low-emphasis button for tertiary actions.
import { TertiaryButton } from '@emporix/cockpit-component-library'
;<TertiaryButton label='Learn more' />ContrastButton
Button variant designed for high-contrast contexts.
import { ContrastButton } from '@emporix/cockpit-component-library'
;<ContrastButton label='Open' />Dropdown
Single-select dropdown component with optional filtering, editable mode, and templating.
import { Dropdown } from '@emporix/cockpit-component-library'
;<Dropdown options={options} value={value} onChange={e => setValue(e.value)} />MultiSelect
Multi-value selection component for choosing multiple options from a list.
import { MultiSelect } from '@emporix/cockpit-component-library'
;<MultiSelect
options={options}
value={value}
onChange={e => setValue(e.value)}
/>RichTextEditor
Rich text input component for formatted content editing.
import { RichTextEditor } from '@emporix/cockpit-component-library'
;<RichTextEditor value={value} onChange={setValue} />Spinner
Loading indicator component with configurable size.
import { Spinner } from '@emporix/cockpit-component-library'
;<Spinner size='md' />Breadcrumb
Navigation breadcrumb for representing hierarchical page location.
import { Breadcrumb } from '@emporix/cockpit-component-library'
;<Breadcrumb items={items} />IconCard
Card-like container with a prominent icon/content layout.
import { IconCard } from '@emporix/cockpit-component-library'
;<IconCard title='Title' description='Description' icon={<span />} />FileUploadDropzone
Dropzone component for drag-and-drop file uploads with type constraints.
import { FileUploadDropzone } from '@emporix/cockpit-component-library'
;<FileUploadDropzone acceptedTypes={['image']} onFilesSelected={handleFiles} />AlertBox
Inline alert component for success, info, warning, and error messages.
import { AlertBox } from '@emporix/cockpit-component-library'
;<AlertBox variant='info' title='Heads up' />SideMenu
Composable side navigation menu with brand, nav items, and footer slots.
import { SideMenu } from '@emporix/cockpit-component-library'
;<SideMenu>{/* ... */}</SideMenu>TopNav
Top navigation/header component for page-level actions and context.
import { TopNav } from '@emporix/cockpit-component-library'
;<TopNav title='Dashboard' />Toast
Toast notification primitives: Toast, ToastProvider, and useToast.
import { ToastProvider, useToast } from '@emporix/cockpit-component-library'Modal
Dialog modal component for focused workflows and confirmations.
import { Modal } from '@emporix/cockpit-component-library'
;<Modal isOpen={open} onClose={onClose} title='Edit item' />ConfirmDialog
Preset confirmation dialog for destructive or high-impact actions.
import { ConfirmDialog } from '@emporix/cockpit-component-library'
;<ConfirmDialog open={open} onConfirm={onConfirm} onCancel={onCancel} />ProductTable
Data table component focused on product records and common actions.
import { ProductTable } from '@emporix/cockpit-component-library'
;<ProductTable items={items} columns={columns} />ProductTableIconAddButton
Compact icon button variant used with ProductTable add flows.
import { ProductTableIconAddButton } from '@emporix/cockpit-component-library'
;<ProductTableIconAddButton onClick={onAdd} />OrderTable
Data table for order rows and order-oriented workflows.
import { OrderTable } from '@emporix/cockpit-component-library'
;<OrderTable rows={rows} />OrderStatusTag
Status badge component for order state presentation.
import { OrderStatusTag } from '@emporix/cockpit-component-library'
;<OrderStatusTag status='submitted' />Wizard
Step-based workflow component for multi-stage forms and processes.
import { Wizard } from '@emporix/cockpit-component-library'
;<Wizard steps={steps} />Timeline
Chronological event list component for activity/history views.
import { Timeline } from '@emporix/cockpit-component-library'
;<Timeline events={events} />Theming with CSS Variables
The component library uses CSS variables for easy theming. You can customize the appearance by overriding these variables:
:root {
--color-primary: #your-primary-color;
--color-primary-hover: #your-primary-hover-color;
--border-radius-md: 0.5rem;
--font-size-lg: 1.2rem;
/* ... other variables */
}Available CSS Variables
The component library provides a minimal set of CSS variables that are actually used in the codebase. These variables are primarily used for the showcase page styling and can be customized for theming.
Colors
--color-primary: #0e99c6;
--color-primary-hover: #0e87b3;
--color-dark: #27313b;Text Colors
--color-text-primary: #ffffff;
--color-text-secondary: #999999;Background Colors
--color-bg-primary: #ffffff;
--color-bg-secondary: #f8f9fa;Border Colors
--color-border-light: #cad0d6;Focus Ring
Reusable focus outline for inputs, dropdowns, SelectButton, etc. Override to change focus appearance globally.
--focus-ring-box-shadow: 0 0 0 2px rgba(38, 101, 183, 0.35);Chip (status badge)
Background and text colors per variant for the Chip component.
--color-chip-success-bg: #ecfdf5;
--color-chip-success-text: #059669;
--color-chip-success-border: #a7f3d0;
--color-chip-warning-bg: #fffbeb;
--color-chip-warning-text: #d97706;
--color-chip-warning-border: #fde68a;
--color-chip-info-bg: #eff6ff;
--color-chip-info-text: #2563eb;
--color-chip-info-border: #bfdbfe;
--color-chip-error-bg: #fef2f2;
--color-chip-error-text: #dc2626;
--color-chip-error-border: #fecaca;Card layout tokens
Used by Card (Card.module.scss). Defaults match src/styles/index.scss; override on :root or a wrapper to change card rhythm app-wide.
--color-card-border: #e5e7eb;
--color-card-heading: #1f2937;
--color-card-meta-icon: #4b5563;
--card-radius: 8px;
--card-padding: 16px;
--card-section-gap: 12px; /* root: space between top-level sections (e.g. body vs footer) */
--card-body-gap: 16px; /* Card.Body: space between title, meta, etc. */
--card-meta-gap: 6px; /* header / meta / meta-row gaps */Note: IconCard uses 24px padding and var(--card-body-gap) for its content column—slightly different from Card’s shell; see IconCard.module.scss if you theme both.
Spacing
--spacing-sm: 0.5rem; /* 8px */
--spacing-md: 1rem; /* 16px */
--spacing-lg: 1.5rem; /* 24px */
--spacing-xl: 3rem; /* 48px */Border Radius
--border-radius-sm: 0.25rem; /* 4px */
--border-radius-md: 0.375rem; /* 6px */
--border-radius-lg: 0.5rem; /* 8px */Typography
Font Sizes:
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 0.875rem; /* 14px */
--font-size-lg: 1.125rem; /* 18px */Font Weights:
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;Line Height:
--line-height-base: 1rem;Theming Examples
Basic Theming
:root {
--color-primary: #your-brand-color;
--color-primary-hover: #your-brand-hover-color;
--border-radius-md: 0.5rem;
--font-size-lg: 1.2rem;
}Dark Theme
:root {
--color-bg-primary: #1a1a1a;
--color-bg-secondary: #2d2d2d;
--color-text-primary: #ffffff;
--color-text-secondary: #cccccc;
--color-border-light: #404040;
}Custom Component Styling
.my-custom-component {
padding: var(--spacing-md);
border-radius: var(--border-radius-lg);
background-color: var(--color-bg-primary);
color: var(--color-text-primary);
border: 1px solid var(--color-border-light);
}Development
Prerequisites
- Node.js 18+
- npm or yarn
Setup
# Clone the repository
git clone <repository-url>
cd cockpit-component-library
# Install dependencies
npm install
# Start development server
npm run dev
# Start Storybook
npm run storybookAvailable Scripts
npm run dev- Start development server with showcasenpm run build- Build the development versionnpm run build:lib- Build the library for distributionnpm run test- Run testsnpm run test:ui- Run tests with UInpm run test:coverage- Run tests with coveragenpm run storybook- Start Storybooknpm run build-storybook- Build Storybooknpm run lint- Run ESLintnpm run lint:fix- Fix ESLint issuesnpm run type-check- Run TypeScript type checkingnpm run format- Format code with Prettiernpm run format:check- Check code formatting
Testing
The library includes comprehensive tests using Vitest and React Testing Library.
# Run all tests
npm test
# Run tests with coverage
npm run test:coverage
# Run tests with UI
npm run test:uiStorybook
Interactive component documentation is available in Storybook.
# Start Storybook
npm run storybook
# Build Storybook
npm run build-storybookBuilding for Distribution
The library is built using Vite with optimized settings for npm distribution.
# Build the library locally
npm run build:libThe build output includes:
- ES modules (
dist/index.es.js) - UMD bundle (
dist/index.umd.js) - TypeScript declarations (
dist/index.d.ts) - CSS styles (
dist/style.css)
The publish workflow runs npm run build:lib before npm publish.
Publishing a new version
Releases are published to npm automatically when a version tag is pushed to GitHub. The workflow is defined in .github/workflows/publish.yml.
Prerequisites (one-time setup)
npm package —
@emporix/cockpit-component-librarymust exist on npm with publish access configured for this repository.npm Trusted Publishing — On npmjs.com, configure a trusted publisher for this package with:
- GitHub organization/repository:
emporix/cockpit-component-library - Workflow filename:
publish.yml - Environment: (optional)
The workflow uses OIDC (
id-token: write); noNPM_TOKENsecret is required when Trusted Publishing is enabled.- GitHub organization/repository:
Release steps
Merge your changes to the default branch (
main).Bump the version in
package.json(semver, without thevprefix), for example1.0.4or2.1.1.Commit the version bump and push to
main.Create and push a Git tag that matches the version with a
vprefix:git tag v1.0.4 git push origin v1.0.4Open Actions in GitHub and confirm the Publish to npm workflow succeeded.
Verify the new version on npm:
npm view @emporix/cockpit-component-library version
The tag must match package.json version (e.g. tag v2.1.1 requires "version": "2.1.1") and follow the format v{major}.{minor}.{patch}.
Example: publish 2.1.1
# After updating package.json to "2.1.1" and pushing main:
git tag v2.1.1
git push origin v2.1.1To remove a tag locally before pushing:
git tag -d v2.1.1Code Formatting
This project uses Prettier for consistent code formatting. The configuration is defined in .prettierrc.
# Format all files
npm run format
# Check if files are formatted correctly
npm run format:checkThe project also includes pre-commit hooks that automatically format staged files before commits.
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Guidelines
- Follow TypeScript best practices
- Write tests for new components
- Add Storybook stories for components
- Follow the existing code style
- Update documentation as needed
- Use arrow functions for React components (enforced by ESLint)
- Avoid using
React.FCtype annotation (redundant with modern TypeScript) - Use CSS variables for all styling values
- Follow the established naming conventions for CSS classes
Troubleshooting
Import Issues
If you encounter import errors like "Failed to resolve entry for package", ensure:
- The package is properly installed:
npm install @emporix/cockpit-component-library - You're using the correct import syntax:
import { PrimaryButton } from '@emporix/cockpit-component-library' import '@emporix/cockpit-component-library/styles' - Your bundler supports ES modules and the package.json exports field
Styling Issues
If the PrimaryButton component appears unstyled or you get "styles not found" errors:
Try Method 1 (Recommended): Import styles in your main CSS file:
@import '@emporix/cockpit-component-library/styles';Try Method 2: Import styles in your main JavaScript/TypeScript file:
import '@emporix/cockpit-component-library/styles'If both methods fail: Copy the CSS file manually:
- Copy
node_modules/@emporix/cockpit-component-library/dist/style.cssto your project - Import it in your main CSS file:
@import './path/to/style.css';
- Copy
Check your bundler configuration: Ensure your bundler (Vite, Webpack, etc.) is configured to handle CSS imports from node_modules
Verify the CSS classes: The component uses SCSS modules with hashed class names (e.g.,
_primaryButton_1d0ha_1,_primaryButtonDisabled_1d0ha_53) and CSS variables for theming
TypeScript Issues
If TypeScript can't find types, make sure:
- The
@types/reactpackage is installed - Your
tsconfig.jsonincludes the library innode_modules
Bundler-Specific Notes
Vite
All methods should work with Vite. Method 1 (CSS import) is recommended.
Webpack
Method 1 and Method 2 should work. If you encounter issues, try Method 3.
Create React App
Method 1 and Method 2 should work. If you encounter issues, try Method 3.
Next.js
Method 1 (CSS import) is recommended. Import the CSS in your _app.tsx or _app.js file.
License
MIT License - see LICENSE file for details.
Support
For support and questions, please open an issue in the repository.
