Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
70 changes: 70 additions & 0 deletions .cursor/rules/angular-components.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
description: Angular component authoring rules for Ion Design System.
globs: projects/ion/src/lib/**/*.component.ts, projects/ion/src/lib/**/*.directive.ts
alwaysApply: false
---

# Angular Component & Directive Rules

## Component Decorator

- Set `changeDetection: ChangeDetectionStrategy.OnPush` in every `@Component`
- Do NOT set `standalone: true` — it's default in Angular v21+
- Use `templateUrl` and `styleUrls` for components with more than a few lines of template
- Use inline `template` only for very small components (< ~10 lines of HTML)
- Use relative paths for templateUrl/styleUrls: `'./button.component.html'`

## Inputs & Outputs

- Use `input()` signal function instead of `@Input()` decorator
- Use `output()` signal function instead of `@Output()` decorator
- Type inputs explicitly: `label = input<string>()`
- Provide defaults where sensible: `type = input<Type>('primary')`
- Use `input.required<T>()` when the value must be provided

## State Management

- Use `signal()` for mutable component state
- Use `computed()` for derived state
- Use `effect()` for reactions to signal changes
- Update signals with `.set()` or `.update()` — NEVER use `.mutate()`

## Host Bindings

- Do NOT use `@HostBinding` or `@HostListener` decorators
- Use the `host` property in the decorator instead:

```typescript
@Component({
host: {
'[attr.disabled]': 'disabled() ? true : null',
'(click)': 'onClick()',
},
})
```

## Templates

- Use native control flow: `@if`, `@for`, `@switch` — NEVER `*ngIf`, `*ngFor`, `*ngSwitch`
- Use `[class.name]` bindings instead of `ngClass`
- Use `[style.prop]` bindings instead of `ngStyle`
- Use `[attr.data-testid]` for test selectors following pattern: `'btn-' + (label() || id())`
- Do NOT write arrow functions in templates
- Do NOT call `new Date()` or other globals directly in templates

## Forms

- Prefer Reactive Forms over Template-driven forms
- Import `ReactiveFormsModule` or individual form directives as needed

## Accessibility

- Must pass all AXE checks and WCAG AA requirements
- Include proper ARIA attributes where semantics are insufficient
- Manage focus for overlays, modals, dropdowns, and tooltips

## Services

- Use `providedIn: 'root'` for singleton services
- Use `inject()` function instead of constructor injection
- Design services around a single responsibility
63 changes: 63 additions & 0 deletions .cursor/rules/ion-project.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
description: Ion Design System - project context and conventions. Always active for all files in this repository.
globs:
alwaysApply: true
---

# Ion Design System — Project Context

You are working on **Ion**, the official Angular-based Design System for **Brisanet**.
This is a **library workspace** (`projectType: "library"`) published as `@brisanet/ion` on npm.

## Tech Stack

- **Angular** v21 (standalone components are the default — do NOT set `standalone: true`)
- **TypeScript** 5.9 with strict mode
- **SCSS** for component styles with a theme system under `projects/ion/src/styles/`
- **Angular CDK** (`@angular/cdk`) for overlays, portals, and scroll strategies
- **ng-packagr** for library builds
- **Jest** 30 + `jest-preset-angular` for unit testing
- **Angular Testing Library** (`@testing-library/angular`) for integration-style tests
- **Storybook** 10 with `@storybook/angular` for component documentation
- **Prettier** (single quotes, auto end-of-line)
- **EditorConfig** (2-space indent, UTF-8)

## Project Structure

```
projects/
ion/ # The core library
src/
lib/ # All component directories
<component-name>/ # One folder per component
<name>.component.ts
<name>.component.html
<name>.component.scss
<name>.component.spec.ts
_<name>.theme.scss # Optional theme file
core/
types/ # Shared TypeScript interfaces/types
bn-filter/ # Business components prefixed `bn-`
utils/ # Shared utility functions
styles/ # Global design tokens (colors, fonts, shadows, z-indexes, themes)
stories/ # Storybook stories
public-api.ts # Library barrel file — all public exports
ng-package.json
ion-test-app/ # Showcase / integration test app
```

## Key Conventions

- All components are **standalone** (do NOT add `standalone: true` — it's default in Angular v21+)
- Component selector prefix: `ion-` (e.g. `ion-button`, `ion-card`)
- Component class naming: `Ion<Name>Component` (e.g. `IonButtonComponent`)
- Service class naming: `Ion<Name>Service` or descriptive name (e.g. `IonModalService`, `TooltipService`)
- Directive class naming: `Ion<Name>Directive` (e.g. `IonTooltipDirective`)
- Type definitions live in `projects/ion/src/lib/core/types/<component>.ts`
- Every new public component/directive/service MUST be exported from `projects/ion/src/public-api.ts`

## CI/CD

- **PR tests**: GitHub Actions runs `npm test` + `npm run build ion` on every PR to `main`
- **Release**: Triggered on GitHub Release publish; builds and publishes to npm with provenance
- **Node version**: 22.22.0 for builds
66 changes: 66 additions & 0 deletions .cursor/rules/storybook.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
description: Storybook story authoring conventions for Ion components.
globs: projects/ion/src/stories/**/*.stories.ts, projects/ion/**/*.stories.ts
alwaysApply: false
---

# Storybook Rules

## Framework

- **Storybook 10** with `@storybook/angular`
- Stories live in `projects/ion/src/stories/` or alongside components

## Story Format

Use CSF3 (Component Story Format 3) with `Meta` and `StoryObj`:

```typescript
import type { Meta, StoryObj } from '@storybook/angular';
import { IonMyComponent } from '../lib/my/my.component';

const meta: Meta<IonMyComponent> = {
title: 'Components/MyComponent',
component: IonMyComponent,
tags: ['autodocs'],
argTypes: {
ionOnClick: { action: 'clicked' },
},
};

export default meta;

export const Default: StoryObj<IonMyComponent> = {
args: {
label: 'Click me',
type: 'primary',
},
};

export const Disabled: StoryObj<IonMyComponent> = {
args: {
label: 'Disabled',
disabled: true,
},
};
```

## Providing Services

If the component depends on services, use `applicationConfig`:

```typescript
render: (args) => ({
props: args,
applicationConfig: {
providers: [MyService],
},
}),
```

## Running Storybook

```bash
npm run storybook # Start dev server on port 6006
npm run build-storybook # Build static site to dist/storybook/ion
```
40 changes: 40 additions & 0 deletions .cursor/rules/styling.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
description: SCSS styling conventions and design tokens for Ion.
globs: projects/ion/**/*.scss
alwaysApply: false
---

# Styling Rules

## Language

- Use **SCSS** for all component styles
- Component styles: `<name>.component.scss` (external file, referenced via `styleUrls`)
- Theme files: `_<name>.theme.scss` (partial, imported by the theme system)

## Design Tokens

Design tokens are defined under `projects/ion/src/styles/`:

| Token Category | Location |
| -------------- | ----------------------------- |
| Colors | `styles/colors/colors.scss` |
| Fonts | `styles/fonts/index.scss` |
| Shadows | `styles/shadows/index.scss` |
| Z-indexes | `styles/z-indexes/index.scss` |
| Themes | `styles/themes/index.scss` |
| Overlays | `styles/_overlays.scss` |

Always use existing design tokens instead of hardcoding values.

## CSS Class Naming

- Component variant classes: `ion-btn-primary`, `ion-btn-secondary`
- Size classes: `ion-btn-sm`, `ion-btn-md`, `ion-btn-lg`, `ion-btn-xl`
- State classes: `danger`, `loading`, `right-side-icon`

## Theming

- Theme variables are defined in `styles/themes/`
- Components should use CSS custom properties (variables) from the theme
- Support light/dark themes through the existing theme infrastructure
101 changes: 101 additions & 0 deletions .cursor/rules/testing.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
description: Testing conventions for Ion Design System using Jest and Angular Testing Library.
globs: projects/ion/**/*.spec.ts
alwaysApply: false
---

# Testing Rules

## Framework

- **Jest 30** with `jest-preset-angular`
- **Angular Testing Library** (`@testing-library/angular`) available for integration-style tests
- **`@testing-library/jest-dom`** matchers are globally available (e.g. `toHaveAttribute`)

## Test File Location

- Test files live alongside their component: `<name>.component.spec.ts`
- Test match pattern: `projects/ion/**/*.spec.ts`

## Test Setup Pattern

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IonMyComponent } from './my.component';

describe('MyComponent', () => {
let component: IonMyComponent;
let fixture: ComponentFixture<IonMyComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [IonMyComponent], // standalone component goes in imports
}).compileComponents();

fixture = TestBed.createComponent(IonMyComponent);
component = fixture.componentInstance;
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
```

## Setting Inputs

- Use `fixture.componentRef.setInput('inputName', value)` — NOT direct property assignment
- Always call `fixture.detectChanges()` after setting inputs

```typescript
fixture.componentRef.setInput('label', 'Click me');
fixture.componentRef.setInput('disabled', true);
fixture.detectChanges();
```

## Querying DOM

- Use `fixture.nativeElement.querySelector()` for DOM queries
- Use `data-testid` attributes for reliable selectors: `[data-testid="btn-myId"]`
- For overlay/portal components (dropdown, modal, tooltip), query from `document.body`

## Event Testing

- Use `jest.fn()` for spy functions
- Subscribe to component outputs for event testing
- Call `.click()` on native elements to trigger events

```typescript
const clickSpy = jest.fn();
component.ionOnClick.subscribe(clickSpy);
fixture.detectChanges();

const button = fixture.nativeElement.querySelector('button');
button.click();

expect(clickSpy).toHaveBeenCalled();
```

## Iteration Tests

- Use `.forEach()` to test multiple variants (types, sizes, states):

```typescript
const types = ['primary', 'secondary', 'ghost', 'dashed'];
types.forEach((type) => {
it(`should render type: ${type}`, () => {
fixture.componentRef.setInput('type', type);
fixture.detectChanges();
const el = fixture.nativeElement.querySelector('button');
expect(el.classList.contains(`ion-btn-${type}`)).toBe(true);
});
});
```

## Running Tests

```bash
npm test # Run all tests
npx jest --testPathPattern=button # Run specific tests
npx jest --coverage # With coverage report
```
Loading