|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +UI5 Web Components is an enterprise-grade, framework-agnostic web components library implementing SAP Fiori design. It's a Yarn-based monorepo using Lerna and Yarn Workspaces. |
| 8 | + |
| 9 | +**Requirements:** Node.js ^20.19.0 or >=22.12.0, Yarn v4 (via Corepack: `corepack enable`) |
| 10 | + |
| 11 | +## Common Commands |
| 12 | + |
| 13 | +Root folder commands: |
| 14 | +```bash |
| 15 | +# Building |
| 16 | +yarn ts # TypeScript compilation, always run in the root folder as the monorepo uses typescript workspaces too |
| 17 | + |
| 18 | +# Linting |
| 19 | +yarn lint # Lint all packages |
| 20 | +``` |
| 21 | + |
| 22 | +Package level commands (e.g., packages/main/): |
| 23 | +```bash |
| 24 | +cd pacakges/main |
| 25 | +# Testing in a specific package |
| 26 | +yarn test:cypress # Run all Cypress tests |
| 27 | +yarn test:cypress:single cypress/specs/Button.cy.tsx # Run single test file |
| 28 | +``` |
| 29 | + |
| 30 | +## Monorepo Structure |
| 31 | + |
| 32 | +Key packages in `/packages/`: |
| 33 | +- **base**: Core framework, UI5Element base class, decorators, rendering |
| 34 | +- **main**: Primary components (Button, Input, Table, etc.) - `@ui5/webcomponents` |
| 35 | +- **fiori**: SAP Fiori-specific components (ShellBar, SideNavigation) - `@ui5/webcomponents-fiori` |
| 36 | +- **ai**: AI-related components |
| 37 | +- **theming**: Theming assets and base themes |
| 38 | +- **localization**: i18n and CLDR assets |
| 39 | +- **icons**, **icons-tnt**, **icons-business-suite**: Icon collections |
| 40 | +- **tools**: Build tools, dev server, CLI |
| 41 | + |
| 42 | +## Component Architecture |
| 43 | + |
| 44 | +Components use decorator-based definitions with Preact JSX templates: |
| 45 | + |
| 46 | +```typescript |
| 47 | +@customElement({ |
| 48 | + tag: "ui5-button", |
| 49 | + renderer: jsxRenderer, |
| 50 | + template: ButtonTemplate, |
| 51 | + styles: buttonCss, |
| 52 | + languageAware: true, |
| 53 | +}) |
| 54 | +class Button extends UI5Element { |
| 55 | + @property() design: `${ButtonDesign}` = "Default"; |
| 56 | + @property({ type: Boolean }) disabled = false; |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +**File structure for a component:** |
| 61 | +- `src/ComponentName.ts` - Component class with decorators |
| 62 | +- `src/ComponentNameTemplate.tsx` - JSX template |
| 63 | +- `src/themes/ComponentName.css` - Styles (works across all themes via CSS variables) |
| 64 | +- `src/i18n/messagebundle*.properties` - Translations |
| 65 | +- `cypress/specs/<Component>.cy.tsx` - the test file for the component |
| 66 | + |
| 67 | +## Critical Development Rules |
| 68 | + |
| 69 | +### DOM manipulation anti-pattern |
| 70 | +this is a common pattern for accessing DOM elements: |
| 71 | +```typescript |
| 72 | + @query("[component-selector]") |
| 73 | + _domRefToComponent!: SomeComponent; |
| 74 | +``` |
| 75 | + |
| 76 | +another way for accessing DOM elements is: |
| 77 | +```typescript |
| 78 | +const _domRefToComponent = this.shadowRoot!.querySelector<SomeComponent>("[component-selector]")!; |
| 79 | +``` |
| 80 | + |
| 81 | +Using the reference is only allowed for calling `.focus()` like this: |
| 82 | +```typescript |
| 83 | +_domRefToComponent?.focus(); |
| 84 | +``` |
| 85 | + |
| 86 | +Modifying properties is strictly forbidden and should be done in the template instead. |
| 87 | +```typescript |
| 88 | +// BAD - don't modify the dom directly |
| 89 | +_domRefToComponent?.value = `don't do this`; |
| 90 | +``` |
| 91 | + |
| 92 | +```tsx |
| 93 | +// GOOD - use the template |
| 94 | +<SomeComponent value={'do this instead'}> |
| 95 | +``` |
| 96 | + |
| 97 | +### Always use template literal types for enums |
| 98 | +Imports: |
| 99 | +```typescript |
| 100 | +// BAD - don't import the enum object from the JS file |
| 101 | +import ButtonDesign from "./types/ButtonDesign.js"; |
| 102 | + |
| 103 | +// GOOD - import the type of the enum only - no runtime overhead |
| 104 | +import type ButtonDesign from "./types/ButtonDesign.js"; |
| 105 | +``` |
| 106 | + |
| 107 | +Property types: |
| 108 | +```typescript |
| 109 | + // BAD - don't use the type directy and don't assign values from the enum object |
| 110 | + design: ButtonDesign = ButtonDesign.Default; |
| 111 | + |
| 112 | + // GOOD - Awalys use template literal for the enum and always assign the string values |
| 113 | + design: `${ButtonDesign}` = "Default"; |
| 114 | +``` |
| 115 | + |
| 116 | +Enum value usage: |
| 117 | +```typescript |
| 118 | +// BAD - don't use enum values from the object in the runtime (having a type import enforces this) |
| 119 | +this.design !== ButtonDesign.Transparent |
| 120 | + |
| 121 | +// GOOD - use strings (IDE will autocomplete, TS will force the enum correctness, no runtime overhead from enum) |
| 122 | +this.design !== "Transparent"; |
| 123 | +``` |
| 124 | + |
| 125 | +### Scoping-Safe Code (Required for Micro-Frontend Support) |
| 126 | +```typescript |
| 127 | +// BAD - hard-coded tag names break scoping |
| 128 | +this.shadowRoot.querySelector("ui5-popover") |
| 129 | +ui5-button.accept-btn { color: green; } |
| 130 | + |
| 131 | +// GOOD - use attribute notation |
| 132 | +this.shadowRoot.querySelector("[ui5-popover]") |
| 133 | +[ui5-button].accept-btn { color: green; } |
| 134 | +``` |
| 135 | + |
| 136 | +### No instanceof Checks, no direct `.tagName` comparison |
| 137 | +```typescript |
| 138 | +// BAD - fails with multiple framework versions |
| 139 | +if (element instanceof Button) { } |
| 140 | + |
| 141 | +// BAD - the tag name could be scoped like `UI5-BUTTON-F5331039` and won't match |
| 142 | +if (element.tagName === "UI5-BUTTON") { } |
| 143 | + |
| 144 | +// GOOD - use the `hasTag` helper that checks the pure tag |
| 145 | +// use instance check like |
| 146 | +const isInstanceOfComboBoxItemGroup = (object: any): object is ComboBoxItemGroup => { |
| 147 | + return "isGroupItem" in object; |
| 148 | +}; |
| 149 | +``` |
| 150 | + |
| 151 | +### Property/Event Conventions |
| 152 | +- Never change public properties without user interaction |
| 153 | +- Set `noAttribute: true` for private properties not used in CSS |
| 154 | +- Fire events upon every user interaction to notify applications |
| 155 | +- Import all icons explicitly (test bundle imports all, but real apps don't) |
| 156 | + |
| 157 | +## Testing with Cypress |
| 158 | + |
| 159 | +Tests use component testing with JSX mounting: |
| 160 | + |
| 161 | +```typescript |
| 162 | +import Button from "../../src/Button.js"; |
| 163 | + |
| 164 | +describe("Button", () => { |
| 165 | + it("tests click event", () => { |
| 166 | + cy.mount(<Button>Click me</Button>); |
| 167 | + |
| 168 | + cy.get("[ui5-button]").then(($btn) => { |
| 169 | + cy.spy($btn[0], "click").as("clicked"); |
| 170 | + }); |
| 171 | + |
| 172 | + cy.get("[ui5-button]").realClick(); // Use realClick, not click |
| 173 | + cy.get("@clicked").should("have.been.called"); |
| 174 | + }); |
| 175 | +}); |
| 176 | +``` |
| 177 | + |
| 178 | +**Key testing patterns:** |
| 179 | +- Use `cypress-real-events`: `realClick()`, `realPress()`, `realType()` instead of simulated events |
| 180 | +- Use `cy.ui5SimulateDevice("phone")` for mobile testing |
| 181 | +- For language testing: `cy.wrap({ setLanguage }).invoke("setLanguage", "bg")` (must await) |
| 182 | +- Import `Assets.js` when testing with non-default languages |
| 183 | + |
| 184 | +**Running single test case from a test file** |
| 185 | +When fixing a failing test, check the fix by running just the failing test case instead of all tests in the file. |
| 186 | + |
| 187 | +Example of failing test case: |
| 188 | +```tsx |
| 189 | + it("should set correct tooltip to right text button", () => { |
| 190 | +``` |
| 191 | +
|
| 192 | +Use `.only` to verify the fix faster |
| 193 | +```tsx |
| 194 | + it.only("should set correct tooltip to right text button", () => { |
| 195 | +``` |
| 196 | +
|
| 197 | +Do this for each failing test case, remove `.only` when all cases are fixed and run the full test file or suite for final verification |
| 198 | +
|
| 199 | +## Commit Message Format |
| 200 | +
|
| 201 | +Follow [Conventional Commits](https://conventionalcommits.org): |
| 202 | +
|
| 203 | +``` |
| 204 | +<type>(<scope>): <description> |
| 205 | + |
| 206 | +[body] |
| 207 | + |
| 208 | +[footer] |
| 209 | +``` |
| 210 | + |
| 211 | +**Types:** `fix`, `feat`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `revert` |
| 212 | +**Scope:** Component name (e.g., `ui5-button`, `ui5-table`) |
| 213 | +**Description:** Imperative present tense, max 100 chars, no period |
| 214 | + |
| 215 | +Example: |
| 216 | +``` |
| 217 | +fix(ui5-button): correct focus with 'tab' key |
| 218 | + |
| 219 | +The button should receive a correct focus outline |
| 220 | +when the 'tab' key is pressed. |
| 221 | + |
| 222 | +Fixes #42 |
| 223 | +``` |
0 commit comments