diff --git a/.cursor/skills/components/SKILL.md b/.cursor/skills/components/SKILL.md new file mode 100644 index 0000000000..c56e6937fe --- /dev/null +++ b/.cursor/skills/components/SKILL.md @@ -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; +export interface IconButtonProps extends IconButtonCoreProps, Overwrite {} + +// 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.IconButton, // Core component + applyIconButtonMods, // Mods function + (props) => { + // Optional: transform props or add defaults + return { + dropdownIcon: props.dropdownIcon || settings.iconButton.icons.dropdownIcon, + }; + }, +); +``` + +**Key points:** +- `withMods(coreComponent, applyMods, propsTransformer?)` +- `applyMods` returns array of CSS class strings +- Third parameter is optional function to transform/inject props +- Use `Overwrite` 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>, IHasCX { + wordsCount?: number; + isNotAnimated?: boolean; +} + +export type TextPlaceholderProps = PropsWithChildren; + +// 2. Implement component directly +export const TextPlaceholder: React.FunctionComponent = (props) => { + // Component logic here + return ( +
+ {/* Component JSX */} +
+ ); +}; +``` + +**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: +
+ +``` + +### Using Settings + +The `settings` object (`uui/settings.tsx`) provides theme-specific defaults: icons, sizes, placeholders. Structure: `settings...`. + +**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( + 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.Switch, applySwitchMods); +``` + +For brand-new components without `withMods`: + +```typescript +export const Tag = React.forwardRef((props, ref) => { + return ( +
+ {/* ... */} +
+ ); +}); +``` + +### 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((props, ref) => { + return ( + + ); +}); + +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({ + 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` diff --git a/.cursor/skills/data-sources/SKILL.md b/.cursor/skills/data-sources/SKILL.md new file mode 100644 index 0000000000..6014ef9d69 --- /dev/null +++ b/.cursor/skills/data-sources/SKILL.md @@ -0,0 +1,180 @@ +--- +name: uui-data-sources +description: Helps work with UUI DataSources (ArrayDataSource, LazyDataSource, AsyncDataSource) powering PickerInput, DataTable, FiltersPanel, and other data-driven components. Use when implementing or fixing features that load, filter, sort, or display lists of data. +--- + +# UUI Data Sources + +UUI DataSources are the core infrastructure for data-driven components like PickerInput, DataTable, FiltersPanel, and PickerModal. They handle loading, filtering, sorting, selection, and tree hierarchies. + +## Location + +- **Package:** `@epam/uui-core` +- **Path:** `uui-core/src/data/processing/` +- **Types:** `uui-core/src/types/dataSources.ts` + +## DataSource Types + +| Type | Use Case | Data Flow | +|------|----------|-----------| +| **ArrayDataSource** | In-memory, synchronous list. Items already loaded. | Pass `items` array. Filtering/search/sorting done client-side. | +| **LazyDataSource** | Server-driven, lazy loading. Large or remote data. | `api(request, context)` fetches on demand. Supports pagination, search, tree. | +| **AsyncDataSource** | Fetches once via API, then caches. | `api(options)` called once; results cached and reused across views. | + +## Hooks (Recommended Usage) + +Use hooks instead of instantiating classes directly — they handle lifecycle and prop updates: + +```typescript +import { useArrayDataSource, useLazyDataSource, useAsyncDataSource } from '@epam/uui-core'; +``` + +### useArrayDataSource + +For in-memory data: + +```typescript +const dataSource = useArrayDataSource( + { + items: myItems, + getId: (item) => item.id, + getParentId: (item) => item.parentId, // optional, for tree + }, + [myItems] +); +``` + +### useLazyDataSource + +For server-driven lazy loading: + +```typescript +const dataSource = useLazyDataSource( + { + api: (request, context) => { + // request: { filter, sorting, search, range, ids, cursor } + // context: { parentId, parent } for tree children + return myApi.fetchItems(request, context); + }, + getId: (item) => item.id, + getParentId: (item) => item.parentId, + }, + [] +); +``` + +API must return `{ items: TItem[], count?: number, from?: number, cursor?: any }`. + +### useAsyncDataSource + +Fetches once, then caches. Good for dropdowns with fixed options: + +```typescript +const dataSource = useAsyncDataSource( + { + api: (options) => svc.api.demo.countries({}, options).then((r) => r.items), + getId: (item) => item.id, + }, + [] +); +``` + +## useView and DataSourceState + +Components consume DataSources via `useView`: + +```typescript +const [dataSourceState, setDataSourceState] = useState({}); +const view = dataSource.useView(dataSourceState, setDataSourceState); + +const rows = view.getVisibleRows(); +const listProps = view.getListProps(); +view.reload(); +``` + +**DataSourceState** (`uui-core/src/types/dataSources.ts`) includes: +- `search` — search string +- `filter` — filter object (passed to LazyDataSource API) +- `sorting` — sort options +- `checked` — checked item IDs (multi-select) +- `selectedId` — single selected item +- `folded` — tree node fold state +- `page`, `pageSize` — pagination +- `focusedIndex`, `scrollTo` — list position + +**SetDataSourceState** is `(update: (prev) => DataSourceState) => void` — functional update pattern. + +## Key Config Options + +- **getId(item)** — Required. Returns unique ID. +- **getParentId(item)** — Optional. For tree hierarchy. LazyDataSource uses it to load parent chain. +- **complexIds** — Set `true` if IDs are objects/arrays (internally JSON.stringified). +- **rowOptions** / **getRowOptions(item)** — Row-level options (selectable, checkable, editable). +- **cascadeSelection** — `true` | `'explicit'` | `'implicit'` for parent-child selection behavior. +- **isFoldedByDefault(item, state)** — Default fold state for tree nodes. +- **selectAll** — Enable/disable select-all. Default `true`. + +## LazyDataSource API Contract + +```typescript +type LazyDataSourceApi = ( + request: LazyDataSourceApiRequest, + context: LazyDataSourceApiRequestContext +) => Promise>; +``` + +- **Request:** `filter`, `sorting`, `search`, `range: { from, count }`, `ids` (for specific IDs), `cursor` (pagination). +- **Context:** `parentId`, `parent` when loading tree children. +- **Response:** `{ items, count?, from?, cursor?, totalCount? }`. + +## Common Patterns + +### PickerInput with DataSource + +```typescript +const dataSource = useArrayDataSource({ items, getId: (i) => i.id }, [items]); + item.name } + entityName="Item" +/> +``` + +### DataTable with DataSource + +```typescript +const dataSource = useArrayDataSource({ items, getId: (i) => i.id }, [items]); +const [tableState, setTableState] = useState({}); +const view = dataSource.useView(tableState, setTableState); + view.getVisibleRows() } + columns={ columns } + value={ tableState } + onValueChange={ setTableState } +/> +``` + +### Tree with getParentId + +```typescript +const dataSource = useArrayDataSource({ + items, + getId: (i) => i.id, + getParentId: (i) => i.parentId, +}, [items]); +``` + +### LazyDataSource clearCache + +When data changes on the server, call `dataSource.clearCache()` (LazyDataSource only) to force reload. + +## References + +- Data source types: `uui-core/src/types/dataSources.ts` +- ArrayDataSource: `uui-core/src/data/processing/ArrayDataSource.tsx` +- LazyDataSource: `uui-core/src/data/processing/LazyDataSource.tsx` +- AsyncDataSource: `uui-core/src/data/processing/AsyncDataSource.tsx` +- Hooks: `uui-core/src/data/processing/hooks/` +- Examples: `app/src/docs/_examples/dataSources/`, `app/src/docs/_examples/pickerInput/`, `app/src/docs/_examples/tables/` diff --git a/.cursor/skills/documentation/SKILL.md b/.cursor/skills/documentation/SKILL.md new file mode 100644 index 0000000000..af9ca80bf2 --- /dev/null +++ b/.cursor/skills/documentation/SKILL.md @@ -0,0 +1,110 @@ +--- +name: uui-documentation +description: Helps update UUI documentation, add doc examples, configure Property Explorer, and manage component API documentation. Use when adding documentation examples, updating Property Explorer configs, generating API references, working with UUI documentation site, or when adding/removing/modifying public props on component interfaces. +--- + +# UUI Documentation + +UUI documentation is published on [uui.epam.com](https://uui.epam.com/). Sources are in `./app` folder. + +**Dependency:** Property Explorer configs and doc examples use `@epam/uui-docs`, which provides `DocBuilder`, `TDocContext`, `TSkin`, `DocPreviewBuilder`, demo API, and PE editors. The package lives in `uui-docs/`. + +## Add Doc Example + +1. Go to `app/src/docs/_examples` and open/add folder for your component +2. Add example file: `example-name.example.tsx` +3. Add link in page config (`app/src/docs/pages/components/.json`): + ```json + { + "id": "alert", + "name": "Alert", + "parentId": "components", + "examples": [ + { "descriptionPath": "alert-descriptions" }, + { "name": "Basic", "componentPath": "alert/Basic.example.tsx" } + ] + } + ``` +4. If adding new page, register it in `app/src/documents/structure/components.ts` for sidebar menu +5. Open local environment (http://localhost:9009/), navigate to page, add description via RTE field + +## Property Explorer + +Property Explorer lets users interactively test components in different prop variations. Most content is auto-generated from component prop types. + +**Preview vs screenshot tests:** The `preview` function in explorerConfigs defines **Preview pages** for documentation — users can switch props and see changes. Screenshot tests (in [e2e-testing](../e2e-testing/SKILL.md)) use these same previews to capture baseline images. Create previews here for docs; add screenshot test entries in `preview.e2e.ts` to include them in E2E. + +### Add/Update ExplorerConfig + +1. Go to `app/src/docs/explorerConfigs` and find/add config file +2. Use same id as page config where PE should be connected +3. Add contexts: + ```typescript + contexts: [ + TDocContext.Default, + TDocContext.Resizable, + TDocContext.Form, + TDocContext.Table + ] + ``` +4. Define `bySkin` mapping: + ```typescript + bySkin: { + [TSkin.UUI]: { type: '@epam/uui:TextInputProps', component: uui.TextInput }, + [TSkin.Loveship]: { type: '@epam/uui:TextInputProps', component: loveship.TextInput }, + [TSkin.Promo]: { type: '@epam/uui:TextInputProps', component: promo.TextInput }, + [TSkin.Electric]: { type: '@epam/uui:TextInputProps', component: electric.TextInput }, + } + ``` +5. (Optional) Override prop editor defaults: + ```typescript + doc: (doc: DocBuilder) => { + doc.merge('type', { defaultValue: 'text' }); + doc.merge('maxLength', { examples: [10, 20, 30] }); + } + ``` + +## API Block + +Component API section is auto-generated from prop interfaces. + +**Generate locally:** +```bash +yarn generate-components-api +``` + +**Important:** You **must** run this command whenever public props are added, removed, or modified on any component interface (in `uui-core`, `uui-components`, or `uui`). Without this step, new or changed props will not appear in the Property Explorer or API docs. + +## External Themes + +To connect external themes (not in UUI repo): + +1. Add to localStorage: + ```javascript + localStorage.setItem('uui-custom-themes', JSON.stringify({ + customThemes: [ + "https://cdn.example.com/theme-1", + "https://cdn.example.com/theme-2" + ] + })) + ``` + +2. Theme URL must serve `/theme-manifest.json` with structure: + ```typescript + interface IThemeManifest { + id: string; + name: string; + css: string[]; + settings?: string | null; + propsOverride?: { [typeRef: string]: { [propName: string]: IThemeManifestPropOverride } }; + } + ``` + +## Workflow + +When adding new functionality: +1. Add doc example in `app/src/docs/_examples` +2. Update page config to link example +3. Create/update explorerConfig for Property Explorer +4. Generate API: `yarn generate-components-api` +5. Test locally at http://localhost:9009/ diff --git a/.cursor/skills/e2e-testing/SKILL.md b/.cursor/skills/e2e-testing/SKILL.md new file mode 100644 index 0000000000..a03c6a1fcc --- /dev/null +++ b/.cursor/skills/e2e-testing/SKILL.md @@ -0,0 +1,142 @@ +--- +name: uui-e2e-testing +description: Helps create and maintain E2E and screenshot tests for UUI components using Playwright. Use when adding E2E tests, creating screenshot tests, updating preview configurations, or working with Property Explorer previews for testing. +--- + +# UUI E2E Testing + +UUI uses Playwright for E2E and screenshot testing. All test infrastructure is in `uui-e2e-tests/` folder. These tests are part of PR quality gate. + +**Prerequisites:** The UUI docs server must be running before E2E tests. Start with `yarn start` or `yarn build-server && yarn start-server`. Tests default to `http://localhost:9009`. For setup details (Docker, browsers, env vars), see [uui-e2e-tests/readme.md](../../uui-e2e-tests/readme.md). + +## Screenshot Testing + +Screenshot tests use Preview pages from Property Explorer. When editing/adding component props, update screenshot tests accordingly. + +### Create/Edit Preview + +1. Go to `app/src/docs/explorerConfigs` and find/add config file for your component +2. Add preview configuration using `preview` function: + +```typescript +preview: (docPreview: DocPreviewBuilder) => { + const TEST_DATA = { icon: 'action-account-fill.svg' }; + + // Object form + docPreview.add({ + id: TComponentPreview['Size Variants'], + matrix: { + size: { examples: '*' }, + caption: { values: ['Test'] }, + }, + cellSize: '210-60', + }); + + // 3-argument shorthand form + docPreview.add(TComponentPreview['Color Variants'], { + color: { examples: '*' }, + fill: { examples: '*' }, + }, '140-60'); +} +``` + +**Skin-specific overrides** with `docPreview.update()`: + +```typescript +preview: (docPreview: DocPreviewBuilder) => { + docPreview.add({ id: TComponentPreview['Size Variants'], matrix: { /* ... */ } }); + + // Override matrix for specific skins + docPreview.update(TComponentPreview['Size Variants'], { + [TSkin.Loveship]: { fill: { values: ['solid'] } }, + }); +} +``` + +**Guidelines:** +- Split size (layout) and color (appearance) props into separate previews +- Use `examples: '*'` to test all enum values +- Use `values: [...]` for specific test values +- Use `condition` for conditional props + +### Define Screenshot Tests + +Add test configuration in `uui-e2e-tests/tests/previewTests/preview.e2e.ts`: + +```typescript +// Array form — multiple test configs per component +.add(componentName, [ + { + previewId: [TComponentPreview['Color Variants']], + skins: SKINS.promo_loveship_electric, + slow: true, // Optional: for complex renders + }, + { + previewId: [TComponentPreview['Size Variants']], + }, + { + onlyChromium: true, // Optional: browser-specific + previewId: [TComponentPreview['Color Variants']], + previewTag: 'PseudoStateHover', + skins: SKINS.promo_loveship_electric, + forcePseudoState: [{ state: 'hover', selector: '.uui-component' }], + }, +]) + +// Single-object shorthand — simple components +.add(alert, { previewId: values(TAlertPreview) }) +.add(tooltip, { previewId: values(TTooltipPreview), skins: SKINS.promo_loveship }) +``` + +**Additional options:** +- `clickElement: () => 'input'` — click an element before screenshot (e.g. for DatePicker open state) + +## E2E Tests + +E2E tests validate complex component behavior that's hard to test via unit tests. + +**Location:** `uui-e2e-tests/tests/Integration/` + +**Setup:** Use `setupDocExampleTest` with `testUrl`, `PageObjectConstructor`, and Playwright fixtures (`pageWrapper`, `testInfo`). Page objects live in `uui-e2e-tests/framework/pageObjects/`. + +**Example:** +```typescript +import { test } from '../../../framework/fixtures'; +import { DropdownObject } from '../../../framework/pageObjects/dropdownObject'; +import { setupDocExampleTest } from '../testUtils'; + +test('Dropdown / Boundary mode', async ({ pageWrapper }, testInfo) => { + const { pageObject } = await setupDocExampleTest({ + testInfo, + pageWrapper, + PageObjectConstructor: DropdownObject, + testUrl: '/preview?theme=loveship&isSkin=true&componentId=dropdown&previewId=...', + }); + await pageObject.waitForContentLoad(); + await pageObject.hoverTarget(); + await pageObject.waitDropdownBodyVisible(); + // ... assertions +}); +``` + +**testUrl** can target doc examples (`/docExample?examplePath=...`) or preview pages (`/preview?componentId=...&previewId=...`). + +## Commands + +- `yarn test-e2e` — Run all E2E tests +- `yarn test-e2e-chromium` — Run in Chromium only +- `yarn test-e2e-update` — Update screenshot baselines +- `yarn test-e2e-open-report` — Open test report + +## Workflow + +When adding/editing component props: +1. Update preview configuration in `app/src/docs/explorerConfigs` +2. Add screenshot test entries in `preview.e2e.ts` +3. Run `yarn test-e2e` to verify +4. Update screenshots if needed: `yarn test-e2e-update` + +## References + +- Setup and troubleshooting: [uui-e2e-tests/readme.md](../../uui-e2e-tests/readme.md) +- Integration test examples: `uui-e2e-tests/tests/Integration/` diff --git a/.cursor/skills/github-issue-workflow/SKILL.md b/.cursor/skills/github-issue-workflow/SKILL.md new file mode 100644 index 0000000000..da5eabd9dd --- /dev/null +++ b/.cursor/skills/github-issue-workflow/SKILL.md @@ -0,0 +1,162 @@ +--- +name: uui-github-issue-workflow +description: Fetches GitHub issues from URLs and creates implementation plans for UUI. Use when the user provides a GitHub issue link (github.com/.../issues/N), asks to plan work from an issue, or implement/fix an issue by URL. +--- + +# GitHub Issue Workflow + +When the user provides a GitHub issue URL or asks to plan work from an issue, follow this **two-phase workflow**. Always run Phase 1 first; only proceed to Phase 2 after the user confirms. + +## Trigger + +Apply this skill when the user: +- Pastes a GitHub issue URL (e.g. `https://github.com/epam/UUI/issues/123`) +- Asks to "plan", "implement", or "fix" an issue by link +- References an issue with a `github.com/.../issues/N` or `.../pull/N` URL + +--- + +## Phase 1: Analysis (always run first) + +Run Phase 1 to analyze scope before implementation. **Do not implement yet.** Limit exploration; produce a short analysis and stop. + +### 1. Parse the URL + +Extract from the URL: +- **owner** — e.g. `epam` from `github.com/epam/UUI/issues/123` +- **repo** — e.g. `UUI` +- **issue_number** — e.g. `123` + +Supported patterns: +- `https://github.com/{owner}/{repo}/issues/{number}` +- `https://github.com/{owner}/{repo}/pull/{number}` (PRs are issues; use `issue_number` from URL) + +If the URL is invalid or cannot be parsed, ask the user to provide a valid `github.com/.../issues/N` URL. + +### 2. Fetch Issue Content (Dual Path) + +**Always try GitHub MCP first.** Only fall back to WebFetch if MCP is unavailable. + +**Step 1: Check for GitHub MCP server** + +Browse the MCP tool descriptors folder to find a GitHub MCP server. Look for server folders that contain GitHub-related tools (e.g. `get_issue`, `issue_read`, `get-issue`). Common server names: `github`, `github-mcp`, `github-mcp-server`. + +If a GitHub MCP server is found, read its tool descriptor to get the exact tool name and parameters, then call it: + +``` +CallMcpTool + server: + toolName: + arguments: { owner, repo, issue_number } +``` + +- MCP returns structured JSON with title, body, labels, state, etc. +- If the call succeeds, use the response directly. + +**Step 2: Fall back to WebFetch (only if MCP is not available or fails)** + +If no GitHub MCP server is configured, or the MCP call fails: +- Use `WebFetch` with the full issue URL +- Parse the returned content (markdown/HTML) to extract: + - Issue title (from page heading or title element) + - Issue description/body + - Labels or type indicators if visible + +### 3. Produce Short Analysis (Phase 1 only) + +Based on the issue content, produce a **concise** analysis: + +| Section | Content | +|---------|---------| +| **Summary** | Title, type (bug/feature/refactor/docs), 2–4 key points from description | +| **Affected Areas** | Package(s), likely files or components (high-level; no deep codebase exploration yet) | +| **Complexity Estimate** | `small` / `medium` / `large` — based on description scope | +| **Proposed Approach** | 1–2 sentences on how to fix or implement | + +### 4. Stop and Ask for Confirmation + +**Do not create a full implementation plan or make any code changes.** + +Present the analysis and ask: + +> "Here's my analysis. Proceed with full implementation? Any changes to scope or approach?" + +Wait for the user to confirm or request changes. Only after explicit confirmation (e.g. "yes", "proceed", "go ahead") move to Phase 2. + +--- + +## Phase 2: Implementation (only after user confirms) + +Run Phase 2 only when the user has approved the analysis or provided adjustments. + +### 1. Create Full Implementation Plan + +Produce a structured plan with these sections: + +| Section | Content | +|---------|---------| +| **Issue Summary** | Title, type (bug/feature/refactor/docs), key points from description | +| **Branch Name** | Follow [pr-contributing](.cursor/skills/pr-contributing/SKILL.md): `fix/123-description` for bugs, `feature/456-description` for features. Use kebab-case for the description part. | +| **Affected Areas** | Package(s) (e.g. `uui`, `uui-components`), likely files, components | +| **Implementation Steps** | Ordered tasks. For component work, follow [components](.cursor/skills/components/SKILL.md) patterns (wrapping vs new, `withMods`, file paths). | +| **Testing** | Unit tests ([unit-testing](.cursor/skills/unit-testing/SKILL.md)), E2E/screenshot tests if UI changes ([e2e-testing](.cursor/skills/e2e-testing/SKILL.md)) | +| **Documentation** | If API changes: examples, Property Explorer ([documentation](.cursor/skills/documentation/SKILL.md)) | +| **Pre-PR Checklist** | From [pr-contributing](.cursor/skills/pr-contributing/SKILL.md): tests, lint, bundle size, changelog | + +### 2. Output Format (Phase 2) + +Use this template: + +```markdown +## Implementation Plan: #[issue_number] [Title] + +### Summary +- **Type:** [bug|feature|refactor|docs] +- **Key points:** [bullet points from description] + +### Branch +`fix/123-description` / `feature/456-description` + +### Affected Areas +- Package: ... +- Files: ... + +### Steps +1. ... +2. ... + +### Testing +- Unit: ... +- E2E: (if UI changes) + +### Documentation +- (if API changes) + +### Checklist +- [ ] yarn test +- [ ] yarn test-update (if UI) +- [ ] yarn test-e2e (if UI) +- [ ] yarn eslint, yarn stylelint +- [ ] yarn track-bundle-size +- [ ] changelog.md +``` + +### 3. Execute Implementation + +After presenting the plan, proceed with implementation steps as approved by the user. + +## Edge Cases + +| Case | Action | +|------|--------| +| **Invalid URL** | Ask for a valid `github.com/.../issues/N` URL | +| **PR URL** (`/pull/N`)** | Extract issue_number from URL; MCP `issue_read` works for PRs in many setups. Otherwise use web fetch. | +| **MCP call fails** | Proceed immediately with web fetch (WebFetch tool); do not retry MCP. | + +## References + +- Branch naming, checklist: [pr-contributing](.cursor/skills/pr-contributing/SKILL.md) +- Component patterns: [components](.cursor/skills/components/SKILL.md) +- Doc examples, Property Explorer: [documentation](.cursor/skills/documentation/SKILL.md) +- Unit tests: [unit-testing](.cursor/skills/unit-testing/SKILL.md) +- E2E/screenshot tests: [e2e-testing](.cursor/skills/e2e-testing/SKILL.md) diff --git a/.cursor/skills/pr-contributing/SKILL.md b/.cursor/skills/pr-contributing/SKILL.md new file mode 100644 index 0000000000..bcf8a0b8d9 --- /dev/null +++ b/.cursor/skills/pr-contributing/SKILL.md @@ -0,0 +1,203 @@ +--- +name: uui-pr-contributing +description: Guides the UUI pull request process including branch naming, pre-PR checklist, changelog updates, and quality requirements. Use when preparing a pull request, writing commit messages, or following UUI PR requirements. +--- + +# UUI Pull Request Process + +Follow this checklist when preparing a pull request for UUI. + +## Pre-PR Checklist + +Before opening a PR, ensure all items are complete: + +- [ ] **All tests pass** - Run `yarn test` +- [ ] **Snapshots updated** - If UI changed, run `yarn test-update` +- [ ] **E2E tests pass** - If UI changed, run `yarn test-e2e` +- [ ] **ESLint passes** - Run `yarn eslint` (or `yarn eslint-fix`) +- [ ] **Stylelint passes** - Run `yarn stylelint` (or `yarn stylelint-fix`) + +**Note:** Husky runs `lint-staged` on commit, which auto-fixes ESLint and Stylelint for staged files. You can still run lint manually to catch issues before committing. +- [ ] **Bundle size check passes** - Run `yarn track-bundle-size` +- [ ] **TypeScript type checking passes** - Run `yarn test-typecheck` +- [ ] **Documentation updated** - If API changed, add examples and update Property Explorer +- [ ] **Changelog updated** - Add entry to `changelog.md` + +## Git Workflow + +### Branch Creation + +1. **Fork and clone** the repository +2. **Create branch from `develop`** (not `main`): + ```bash + git checkout develop + git pull origin develop + git checkout -b fix/123-description + ``` + +### Branch Naming + +Use descriptive branch names following these patterns: + +**For bug fixes:** +- `fix/123-dropdown-position` - Include GitHub issue number +- `fix/dropdown-position` - If no issue number + +**For features:** +- `feature/456-new-component` - Include GitHub issue number +- `feature/new-component` - If no issue number + +**For other changes:** +- `refactor/component-structure` +- `docs/update-readme` + +**Important:** When working on features or bugs from GitHub issues, **include the issue number** in the branch name (e.g., `fix/123-description`, `feature/456-new-component`). + +**Avoid generic names:** +- ❌ `update` +- ❌ `fix` +- ❌ `changes` + +## Commit Messages + +- Use clear, descriptive commit messages +- Reference issue numbers when applicable: `fix: resolve dropdown positioning issue (#123)` +- Follow conventional commit format when possible: + - `feat: add new component` + - `fix: correct date formatting` + - `docs: update README` + - `refactor: simplify component logic` +- **Before committing:** Always show the proposed commit message to the user and wait for approval before running `git commit`. Do not commit automatically. +- **Shell compatibility:** Do not use bash-only syntax (heredoc `<<'EOF'`, `&&` chaining). Use multiple `-m` flags for multi-line commits: + ```bash + git commit -m "subject line" -m "body line 1" -m "body line 2" + ``` + +## PR Requirements + +### Code Quality + +- **All tests must pass** (`yarn test`) +- **Bundle size check must pass** (`yarn track-bundle-size`) +- **E2E tests must pass** (if UI changes) +- **Code must be linted** (`yarn eslint`, `yarn stylelint`) + +### Documentation + +If making API changes or adding functionality: + +1. **Add example** to documentation: + - Location: `app/src/docs/_examples` + - Add link in page config: `app/src/docs/pages` + +2. **Update Property Explorer**: + - Location: `app/src/docs/explorerConfigs` + - Add/update explorer config for component + +3. **Generate API** (usually done in deployment): + ```bash + yarn generate-components-api + ``` + +### Changelog + +Add entry to the top (unreleased) section of `changelog.md`: + +```markdown +# 6.x.x - xx.xx.2026 + +**What's New** +* [ComponentName]: description of new feature ([#456](https://github.com/epam/UUI/issues/456)) + +**What's Fixed** +* [ComponentName]: description of fix ([#123](https://github.com/epam/UUI/issues/123)) +``` + +**Format:** +- Use `**What's New**` for features/enhancements, `**What's Fixed**` for bug fixes +- Prefix with `[ComponentName]:` to identify the affected component +- Include issue link in markdown format: `([#123](https://github.com/epam/UUI/issues/123))` +- Add to the existing top section; do not create a new version header + +## PR Process Steps + +1. **Fork and clone** the repository +2. **Create branch from `develop`** (include GitHub issue number if applicable) +3. **Make changes** following development guides +4. **Add tests** (unit and E2E where appropriate) +5. **Ensure test suite passes** (`yarn test`), update snapshots if needed +6. **Run E2E/screenshot tests** if making UI changes +7. **If making API changes:** + - Add example to documentation + - Update Property Explorer +8. **Add short description** to `changelog.md` +9. **Commit and push** to your fork +10. **Open PR** targeting `develop` branch + +## PR Description Template + +When opening a PR, include: + +- **Summary** - Brief description of changes +- **Issue** - Link to GitHub issue (if applicable) +- **Type** - Bug fix, Feature, Refactor, Docs, etc. +- **Testing** - How to test the changes +- **Checklist** - Confirm all PR requirements met + +## Quality Gates + +PRs must pass these quality checks: + +1. ✅ All tests pass +2. ✅ Bundle size check passes +3. ✅ E2E tests pass (if UI changes) +4. ✅ Code is linted +5. ✅ Documentation updated (if API changes) +6. ✅ Changelog updated + +## Common Issues + +### Bundle Size Check Fails + +If bundle size increased intentionally: + +```bash +yarn track-bundle-size-override +``` + +**Warning:** Only use if sizes are expected to increase. This overrides the baseline. + +### Tests Fail on Windows + +Use reduced worker count: + +```bash +yarn test --maxWorkers=2 --testTimeout=10000 +``` +You can increase `maxWorkers` up to 4 if needed. + +### E2E Tests Fail + +1. Ensure server is running (`yarn start` or `yarn build-server && yarn start-server`) +2. Check `.env` file for `UUI_APP_BASE_URL` if using non-standard URL +3. Update screenshots if intentional: `yarn test-e2e-update` + +### App Won't Start + +1. Run `yarn` and `cd server && yarn` to install all dependencies +2. Run `yarn build-server` before `yarn start` +3. Ensure port 9009 is free. If occupied, set `PORT` env var or stop the conflicting process + +### Run Single Test File + +```bash +yarn test -- --testPathPattern="Button" +``` +Use `--testNamePattern="should render"` to match test names. See [unit-testing](../unit-testing/SKILL.md) for more. + +## References + +- Main guidelines: [AGENTS.md](../AGENTS.md) Section 7 +- Contributing guide: [CONTRIBUTING.md](../../CONTRIBUTING.md) +- E2E testing: [.cursor/skills/e2e-testing/SKILL.md](../e2e-testing/SKILL.md) +- Unit testing: [.cursor/skills/unit-testing/SKILL.md](../unit-testing/SKILL.md) diff --git a/.cursor/skills/release-workflow/SKILL.md b/.cursor/skills/release-workflow/SKILL.md new file mode 100644 index 0000000000..a25707ae78 --- /dev/null +++ b/.cursor/skills/release-workflow/SKILL.md @@ -0,0 +1,71 @@ +--- +name: uui-release-workflow +description: Guides the UUI package release process including stable and beta releases, changelog updates, and handling failed releases. Use when releasing UUI packages, updating changelog, or troubleshooting release issues. For maintainers only. +--- + +# UUI Release Workflow + +## Important + +**Always ask for explicit user permission before running `yarn release` or `yarn release-beta`.** These commands publish packages to npm and cannot be undone. Never publish to npm without confirming the user intends to do so. + +**Before asking for permission, provide a pre-publish summary:** +- **Version**: The version that will be published (from `changelog.md` top section or Lerna) +- **Packages**: Which packages will be published (from `package.json` workspaces) +- **Summary**: Brief highlights of changes from the current `changelog.md` entry + +Then ask the user to confirm before running the release command. + +## Prerequisites + +- Terminal logged into UUI GitHub account +- NPM account with write access to UUI packages +- For MFA: Generate Access Token in NPM profile + +## Stable Release + +1. Merge all release changes to `main` branch +2. Update `changelog.md` with all released changes +3. Verify builds: `yarn build` +4. Provide pre-publish summary (version, packages, changelog highlights), **ask user for permission**, then run: `yarn release` +5. Follow console prompts +6. After successful release: + - Publish changelog to GitHub Releases: https://github.com/epam/UUI/releases + - Post in UUI Teams channel + +## Beta Release + +Provide pre-publish summary, then **ask user for permission** before running. Use beta dist-tag instead of latest: + +```bash +yarn release-beta +``` + +## NPM Login with MFA + +If using MFA, login with access token: + +```bash +npm config set //registry.npmjs.org/:_authToken=your_token +``` + +## Handling Failed Releases + +If release fails and packages aren't published, revert Lerna commits and tags: + +1. Revert latest commit (usually has version number in message) +2. Delete local tag: `git tag -d ` (e.g., `v5.7.0`) +3. Delete remote tag: `git push --delete origin ` +4. Try release again + +## Release Checklist + +- [ ] All changes merged to `main` +- [ ] Changelog updated +- [ ] Builds verified (`yarn build`) +- [ ] GitHub and NPM accounts authenticated +- [ ] Pre-publish summary shown (version, packages, changelog) +- [ ] **User permission obtained** to publish to npm +- [ ] Run release command +- [ ] Publish changelog to GitHub Releases +- [ ] Notify team in Teams channel diff --git a/.cursor/skills/services-context/SKILL.md b/.cursor/skills/services-context/SKILL.md new file mode 100644 index 0000000000..e53c608dc1 --- /dev/null +++ b/.cursor/skills/services-context/SKILL.md @@ -0,0 +1,129 @@ +--- +name: uui-services-context +description: Helps work with UUI services layer including ContextProvider, ApiContext, ModalContext, NotificationContext, and useUuiContext. Use when implementing features that need modals, notifications, API calls, error handling, or routing. +--- + +# UUI Services & Context + +UUI provides a services layer that components consume via `useUuiContext()`. Services are initialized by `ContextProvider` and include API, modals, notifications, routing, analytics, and more. + +## Location + +- **Package:** `@epam/uui-core` +- **Path:** `uui-core/src/services/` +- **Types:** `uui-core/src/types/contexts.ts` + +## ContextProvider + +Wrap the app with `ContextProvider` to initialize UUI services: + +```typescript +import { ContextProvider } from '@epam/uui-core'; + + ({ + myApi: (data) => processRequest('/api/endpoint', 'POST', data), + })} + loadAppContext={ async (api) => ({ user: await api.getUser() }) } + onInitCompleted={ (svc) => { /* optional: store ref, etc. */ } } + history={ history } // optional: for SPA routing +> + + +``` + +- **apiDefinition** — Returns API object. Each method receives `(url, method, data, options)` via `processRequest`. +- **loadAppContext** — Loads global data before mount. Result stored in `uuiApp`. +- **onInitCompleted** — Called when contexts are ready. +- **history** — React Router history for `uuiRouter.redirect()`. + +## useUuiContext + +Access services inside any component under `ContextProvider`: + +```typescript +import { useUuiContext } from '@epam/uui-core'; + +function MyComponent() { + const svc = useUuiContext(); + const { api, uuiModals, uuiNotifications, uuiRouter } = svc; + // ... +} +``` + +**Throws** if used outside `ContextProvider`. + +## Key Services + +| Service | Property | Purpose | +|---------|----------|---------| +| API | `api` | HTTP requests. Methods from `apiDefinition`. `api.withOptions({})` for call options. | +| Modals | `uuiModals` | Show modal dialogs. `uuiModals.show(render, params)` returns `Promise`. | +| Notifications | `uuiNotifications` | Toast notifications. `uuiNotifications.show(render, params)`. | +| Router | `uuiRouter` | `redirect(link)`, `getCurrentLink()`, `transfer(link)`. | +| Layout | `uuiLayout` | Layers, portal root. `getLayer()`, `releaseLayer()`, `getPortalRoot()`. | +| Locks | `uuiLocks` | Concurrent action locking. `acquire()`, `release()`, `withLock()`. | +| Errors | `uuiErrors` | Error handling, recovery. | +| Analytics | `uuiAnalytics` | Analytics events. | +| User Settings | `uuiUserSettings` | Persistent user settings (e.g. form drafts). | +| App Context | `uuiApp` | Data from `loadAppContext`. | + +## Modals + +```typescript +const result = await uuiModals.show((props) => ( + props.success(data) } + onCancel={ () => props.abort() } + /> +), { initialValue }); +``` + +- `props.success(data)` — Resolves the promise, closes modal. +- `props.abort()` — Rejects the promise, closes modal. +- `ModalOperationCancelled` — Thrown when user dismisses modal (e.g. backdrop click). + +## Notifications + +```typescript +await uuiNotifications.show((props) => ( + + Saved successfully + +), { duration: 5, position: 'bot-right' }); +``` + +- **duration** — Seconds or `'forever'` for persistent notification. +- **position** — `'bot-left'` | `'bot-right'` | `'top-left'` | `'top-right'` | `'top-center'` | `'bot-center'`. + +## API (ApiContext) + +- `processRequest(url, method, data, options)` — Low-level HTTP. +- API methods from `apiDefinition` use `processRequest` internally. +- AbortSignal passed via `FetchingOptions` for cancellation. +- Auth recovery, relogin path configured via `ApiContext` props. + +## useUuiServices + +Used by `ContextProvider` internally. For tests or custom setup: + +```typescript +const { services } = useUuiServices({ apiDefinition, router }); +// Provide via UuiContext.Provider value={services} +``` + +## Routing Adapters + +- **HistoryAdaptedRouter** — Wraps react-router `history`. +- **StubAdaptedRouter** — No-op for apps without routing. +- **Next.js** — Use `useUuiServicesSsr` from `@epam/uui-core/ssr`. + +## References + +- ContextProvider: `uui-core/src/services/ContextProvider.tsx` +- useUuiServices: `uui-core/src/hooks/useUuiServices.ts` +- Types: `uui-core/src/types/contexts.ts` +- ApiContext: `uui-core/src/services/ApiContext.ts` +- ModalContext: `uui-core/src/services/ModalContext.ts` +- NotificationContext: `uui-core/src/services/NotificationContext.ts` diff --git a/.cursor/skills/themes/SKILL.md b/.cursor/skills/themes/SKILL.md new file mode 100644 index 0000000000..c5dbdb8fa2 --- /dev/null +++ b/.cursor/skills/themes/SKILL.md @@ -0,0 +1,198 @@ +--- +name: uui-themes +description: Helps work with UUI themes including generating theme tokens from Figma, working with skin packages, and connecting external themes. Use when generating theme tokens, working with loveship/electric/promo themes, or connecting external themes. +--- + +# UUI Themes + +UUI supports multiple themes (skins) and allows connecting external themes. Built-in themes include Loveship, Electric, and Promo. + +## Built-in Theme Packages + +UUI includes three styled theme packages: + +- **`@epam/loveship`** — Loveship theme (light and dark modes) +- **`@epam/electric`** — Electric theme (light and dark modes) +- **`@epam/promo`** — Promo theme + +These are located in: +- `loveship/` — Loveship theme package +- `epam-electric/` — Electric theme package +- `epam-promo/` — Promo theme package + +## Generating Theme Tokens from Figma + +Theme tokens are generated from Figma JSON exports. + +### Process + +1. **Obtain Theme.json from UX design team** + - Request the most recent `Theme.json` file exported from Figma + +2. **Replace existing file** + - Place new `Theme.json` in `public/docs/figmaTokensGen/Theme.json` + - Replace the existing file + +3. **Generate tokens** + ```bash + yarn generate-theme-tokens + ``` + + **Important:** Run from UUI monorepo root! + +### Generated Files + +The command generates: + +- **`public/docs/figmaTokensGen/ThemeOutput.json`** — Original file with added CSS variable info: + ```json + { + "codeSyntax": { + "WEB": "var(--uui-control-border-focus)" + } + } + ``` + This file should be sent back to UX designers to import into Figma. + +- **`public/docs/figmaTokensGen/ThemeTokens.json`** — Normalized tokens with inheritance hierarchy in minimalistic format. Used for: + - Color palette documentation + - Token tables in sandbox + +- **`epam-assets/theme/variables/tokens/*.scss`** — Theme-specific SCSS mixins with token variables + +### Token Structure + +Tokens include: +- **Modes** — Theme variants (e.g., Loveship-Light, Loveship-Dark, Promo, Electric-Light, Electric-Dark) +- **Exposed tokens** — CSS variables with values per theme +- **Value chains** — Token aliases and inheritance + +Example token: +```json +{ + "id": "core/controls/control-bg", + "type": "COLOR", + "cssVar": "--uui-control-bg", + "valueByTheme": { + "Loveship-Light": { + "value": "#FFFFFF", + "valueChain": { + "alias": [ + { "id": "core/surfaces/surface-main", "cssVar": "--uui-surface-main" } + ] + } + } + } +} +``` + +## External Themes + +Connect themes hosted outside the UUI repository. + +### Setup + +1. **Add theme URLs to localStorage:** + ```javascript + localStorage.setItem('uui-custom-themes', JSON.stringify({ + customThemes: [ + "https://cdn.example.com/theme-1", + "https://cdn.example.com/theme-2" + ] + })) + ``` + +2. **Theme URL must serve `/theme-manifest.json`** + +### Theme Manifest Structure + +The theme URL must serve a `/theme-manifest.json` endpoint with this structure: + +```typescript +interface IThemeManifest { + id: string; // Unique theme identifier + name: string; // Display name + css: string[]; // Array of CSS file URLs + settings?: string | null; // Optional settings URL + propsOverride?: { // Optional component prop overrides + [typeRef: string]: { + [propName: string]: IThemeManifestPropOverride + } + }; +} +``` + +**Example manifest:** +```json +{ + "id": "custom-theme", + "name": "Custom Theme", + "css": [ + "https://cdn.example.com/theme-1/styles.css" + ], + "settings": "https://cdn.example.com/theme-1/settings.json" +} +``` + +### CSS Variables and uui-* Classes + +**CSS variables** use `--uui-*` prefix: +- `--uui-control-bg`, `--uui-control-border`, `--uui-text-primary`, etc. +- Defined in theme token SCSS files. Follow existing token structure for consistency. + +**Global utility classes** use `uui-*` prefix for themeable props. These are applied by components via `applyMods`: + +| Pattern | Purpose | Examples | +|---------|---------|----------| +| `uui-color-{value}` | Text/icon color | `uui-color-primary`, `uui-color-neutral`, `uui-color-error` | +| `uui-size-{value}` | Component size | `uui-size-24`, `uui-size-36`, `uui-size-48` | +| `uui-fill-{value}` | Button fill style | `uui-fill-solid`, `uui-fill-outline`, `uui-fill-ghost` | +| `uui-{component}` | Component root | `uui-button`, `uui-tab-button`, `uui-input-box` | +| `uui-{component}_{part}` | Component part | `uui-icon_button`, `uui-link_button` | + +Skin packages (loveship, epam-electric, epam-promo) define styles for these classes. When adding new component mods, follow the same pattern so themes can style them. + +## Working with Theme Tokens in Components + +Components access theme tokens via CSS variables: + +```scss +// Component.module.scss +.root { + background-color: var(--uui-control-bg); + border-color: var(--uui-control-border); + color: var(--uui-text-primary); +} +``` + +## Theme Settings + +Components can access theme settings via the `settings` object: + +```typescript +import { settings } from '../../settings'; + +// Access theme-specific icons, sizes, etc. +const icon = settings.button.icons.dropdownIcon; +const defaultSize = settings.button.sizes.default; +``` + +## Commands + +**Generate theme tokens:** +```bash +yarn generate-theme-tokens +``` + +**Process icons** (if updating theme icons): +```bash +yarn process-icons +``` +Place icons in `icons-source` folder first. + +## References + +- Theme tokens generation: `uui-build/ts/themeTokens.md` +- External themes: [.cursor/skills/documentation/SKILL.md](../documentation/SKILL.md) (External Themes section) +- Theme tokens output: `public/docs/figmaTokensGen/ThemeTokens.json` +- SCSS mixins: `epam-assets/theme/variables/tokens/` diff --git a/.cursor/skills/unit-testing/SKILL.md b/.cursor/skills/unit-testing/SKILL.md new file mode 100644 index 0000000000..cca3e7d923 --- /dev/null +++ b/.cursor/skills/unit-testing/SKILL.md @@ -0,0 +1,313 @@ +--- +name: uui-unit-testing +description: Helps write unit tests for UUI components using Jest, jsdom, and @epam/uui-test-utils. Use when writing unit tests for UUI components, updating snapshots, running Jest tests, or working with test utilities. +--- + +# UUI Unit Testing + +UUI uses Jest with jsdom for unit testing React components. Tests are located in `__tests__` folders within each package. + +## Test File Location + +Test files follow the pattern: `**/__tests__/**/*.test.{js,ts,tsx}` + +**Examples:** +- `uui/components/buttons/__tests__/Button.test.tsx` +- `uui/components/inputs/__tests__/NumericInput.test.tsx` +- `uui-core/src/hooks/__tests__/useVirtualList.test.tsx` + +## Test Commands + +**Run all tests:** +```bash +yarn test +``` + +**Run tests in watch mode:** +```bash +yarn test-watch +``` + +**Run a single test file:** +```bash +yarn test -- --testPathPattern="Button" +``` +Use `--testPathPattern` to match file path, or `--testNamePattern="should render"` to match test names. + +**Update snapshots:** +```bash +yarn test-update +``` + +**Generate test coverage report:** +```bash +yarn test-report +``` +Coverage report saves to `.reports/unit-tests` folder. + +**TypeScript type checking:** +```bash +yarn test-typecheck +``` + +**Windows workaround:** If encountering test errors on Windows, use reduced worker count: +```bash +yarn test --maxWorkers=2 --testTimeout=10000 +``` +You can increase `maxWorkers` up to 4 if needed. + +## Using @epam/uui-test-utils + +The `@epam/uui-test-utils` package provides helpers for testing UUI components with proper context. + +### Key Exports + +| Export | Purpose | +|--------|---------| +| `renderSnapshotWithContextAsync` | Render with UUI context, returns `DocumentFragment` for snapshots | +| `renderWithContextAsync` | Render with UUI context for interaction testing | +| `renderHookWithContextAsync` | Test custom hooks with UUI context | +| `setupComponentForTest` | Setup component with mocks and `setProps` helper | +| `screen` | Re-exported from `@testing-library/react` | +| `userEvent` | Re-exported from `@testing-library/user-event` | +| `fireEvent` | Re-exported from `@testing-library/react` | +| `SvgMock` | Mock for SVG icon imports in snapshots | +| `mockReactPortalsForSnapshots` | Mock portals for snapshot tests | +| `delay`, `delayAct` | Async timing utilities for tests | + +### renderSnapshotWithContextAsync + +Renders component with UUI context and returns a `DocumentFragment` (via `asFragment()`) for snapshot matching: + +```typescript +import { renderSnapshotWithContextAsync } from '@epam/uui-test-utils'; +import { NumericInput } from '../NumericInput'; + +it('should be rendered with minimum props', async () => { + const tree = await renderSnapshotWithContextAsync( + + ); + expect(tree).toMatchSnapshot(); +}); +``` + +### setupComponentForTest + +Use for testing **controlled components** (e.g. `value` + `onValueChange`). Provides `setProps` to update props without unmounting, and `mocks` for callback assertions. Call `context.current.setProperty(name, value)` in callback mocks to simulate controlled updates: + +```typescript +import { setupComponentForTest, screen, fireEvent } from '@epam/uui-test-utils'; +import { TextInput } from '@epam/uui'; + +interface TestComponentProps { + value?: string; + onValueChange?: (value: string) => void; +} + +const { mocks, setProps } = await setupComponentForTest( + (context) => ({ + value: 'initial', + onValueChange: jest.fn().mockImplementation((newValue) => { + context.current.setProperty('value', newValue); + }), + }), + (props) => , +); + +const input = screen.queryByRole('textbox'); +fireEvent.change(input, { target: { value: 'updated' } }); +expect(mocks.onValueChange).toHaveBeenLastCalledWith('updated'); + +setProps({ value: 'external' }); // Update props without unmounting +``` + +### renderHookWithContextAsync + +Renders a hook with UUI context. Returns `{ result, rerender, unmount, svc }`: +- **result** — current hook return value (use `result.current`) +- **svc** — UUI services (api, modals, router, etc.) for mocking or assertions +- **rerender**, **unmount** — same as `renderHook` from Testing Library + +```typescript +import { renderHookWithContextAsync, act } from '@epam/uui-test-utils'; + +it('should use DataSource view', async () => { + const dataSource = useArrayDataSource({ items: [...], getId: (i) => i.id }, []); + const { result, svc } = await renderHookWithContextAsync(() => + dataSource.useView({}, () => {}, {}) + ); + const rows = result.current.getVisibleRows(); + expect(rows).toHaveLength(5); + // svc.api, svc.uuiModals, etc. available for mocks +}); + +it('should test custom hook with services', async () => { + const { result, svc } = await renderHookWithContextAsync(useMyHook); + act(() => result.current.doSomething()); + expect(svc.uuiAnalytics.sendEvent).toHaveBeenCalled(); +}); +``` + +### renderWithContextAsync + +Renders component with UUI context for interaction testing: + +```typescript +import { renderWithContextAsync, screen, userEvent } from '@epam/uui-test-utils'; +import { Tag } from '../Tag'; + +it('should call onClear callback', async () => { + const onClearMock = jest.fn(); + + await renderWithContextAsync( + + ); + + const clearButton = await screen.findByRole('button', { name: /remove tag/i }); + await userEvent.click(clearButton); + + expect(onClearMock).toHaveBeenCalled(); +}); +``` + +## Test Structure + +### Basic Snapshot Test + +```typescript +import React from 'react'; +import { ComponentName } from '../ComponentName'; +import { renderSnapshotWithContextAsync } from '@epam/uui-test-utils'; + +describe('ComponentName', () => { + describe('snapshots', () => { + it('should be rendered with minimum props', async () => { + const tree = await renderSnapshotWithContextAsync(); + expect(tree).toMatchSnapshot(); + }); + + it('should be rendered with maximum props', async () => { + const tree = await renderSnapshotWithContextAsync( + + ); + expect(tree).toMatchSnapshot(); + }); + }); +}); +``` + +### Interaction Test + +```typescript +import { renderWithContextAsync, screen, userEvent } from '@epam/uui-test-utils'; + +it('should handle user interaction', async () => { + const onActionMock = jest.fn(); + + await renderWithContextAsync( + + ); + + const button = await screen.findByRole('button'); + await userEvent.click(button); + + expect(onActionMock).toHaveBeenCalled(); +}); +``` + +## Snapshot Update Workflow + +When UI changes require snapshot updates: + +1. **Run tests** to see which snapshots fail: + ```bash + yarn test + ``` + +2. **Review changes** - verify the visual changes are intentional + +3. **Update snapshots**: + ```bash + yarn test-update + ``` + +4. **Commit updated snapshots** along with component changes + +**Important:** Always review snapshot diffs before updating. Snapshots should reflect intentional UI changes, not accidental regressions. + +## Test Environment + +- **Test framework:** Jest +- **Test environment:** jsdom (for React components), node (for build scripts) +- **Test utilities:** `@epam/uui-test-utils` for component testing helpers + +## Test Coverage + +Test coverage is collected from: +- `uui-core` +- `uui-components` +- `uui` +- `epam-promo` +- `epam-electric` +- `loveship` + +Run `yarn test-report` to generate coverage reports. + +## Common Patterns + +### Mocking Portals for Snapshots + +Use the built-in helper instead of manual mocking: + +```typescript +import { mockReactPortalsForSnapshots, renderSnapshotWithContextAsync } from '@epam/uui-test-utils'; + +describe('ComponentWithPortal', () => { + mockReactPortalsForSnapshots(); + + it('should render correctly', async () => { + const tree = await renderSnapshotWithContextAsync(); + expect(tree).toMatchSnapshot(); + }); +}); +``` + +### Testing with Icons + +Use `SvgMock` from test utils for icon props in snapshots: + +```typescript +import { SvgMock, renderSnapshotWithContextAsync } from '@epam/uui-test-utils'; + +it('should render with icon', async () => { + const tree = await renderSnapshotWithContextAsync( + + ); + expect(tree).toMatchSnapshot(); +}); +``` + +For specific icon imports (when behavior depends on the icon): + +```typescript +// @ts-expect-error +import { ReactComponent as CalendarIcon } from '@epam-assets/icons/action-calendar-fill.svg'; +``` + +## Best Practices + +1. **Add tests for bug fixes** - Prevent regressions +2. **Add tests for new functionality** - Ensure components work as expected +3. **Update snapshots when UI changes** - Keep snapshots current +4. **Run full test suite before committing** - `yarn test` +5. **Use descriptive test names** - Make failures easy to understand +6. **Test user interactions** - Not just rendering, but behavior + +## References + +- Test utilities: `@epam/uui-test-utils` package +- Example snapshot test: `uui/components/inputs/__tests__/NumericInput.test.tsx` +- Example interaction test: `uui/components/widgets/__tests__/Tag.test.tsx` +- Example setupComponentForTest: `app/src/docs/_examples/testing/__tests__/testComponent.test.tsx` +- Example renderHookWithContextAsync: `uui-core/src/data/forms/__tests__/useForm.test.tsx` +- Testing documentation: https://uui.epam.com/documents?id=testing-getting-started&mode=doc&isSkin=null&category=testing diff --git a/.gitignore b/.gitignore index 8e595b2850..872905de80 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,6 @@ coverage .vscode/settings.json .cursorrules .cursorignore -.cursor/ # next.js .next/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..2621a914ed --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,169 @@ +# UUI Project - Agent Guidelines + +## 1) Project Overview + +**UUI** (EPAM Unified UI) is a React-based components and accelerators library built by EPAM Systems. This is a monorepo managed with **Lerna** and **Yarn Workspaces**, containing multiple packages for themeable components, headless core components, and styled theme packages. + +### Architecture + +- **Monorepo structure**: Multiple packages managed with Lerna and Yarn Workspaces +- **Local development**: Entire repository built by Create React App (CRA) with watch mode +- **Production builds**: Each workspace built separately using individual build processes + +### Key Packages + +- `@epam/uui` — Ready-to-use, themeable components (**new components go here**) +- `@epam/uui-components` — Legacy package with logic-only components (no visual styles) +- `@epam/uui-core` — Core interfaces, types, services, data sources, hooks, and utilities +- `@epam/loveship`, `@epam/electric`, `@epam/promo` — Styled theme packages +- `@epam/uui-editor` — Slate.js-based Rich Text Editor +- `@epam/uui-timeline` — Gantt-chart like interfaces +- `@epam/assets` — Icons set, built-in themes and fonts +- `@epam/uui-test-utils` — Helpers for unit tests +- `@epam/uui-docs` — Utilities for docs site (DocBuilder, demo data, Property Explorer contexts). Needed when working on documentation +- `@epam/uui-build` — Build scripts, linting config, npm scripts +- `@epam/uui-db` — Client-side relational state cache +- `templates/` — Project templates (CRA, Next.js, Vite) +- `next-demo/` — Next.js demo apps for integration testing + +## 2) Development Environment + +### Requirements + +- **Node.js**: >= 18.x +- **Package Manager**: Yarn +- **Git**: Fork-and-Pull workflow + +### Setup Instructions + +1. Clone the repository: + ```bash + git clone git@github.com:epam/UUI.git + ``` + +2. Install dependencies: + ```bash + yarn + cd ./server + yarn + cd .. + ``` + +3. Build server and start development app: + ```bash + yarn build-server + yarn start + ``` + This opens uui.epam.com locally in watch mode. + +### Workspace Commands + +- `yarn start` — Run development app (opens documentation site) +- `yarn start-server` — Start Node.js server for docs site +- `yarn watch-server` — Start server in watch mode + +## 3) Build & Test Commands + +### Build Commands + +- `yarn build` — Build all packages +- `yarn build-dev` — Build app for development +- `yarn build-modules` — Build all workspace modules +- `yarn build-server` — Build Node.js server + +### Test Commands + +**Unit Tests:** +- `yarn test` — Run all unit tests +- `yarn test-watch` — Run tests in watch mode +- `yarn test-update` — Update test snapshots +- `yarn test-report` — Generate test coverage report (saves to `.reports/unit-tests`) +- `yarn test-typecheck` — TypeScript type checking + +**E2E Tests:** +- `yarn test-e2e` — Run E2E tests (Playwright) +- `yarn test-e2e-chromium` — Run E2E tests in Chromium only +- `yarn test-e2e-update` — Update E2E screenshots +- `yarn test-e2e-open-report` — Open E2E test report + +**Note for Windows users:** If encountering test errors, use reduced worker count: +```bash +yarn test --maxWorkers=2 --testTimeout=10000 +``` +You can increase `maxWorkers` up to 4 if needed. + +### Validation Commands + +- `yarn eslint` — Run ESLint +- `yarn eslint-fix` — Run ESLint with auto-fix +- `yarn stylelint` — Run Stylelint +- `yarn stylelint-fix` — Run Stylelint with auto-fix +- `yarn track-bundle-size` — Check if bundle sizes exceed baseline (PR quality check) +- `yarn track-bundle-size-override` — Override baseline with current sizes (use only if sizes are expected to increase) + +### Other Commands + +- `yarn generate-components-api` — Generate Property Explorer data and API references +- `yarn generate-theme-tokens` — Generate theme CSS variables from Figma JSON +- `yarn process-icons` — Update icons from Figma (place icons in `icons-source` folder first) + +## 4) Code Style & Conventions + +### Component Architecture + +- **New components** must be created in `uui/components/`, **not** `uui-components/` +- See [.cursor/skills/components/SKILL.md](.cursor/skills/components/SKILL.md) for `withMods`, `forwardRef`, styling patterns, and examples + +### Linting & Formatting + +- ESLint configuration: extends `uui-build/linting/eslintrc.base.js` +- Stylelint for CSS/SCSS/LESS files +- Pre-commit hooks via Husky and lint-staged + +### TypeScript + +- TypeScript 5.4.2 (enforced via resolutions) +- Type checking: `yarn test-typecheck` +- Project uses strict TypeScript configuration + +### File Organization + +- Test files: `__tests__/**/*.test.{js,ts,tsx}` pattern +- Components: `uui/components/` for new components +- Documentation: `app/src/docs/` for documentation site +- Preview configs: `app/src/docs/explorerConfigs` for Property Explorer + +## 5) Testing Guidelines + +- Add unit tests for bug fixes and new functionality +- Update snapshots when UI changes: `yarn test-update` +- Run full test suite before committing: `yarn test` +- Consider E2E/screenshot tests for UI changes +- See [.cursor/skills/unit-testing/SKILL.md](.cursor/skills/unit-testing/SKILL.md) for test patterns and `@epam/uui-test-utils` usage +- See [.cursor/skills/e2e-testing/SKILL.md](.cursor/skills/e2e-testing/SKILL.md) for Playwright screenshot and E2E tests + +## 6) Security Considerations + +- Be cautious with `dangerouslySetInnerHTML` — sanitize content before rendering +- Never commit API keys, tokens, or credentials +- Sensitive areas: `server/` folder (API endpoints), authentication logic + +## 7) PR/Commit Guidelines + +- Create branches from `develop` (not `main`): `fix/123-description`, `feature/456-description` +- Include GitHub issue number in branch name when applicable +- Add entry to `changelog.md` for every change (see [.cursor/skills/pr-contributing/SKILL.md](.cursor/skills/pr-contributing/SKILL.md) for format) +- Target `develop` branch for PRs +- See [.cursor/skills/pr-contributing/SKILL.md](.cursor/skills/pr-contributing/SKILL.md) for full PR checklist, commit messages, and quality gates + +## Resources + +- **Skills** (in `.cursor/skills/`): components, documentation, e2e-testing, unit-testing, pr-contributing, themes, release-workflow, github-issue-workflow, **data-sources**, **services-context** +- **Documentation**: https://uui.epam.com +- **Contributing Guide**: [CONTRIBUTING.md](CONTRIBUTING.md) +- **Developer Guides**: See [dev-docs/](dev-docs/) folder: + - [dev-docs/overview.md](dev-docs/overview.md) — Project overview and monorepo layout + - [dev-docs/dev-workflows.md](dev-docs/dev-workflows.md) — Local development workflows + - [dev-docs/uui-documentation.md](dev-docs/uui-documentation.md) — Editing documentation + - [dev-docs/e2e-tests.md](dev-docs/e2e-tests.md) — E2E and screenshot testing + - [dev-docs/release-workflow.md](dev-docs/release-workflow.md) — Release workflow (maintainers) diff --git a/app/src/demo/tables/filteredTable/FilteredTable.tsx b/app/src/demo/tables/filteredTable/FilteredTable.tsx index 51024fa452..de4e8f1cb2 100644 --- a/app/src/demo/tables/filteredTable/FilteredTable.tsx +++ b/app/src/demo/tables/filteredTable/FilteredTable.tsx @@ -79,7 +79,7 @@ export function FilteredTable() { return (
- + Users Dashboard @@ -101,6 +101,7 @@ export function FilteredTable() { showColumnsConfig={ true } allowColumnsResizing={ true } allowColumnsReordering={ true } + rawProps={ { 'aria-labelledby': 'presets-title' } } { ...listProps } /> diff --git a/app/src/demo/tables/masterDetailedTable/MasterDetailedTable.tsx b/app/src/demo/tables/masterDetailedTable/MasterDetailedTable.tsx index ba06184ef1..bae6646ec1 100644 --- a/app/src/demo/tables/masterDetailedTable/MasterDetailedTable.tsx +++ b/app/src/demo/tables/masterDetailedTable/MasterDetailedTable.tsx @@ -117,6 +117,7 @@ export function MasterDetailedTable() { showColumnsConfig={ true } allowColumnsResizing allowColumnsReordering + rawProps={ { 'aria-label': 'Users Dashboard' } } { ...view.getListProps() } />
diff --git a/app/src/docs/_examples/tables/FiltersPanelBasic.example.tsx b/app/src/docs/_examples/tables/FiltersPanelBasic.example.tsx index 009e20bef4..ba5b1e1faa 100644 --- a/app/src/docs/_examples/tables/FiltersPanelBasic.example.tsx +++ b/app/src/docs/_examples/tables/FiltersPanelBasic.example.tsx @@ -147,6 +147,9 @@ export default function FiltersPanelExample() { title: 'Salary', type: 'numeric', predicates: defaultPredicates.numeric, + min: 100, + max: 1000, + step: 100, }, { field: 'productionCategory', diff --git a/app/src/docs/_examples/tooltip/WithFocusableElements.example.tsx b/app/src/docs/_examples/tooltip/WithFocusableElements.example.tsx new file mode 100644 index 0000000000..e08122d69e --- /dev/null +++ b/app/src/docs/_examples/tooltip/WithFocusableElements.example.tsx @@ -0,0 +1,63 @@ +import React, { useState } from 'react'; +import { FlexRow, Text, Tooltip, Switch, FlexCell, Button } from '@epam/uui'; + +export default function WithFocusableElementsExample() { + const [switchValue, setSwitchValue] = useState(false); + + return ( + + + + + Tooltip on a focusable element + + + + + + + + + + +