@emporix/component-library
v1.8.2
Published
A React component library for Emporix projects
Readme
Emporix 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 and publishing pipeline
- 🎨 CSS Variables: Global theming system with CSS custom properties
Quick Start
Installation
npm install @emporix/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/component-library/styles';import { PrimaryButton } from '@emporix/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/component-library'
import '@emporix/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/component-library/dist/style.css to your project and import it directly.
Components
PrimaryButton
A simple, focused button component for primary actions.
import { PrimaryButton } from '@emporix/component-library'
// Basic usage
<PrimaryButton>Click me</PrimaryButton>
// With disabled state
<PrimaryButton disabled>Disabled Button</PrimaryButton>
// With click handler
<PrimaryButton onClick={() => alert('Confirmed!')}>
Confirm Action
</PrimaryButton>Props
| Prop | Type | Default | Description | | ----------- | ---------- | ------- | -------------------------- | | children | ReactNode | - | Button content | | disabled | boolean | false | Whether button is disabled | | onClick | () => void | - | Click handler | | className | string | - | Additional CSS class | | data-testid | string | - | Test ID for testing |
InputText
A text input with optional label and tooltip. Hover and focus styles match the Dropdown component.
import { InputText } from '@emporix/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" />
// With validation error
<InputText label="Email" required error="Please enter a valid email address" />
// Disabled
<InputText label="Id" placeholder="Enter id" disabled />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) |
| error | ReactNode | - | Error text displayed below the input |
| 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 (suffix -input) |
All standard HTML input attributes (placeholder, value, onChange, etc.) are supported.
Dropdown
A select-style dropdown for choosing one or more items from a list. Supports optional filter/search, editable input, label, required indicator, and validation errors. Hover and focus styles match the InputText component. API inspired by PrimeReact Dropdown.
import { Dropdown } from '@emporix/component-library'
const options = [
{ label: 'GBP - British Pound Sterling', value: 'GBP' },
{ label: 'EUR - Euro', value: 'EUR' },
{ label: 'USD - US Dollar', value: 'USD' },
]
// With label, filter, and required
<Dropdown
value={currency}
onChange={e => setCurrency(e.value)}
options={options}
optionLabel="label"
optionValue="value"
placeholder="Select a currency"
label="Preferred Currency"
required
filter
/>
// With validation error (same styling as InputText)
<Dropdown
value={currency}
onChange={e => setCurrency(e.value)}
options={options}
placeholder="Select a currency"
label="Preferred Currency"
required
error="Currency is required"
/>
// Multiple selection
<Dropdown
value={currencies}
onChange={e => setCurrencies(Array.isArray(e.value) ? e.value : [])}
options={options}
placeholder="Select currencies"
label="Currencies"
multiple
filter
/>Props
| Prop | Type | Default | Description |
| -------------- | ------------------------- | ---------- | --------------------------------------------- |
| value | unknown | - | Selected value (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 on options |
| placeholder | string | "Select" | Placeholder when no value selected |
| filter | boolean | false | Enable search filter in the overlay |
| editable | boolean | false | Allow typing in the trigger to filter/select |
| onEditEnd | (e: EditEndEvent) => void | - | Fired when Enter is pressed in editable mode |
| multiple | boolean | false | Allow multiple selection |
| label | ReactNode | - | Label text above the trigger |
| error | ReactNode | - | Error text displayed below the trigger |
| required | boolean | false | Shows asterisk on label |
| inputId | string | - | id for the trigger (for label association) |
| disabled | boolean | false | Whether the dropdown is disabled |
| valueTemplate | (option) => ReactNode | - | Custom render for selected value in trigger |
| itemTemplate | (option) => ReactNode | - | Custom render for each option in the list |
| className | string | - | Additional CSS class for the root |
| panelClassName | string | - | Additional CSS class for the overlay panel |
| data-testid | string | - | Test ID for root, trigger, panel, and options |
SelectButton
Choose single or multiple options using buttons. API inspired by PrimeReact SelectButton. Selected state uses project primary colors.
import { SelectButton } from '@emporix/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/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 |
Tooltip
Floating overlays on hover or focus, similar to PrimeReact Tooltip. Use children mode to wrap a button, input, or other component, or selector mode with a CSS target and data-pr-tooltip / data-pr-position on matching elements.
import { Tooltip } from '@emporix/component-library'
// Wrap a control (content prop required)
<Tooltip content="Save your changes" position="top">
<PrimaryButton>Save</PrimaryButton>
</Tooltip>
// Attach to existing markup — targets use data attributes
<Tooltip target=".my-help-icon" />
<i
className="my-help-icon pi pi-question-circle"
data-pr-tooltip="Extra help"
data-pr-position="right"
/>
// Reactive label while open
<Tooltip content={saveHint} position="bottom">
<PrimaryButton onClick={() => setSaveHint('Saved')}>Save</PrimaryButton>
</Tooltip>Props
| Prop | Type | Default | Description |
| -------------- | ------------------------------------------------- | ------- | --------------------------------------------------------------------------- |
| target | string | - | CSS selector for anchor elements; when set, children is ignored |
| content | ReactNode | - | Tooltip body; required for children mode; fallback for selector mode |
| position | top | bottom | left | right | mouse | top | Placement; mouse follows the pointer |
| event | hover | focus | both | hover | How the tooltip is triggered |
| showDelay | number | 0 | Delay before show (ms) |
| hideDelay | number | 0 | Delay before hide (ms) |
| autoHide | boolean | true | When false, pointer can move to the tooltip without hiding it |
| mouseTrack | boolean | false | While visible, position tracks the pointer |
| mouseTrackTop | number | 0 | Vertical offset when using mouse track |
| mouseTrackLeft | number | 0 | Horizontal offset when using mouse track |
| disabled | boolean | false | Disable the tooltip |
| className | string | - | Extra class on the anchor wrapper (children mode) or merged on single child |
| panelClassName | string | - | Extra class on the floating panel |
| id | string | - | Optional id for the tooltip (aria-describedby on the anchor) |
| children | ReactNode | - | Single element recommended; multiple nodes are wrapped in an inline span |
| data-testid | string | - | Adds data-testid on the panel as {id}-panel |
Selector targets can override position per element with data-pr-position. Escape closes the tooltip.
Accordion
Collapsible content panels grouped in tabs.
import { Accordion, AccordionTab } from '@emporix/cockpit-component-library'
;<Accordion activeIndex={0}>
<AccordionTab header='Header I'>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
</p>
</AccordionTab>
<AccordionTab header='Header II'>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem.</p>
</AccordionTab>
<AccordionTab header='Header III' disabled />
</Accordion>Multiple (multiple tabs open)
<Accordion multiple activeIndex={[0, 2]}>
<AccordionTab header='Header I'>Content 1</AccordionTab>
<AccordionTab header='Header II'>Content 2</AccordionTab>
<AccordionTab header='Header III'>Content 3</AccordionTab>
</Accordion>Controlled
import { useState } from 'react'
const [activeIndex, setActiveIndex] = useState<number[]>([0])
<Accordion
multiple
activeIndex={activeIndex}
onTabChange={e => setActiveIndex(e.index)}
>
<AccordionTab header="Header I">Content 1</AccordionTab>
<AccordionTab header="Header II">Content 2</AccordionTab>
</Accordion>Props
| Prop | Type | Default | Description | | ----------------- | ---------------------------------------------------- | -------- | --------------------------------------------------- | ---- | ------------------------------------- | | activeIndex | number | number[] | null | null | Index (or indexes) of expanded tab(s) | | multiple | boolean | false | Allows multiple tabs to be expanded | | expandIcon | ReactNode | - | Icon shown when a tab is collapsed | | collapseIcon | ReactNode | - | Icon shown when a tab is expanded | | onTabChange | (e: { index: number[]; originalEvent: any }) => void | - | Fired when the expanded tab(s) change | | onTabOpen | (e: { index: number; originalEvent: any }) => void | - | Fired when a tab is opened | | onTabClose | (e: { index: number; originalEvent: any }) => void | - | Fired when a tab is closed | | transitionOptions | object | - | Configures the panel open/close transition | | unstyled | boolean | false | When true, removes component styling (if supported) |
AccordionTab
Defines a single accordion tab.
<Accordion activeIndex={0}>
<AccordionTab header='Header I'>
<div>Tab content</div>
</AccordionTab>
</Accordion>Props
| Prop | Type | Default | Description | | -------- | --------- | ------- | ------------------------------------------ | | header | ReactNode | - | Header content (can be any React node) | | disabled | boolean | false | Disables clicking/toggling this tab | | children | ReactNode | - | Content to render when the tab is expanded |
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: #007bff;
--color-primary-hover: #0056b3;
--color-dark: #343a40;Text Colors
--color-text-primary: #212529;
--color-text-secondary: #6c757d;Background Colors
--color-bg-primary: #ffffff;
--color-bg-secondary: #f8f9fa;Border Colors
--color-border-light: #e9ecef;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);Tooltip
--color-tooltip-bg: #3e4759;
--color-tooltip-text: #ffffff;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 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
npm run build:libThe build output includes:
- ES modules (
index.es.js) - UMD bundle (
index.umd.js) - TypeScript declarations (
index.d.ts) - CSS styles (
styles.css)
Code 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/component-library - You're using the correct import syntax:
import { PrimaryButton } from '@emporix/component-library' import '@emporix/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/component-library/styles';Try Method 2: Import styles in your main JavaScript/TypeScript file:
import '@emporix/component-library/styles'If both methods fail: Copy the CSS file manually:
- Copy
node_modules/@emporix/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.
