Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6034d14
fix: DataTable accessibility issues
Feb 23, 2026
a02a0a2
fix: update role for Sort Descending button in DataTable e2e test
Feb 23, 2026
b929b10
Merge pull request #3042 from epam/fix/2992-data-table-accessibility
MSt1ch Feb 25, 2026
51c5500
fix: Tooltip doesn't show on keyboard focus (#3043)
i-runets Feb 25, 2026
e6cd38f
fix: errors in DataTable Property Explorer tab (#3044)
i-runets Feb 26, 2026
b716d30
fix: prevent DropdownMenuSwitchButton from calling onValueChange mult…
Feb 27, 2026
b1187fc
Merge pull request #3046 from epam/fix/3045-DropdownMenuSwitchButton-…
i-runets Mar 3, 2026
647709a
Add Cursor AI integration: AGENTS.md and skills (#3047)
i-runets Mar 4, 2026
34fd781
feat: [FiltersPanel] Add support of `min`/`max`/`step` props at `nume…
cpoftea Mar 4, 2026
a3378a7
fix: prevent Dropdown/Tooltip from flipping to orthogonal placements …
i-runets Mar 5, 2026
aa15bb6
Merge pull request #3051 from cpoftea/table-filters
cpoftea Mar 6, 2026
1be85c2
fix(DataTable): improve column header accessibility (#3048, #3049) (#…
i-runets Mar 10, 2026
ba9e222
fix(Dropdown): stale closure in onValueChange and onClose callbacks (…
i-runets Mar 11, 2026
79af5b4
feat: [Promo] Add '18' size option to IconButton component and update…
MSt1ch Mar 11, 2026
ed3dc49
fix: add aria-selected to VerticalTabButton for screen reader (#2742)
JuliaMV Mar 11, 2026
aa436d1
Merge pull request #3056 from epam/fix/2742-vertical-tab-button-aria-…
JuliaMV Mar 12, 2026
890efbb
fix(searchToQuery): fix double decoding in searchToQuery (#3059)
i-runets Mar 16, 2026
a91dc73
fix: align Time Picker dropdown time format with input field (#2910)
JuliaMV Mar 16, 2026
45e4cd0
fix(useVirtualList): fix scroll jump when visible range changes (#286…
i-runets Mar 17, 2026
125e550
Merge pull request #3060 from epam/fix/2910-time-picker-leading-zeros
JuliaMV Mar 18, 2026
916ac01
feat(DataRowOptions): add reserveSpace property for checkbox configur…
i-runets Mar 23, 2026
9695cc1
fix(DatePicker): preserve value on blur when using dddd format (#2560…
JuliaMV Mar 23, 2026
e3cb59c
fix(FiltersPanel): fix incorrect alignment of filters inside dropdown…
i-runets Mar 23, 2026
598b629
fix: remove nonce for dev server (#3070)
i-runets Mar 23, 2026
7e6e97b
fix(VirtualList): loading Blocker not fully covering visible scroll a…
i-runets Mar 24, 2026
f4a84dd
fix: empty status message in Picker is not announced by screen reader…
i-runets Mar 27, 2026
e489578
chore: update changelog (#3077)
JuliaMV Apr 1, 2026
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
348 changes: 348 additions & 0 deletions .cursor/skills/components/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
---
name: uui-components
description: Helps create and modify UUI (EPAM Unified UI) components following established patterns. Use when creating new components, modifying existing components, working with withMods, component props, styling, or component architecture in the UUI library.
---

# UUI Components Skill

## Package Structure

- **`@epam/uui-components`** — Legacy package with logic-only components (no visual styles). Contains older components.
- **`@epam/uui`** — Package for ready-to-use, themeable components. **All new components must be created here** in the `uui/` folder.

When creating new components, add them to `uui/components/`, not to `uui-components/`.

## File Path Conventions

Components are organized by category in `uui/components/`:

| Category | Path | Examples |
|----------|------|----------|
| buttons | `uui/components/buttons/` | Button, IconButton, LinkButton |
| inputs | `uui/components/inputs/` | Checkbox, TextInput, NumericInput, Switch |
| typography | `uui/components/typography/` | Text, TextPlaceholder, RichTextView |
| overlays | `uui/components/overlays/` | Modal, Tooltip, DropdownMenu |
| layout | `uui/components/layout/` | FlexRow, FlexCell, Blocker |
| pickers | `uui/components/pickers/` | PickerInput, PickerModal |
| datePickers | `uui/components/datePickers/` | DatePicker, RangeDatePicker |
| dnd | `uui/components/dnd/` | Drag and drop |
| widgets | `uui/components/widgets/` | Tag, Badge, Spinner |
| tables | `uui/components/tables/` | DataTable |
| filters | `uui/components/filters/` | FiltersPanel |
| navigation | `uui/components/navigation/` | MainMenu, TabButton |
| forms | `uui/components/forms/` | Form components |
| fileUpload | `uui/components/fileUpload/` | File upload |
| errors | `uui/components/errors/` | Error handling |

Each component typically has:
- `ComponentName.tsx` — Component implementation
- `ComponentName.module.scss` — Styles (SCSS modules)
- `__tests__/ComponentName.test.tsx` — Unit tests

## Component Creation Workflows

### Workflow 1: Wrapping Existing `uui-components` Component

When wrapping a logic-only component from `@epam/uui-components`:

**Example: IconButton**

```typescript
import * as uuiComponents from '@epam/uui-components';
import { withMods, Overwrite } from '@epam/uui-core';
import { settings } from '../../settings';
import css from './IconButton.module.scss';

// 1. Define mods type for themeable properties
interface IconButtonMods {
color?: 'info' | 'success' | 'error' | 'primary' | 'accent' | 'critical' | 'warning' | 'secondary' | 'neutral' | 'white';
size?: '18' | '24' | '30' | '36';
}

export interface IconButtonModsOverride {}

// 2. Create props interface extending core props
export type IconButtonCoreProps = Omit<uuiComponents.IconButtonProps, 'size'>;
export interface IconButtonProps extends IconButtonCoreProps, Overwrite<IconButtonMods, IconButtonModsOverride> {}

// 3. Create applyMods function that returns CSS classes
function applyIconButtonMods(props: IconButtonProps) {
return [
'uui-icon_button',
`uui-color-${props.color || 'neutral'}`,
css.root, // SCSS module class
];
}

// 4. Wrap with withMods HOC
export const IconButton = withMods<uuiComponents.IconButtonProps, IconButtonProps>(
uuiComponents.IconButton, // Core component
applyIconButtonMods, // Mods function
(props) => {
// Optional: transform props or add defaults
return {
dropdownIcon: props.dropdownIcon || settings.iconButton.icons.dropdownIcon,
};
},
);
```

**Key points:**
- `withMods<CoreProps, Props>(coreComponent, applyMods, propsTransformer?)`
- `applyMods` returns array of CSS class strings
- Third parameter is optional function to transform/inject props
- Use `Overwrite<Mods, ModsOverride>` pattern for extensibility

### Workflow 2: Brand-New Component (No `uui-components` Counterpart)

When creating a component from scratch:

**Example: TextPlaceholder**

```typescript
import * as React from 'react';
import cx from 'classnames';
import { IHasCX, IHasRawProps } from '@epam/uui-core';
import css from './TextPlaceholder.module.scss';
import { PropsWithChildren } from 'react';

// 1. Define props interface
export interface ITextPlaceholderProps extends IHasRawProps<React.HTMLAttributes<HTMLDivElement>>, IHasCX {
wordsCount?: number;
isNotAnimated?: boolean;
}

export type TextPlaceholderProps = PropsWithChildren<ITextPlaceholderProps>;

// 2. Implement component directly
export const TextPlaceholder: React.FunctionComponent<TextPlaceholderProps> = (props) => {
// Component logic here
return (
<div className={cx(css.root, 'uui-text-placeholder')} {...props.rawProps}>
{/* Component JSX */}
</div>
);
};
```

**Key points:**
- Implement directly without `withMods` if not themeable
- Use `IHasCX` and `IHasRawProps` from `@epam/uui-core` for standard props
- Use `cx` (classnames) for conditional classes
- Use SCSS modules for styling

## Styling Patterns

### SCSS Modules

Components use SCSS modules (`.module.scss`):

```scss
// IconButton.module.scss
.root {
display: inline-flex;
align-items: center;
// Styles here
}
```

Import in component:
```typescript
import css from './IconButton.module.scss';
```

### Mods to CSS Classes

The `applyMods` function maps props to CSS classes:

```typescript
function applyButtonMods(mods: ButtonProps) {
return [
css.root, // SCSS module class
'uui-button', // Global UUI class
`uui-fill-${mods.fill || 'solid'}`, // Dynamic class from prop
`uui-color-${mods.color || 'primary'}`,
`uui-size-${mods.size || settings.button.sizes.default}`,
];
}
```

**Patterns:**
- Use `css.root` for component-specific styles
- Use `uui-*` prefixed classes for themeable utilities
- Use `settings.componentName.*` for default values
- Return array of class strings (will be joined by `withMods`)

## Export Pattern

### Component-Level Index

Each category folder has an `index.ts` that re-exports components:

```typescript
// uui/components/buttons/index.ts
export * from './Button';
export * from './IconButton';
export * from './LinkButton';
```

### Root Index

Main `uui/components/index.ts` re-exports all categories:

```typescript
// uui/components/index.ts
export * from './buttons';
export * from './inputs';
export * from './typography';
// ... etc
```

**When adding a new component:**
1. Export from component file: `export const MyComponent = ...`
2. Add to category `index.ts`: `export * from './MyComponent';`
3. If new category, add to root `index.ts`: `export * from './newCategory';`

## Common Patterns

### cx Prop (IHasCX)

Most components extend `IHasCX`, which adds a `cx` prop for consumer styling. Always merge `props.cx` into the root element's classNames so consumers can pass custom classes:

```typescript
import cx from 'classnames';

// With withMods — withMods merges cx automatically
// Manual mods:
<div className={cx(applyTagMods(props), props.cx)}>
<Clickable cx={cx(applyTagMods(props), props.cx)} {...props} />
```

### Using Settings

The `settings` object (`uui/settings.tsx`) provides theme-specific defaults: icons, sizes, placeholders. Structure: `settings.<component>.<category>.<key>`.

**Access:**
```typescript
import { settings } from '../../settings';

// Icons
dropdownIcon: props.dropdownIcon || settings.iconButton.icons.dropdownIcon,
// Sizes
size: props.size || settings.button.sizes.default,
```

**Structure:** Each component has nested objects (e.g. `icons`, `sizes`). Themes override via `PartialSettings` — see skin packages (loveship, epam-electric, epam-promo) for how settings are merged. To add new component settings, add a section in `uui/settings.tsx` following existing patterns (e.g. `accordionSettings`, `buttonSettings`).

### Props Transformation

The third parameter of `withMods` can transform props:

```typescript
export const TextInput = withMods<CoreTextInputProps, TextInputProps>(
uuiTextInput,
applyTextInputMods,
() => ({
acceptIcon: settings.textInput.icons.acceptIcon,
cancelIcon: settings.textInput.icons.clearIcon,
dropdownIcon: settings.textInput.icons.dropdownIcon,
}),
);
```

### Conditional Mods

Apply mods conditionally in `applyMods`:

```typescript
function applyCheckboxMods(mods: CheckboxMods) {
return [
css.root,
`uui-size-${mods.size || settings.checkbox.sizes.default}`,
'uui-control-mode-' + (mods.mode || 'form'),
'uui-color-primary',
];
}
```

## Additional Patterns

### Using `forwardRef`

Many components expose refs via `React.forwardRef`. Use when consumers need direct DOM access (e.g. focus management, measurements):

```typescript
import React from 'react';
import { withMods } from '@epam/uui-core';
import * as uuiComponents from '@epam/uui-components';

// withMods works with forwardRef components automatically.
// If the core component uses forwardRef, the wrapped component inherits it.
export const Switch = withMods<uuiComponents.SwitchProps, SwitchProps>(uuiComponents.Switch, applySwitchMods);
```

For brand-new components without `withMods`:

```typescript
export const Tag = React.forwardRef<HTMLElement, TagProps>((props, ref) => {
return (
<div ref={ ref } className={ cx(applyTagMods(props), props.cx) }>
{/* ... */}
</div>
);
});
```

### Manual Mods (without `withMods`)

Some components apply mods manually instead of using `withMods`. This is common when wrapping primitives like `Clickable` with `forwardRef`:

```typescript
export const Tag = React.forwardRef<HTMLElement, TagProps>((props, ref) => {
return (
<Clickable
cx={ cx(applyTagMods(props), props.cx) }
ref={ ref }
{ ...props }
/>
);
});

function applyTagMods(props: TagProps) {
return [
css.root,
`uui-size-${props.size || '36'}`,
`uui-color-${props.color || 'neutral'}`,
];
}
```

See `uui/components/widgets/Tag.tsx`, `uui/components/layout/FlexRow.tsx` for examples.

### Data-Driven Components (DataTable, PickerInput)

Components like DataTable, PickerInput, PickerModal, and FiltersPanel use DataSource from `@epam/uui-core`. See [.cursor/skills/data-sources/SKILL.md](../data-sources/SKILL.md) for DataSource usage.

### Deprecation Warnings

Use `devLogger` to warn about deprecated prop values:

```typescript
import { devLogger } from '@epam/uui-core';

devLogger.warnAboutDeprecatedPropValue<IconButtonProps, 'color'>({
component: 'IconButton',
propName: 'color',
propValue: props.color,
condition: () => ['info', 'success'].includes(props.color),
message: "'info' and 'success' colors are deprecated. Use 'primary' instead.",
});
```

## References

- Example wrapping component: `uui/components/buttons/IconButton.tsx`
- Example brand-new component: `uui/components/typography/TextPlaceholder.tsx`
- Example with complex mods: `uui/components/inputs/TextInput.tsx`
- Example with props transformation: `uui/components/buttons/Button.tsx`
- Example with forwardRef + manual mods: `uui/components/widgets/Tag.tsx`
Loading
Loading