Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
30 changes: 15 additions & 15 deletions .claude/skills/contributing/references/best-practices.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Two patterns exist for this — pick by need:

### Pattern A: Per-input `BreakpointInput<T>` (preferred when one or two inputs need to be responsive)

Use this when a single input (e.g., a flag, count, or variant) should vary by breakpoint without restating sibling inputs. Reference: `tedi/components/content/carousel/carousel-content/carousel-content.component.ts:46-55`, `tedi/components/form/time-field/time-field.component.ts` (`useNativePicker`).
Use this when a single input (e.g., a flag, count, or variant) should vary by breakpoint without restating sibling inputs. Reference: `src/tedi/components/content/carousel/carousel-content/carousel-content.component.ts:46-55`, `src/tedi/components/form/time-field/time-field.component.ts` (`useNativePicker`).

```typescript
import {
Expand Down Expand Up @@ -118,7 +118,7 @@ Notes:

### Pattern B: Per-component breakpoint inputs (preferred when many inputs need to be responsive together)

Use this when several inputs commonly change together at a given breakpoint and consumers benefit from grouping them. Reference: `tedi/components/navigation/link/link.component.ts:55-105`.
Use this when several inputs commonly change together at a given breakpoint and consumers benefit from grouping them. Reference: `src/tedi/components/navigation/link/link.component.ts:55-105`.

```typescript
export type LinkInputs = {
Expand Down Expand Up @@ -429,27 +429,27 @@ export { ComponentNameType } from './component-name.types'; // if applicable
```

### Register in Category `index.ts`
Add the new component export to the parent category barrel file (e.g., `tedi/components/form/index.ts`).
Add the new component export to the parent category barrel file (e.g., `src/tedi/components/form/index.ts`).

## Key File Locations

### Services
- Translation: `tedi/services/translation/translation.service.ts` (root-provided)
- Theme: `tedi/services/theme/theme.service.ts` (root-provided)
- Toast: `tedi/services/toast/toast.service.ts` (root-provided, has static state)
- Breakpoint: `tedi/services/breakpoint/breakpoint.service.ts`
- Translation: `src/tedi/services/translation/translation.service.ts` (root-provided)
- Theme: `src/tedi/services/theme/theme.service.ts` (root-provided)
- Toast: `src/tedi/services/toast/toast.service.ts` (root-provided, has static state)
- Breakpoint: `src/tedi/services/breakpoint/breakpoint.service.ts`

### Configuration
- App-level provider: `tedi/providers/tedi.provider.ts` → `provideTedi(config)`
- Translation token: `tedi/tokens/translation.token.ts`
- Theme token: `tedi/tokens/theme.token.ts`
- Translations map: `tedi/services/translation/translations.ts` (et, en, ru)
- App-level provider: `src/tedi/providers/tedi.provider.ts` → `provideTedi(config)`
- Translation token: `src/tedi/tokens/translation.token.ts`
- Theme token: `src/tedi/tokens/theme.token.ts`
- Translations map: `src/tedi/services/translation/translations.ts` (et, en, ru)

### Utilities
- Date formatting: `tedi/utils/date.util.ts`
- Cookie signal: `tedi/utils/cookies.util.ts`
- DOM helpers: `tedi/utils/elements.util.ts`
- UUID generation: `tedi/helpers/generate-uuid.ts`
- Date formatting: `src/tedi/utils/date.util.ts`
- Cookie signal: `src/tedi/utils/cookies.util.ts`
- DOM helpers: `src/tedi/utils/elements.util.ts`
- UUID generation: `src/tedi/helpers/generate-uuid.ts`

## Known Quirks
- `toggle.component.ts` still uses old `@ViewChild` decorator — should be migrated to `viewChild()` signal
Expand Down
8 changes: 4 additions & 4 deletions .claude/skills/contributing/references/new-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ If no Figma link was provided, stop immediately and ask the user for one. Do not
Enter plan mode and create a detailed plan covering:

- **Component name** and selector (`tedi-` prefix)
- **Category** — which folder under `tedi/components/` it belongs to
- **Category** — which folder under `src/tedi/components/` it belongs to
- **API design** — all inputs (with types and defaults), outputs, content projection slots
- **Responsive inputs** — for each input, check if the React equivalent uses `BreakpointSupport<T>` or `BreakpointInput<T>`. If yes, plan the Angular equivalent (see "Responsive Inputs (Breakpoint Support)" in best-practices.md for pattern A vs B). Even without a React reference, ask: would consumers reasonably need to vary this input per breakpoint? If so, add breakpoint support up front — retrofitting later is a breaking change.
- **Accessibility** — ARIA roles, keyboard interactions, screen reader behavior, focus management
Expand All @@ -30,7 +30,7 @@ If a new dependency is needed, stop and ask the user for permission.

## Step 3: Scaffold Files

Create the following files in `tedi/components/<category>/<component-name>/`:
Create the following files in `src/tedi/components/<category>/<component-name>/`:

```text
component-name.component.ts
Expand All @@ -53,7 +53,7 @@ Follow all patterns from best-practices:
## Step 5: Export

1. Create barrel export in `index.ts`
2. Add export to the parent category `index.ts` (e.g., `tedi/components/form/index.ts`)
2. Add export to the parent category `index.ts` (e.g., `src/tedi/components/form/index.ts`)

## Step 6: Code Review

Expand All @@ -63,7 +63,7 @@ A finding is **valid** when it is confirmed against the current code (not stale

## Step 7: Verify

1. Run tests: `npx jest tedi/components/<category>/<component-name>/`
1. Run tests: `npx jest src/tedi/components/<category>/<component-name>/`
2. Fix any failures.
3. Run lint: `npm run lint`
4. Fix any lint errors.
Expand Down
82 changes: 69 additions & 13 deletions .claude/skills/contributing/references/stories.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,17 @@ Rules:

### 3. Determine the Story Category

Find where the component lives under `tedi/components/` and map to the Storybook title:
Find where the component lives under `src/tedi/components/` and map to the Storybook title:

| Component path | Story title prefix |
|---|---|
| `tedi/components/form/` | `TEDI-Ready/Components/Form/` |
| `tedi/components/buttons/` | `TEDI-Ready/Components/Buttons/` |
| `tedi/components/overlay/` | `TEDI-Ready/Components/Overlay/` |
| `tedi/components/navigation/` | `TEDI-Ready/Components/Navigation/` |
| `tedi/components/content/` | `TEDI-Ready/Content/` (no `Components/` segment) |
| `tedi/components/layout/` | `TEDI-Ready/Layout/` (no `Components/` segment) |
| `tedi/components/base/` | `TEDI-Ready/Base/` (no `Components/` segment) |
| `src/tedi/components/form/` | `TEDI-Ready/Components/Form/` |
| `src/tedi/components/buttons/` | `TEDI-Ready/Components/Buttons/` |
| `src/tedi/components/overlay/` | `TEDI-Ready/Components/Overlay/` |
| `src/tedi/components/navigation/` | `TEDI-Ready/Components/Navigation/` |
| `src/tedi/components/content/` | `TEDI-Ready/Content/` (no `Components/` segment) |
| `src/tedi/components/layout/` | `TEDI-Ready/Layout/` (no `Components/` segment) |
| `src/tedi/components/base/` | `TEDI-Ready/Base/` (no `Components/` segment) |
| Other category | `TEDI-Ready/Components/<Category>/` |

**Note:** the `Content`, `Layout`, and `Base` groups sit directly under `TEDI-Ready/` — they skip the `Components/` segment. Check sibling stories in the same folder before picking a title.
Expand Down Expand Up @@ -150,17 +150,73 @@ If the component also has a Zeroheight page, add it on the next line with `<br>`

Applies equally to new components and retrofits — if an existing story lacks the link, add it.

### 5. Story Checklist
### 5. Make the `Default` Story's Controls Functional

**The `Default` story's controls must actually drive the rendered component — always, including when the component is nested or composed of sub-components.** Changing a control in the panel must visibly change the rendered output. A `Default` story whose template ignores `args` (so the controls do nothing) is wrong, even if it looks correct at a glance.

**Bind args directly as template props in `render` — never build a host-wrapper component.** Storybook passes args into the `props` object and re-renders on every control change; template expressions reference the arg names directly.

```typescript
export const Default: StoryObj<ComponentName> = {
args: { variant: 'primary', size: 'medium', disabled: false },
render: (args) => ({
props: { ...args },
template: `<tedi-component [variant]="variant" [size]="size" [disabled]="disabled" />`,
}),
};
```

Prefer `argsToTemplate(args)` to forward every arg as a binding without restating each one — this keeps the story functional as inputs are added:

```typescript
import { argsToTemplate } from '@storybook/angular';

render: (args) => ({
props: { ...args },
template: `<tedi-component ${argsToTemplate(args)} />`,
}),
```

**Nested / composed components.** The args belong to the **outer component under test** (`component:` in the default export). Bind them to that component's element and let it pass them down to its sub-components internally — do **not** redirect the controls to a child.

- If the rendered example wraps the component or projects content, still bind the args to the component being documented, and bind any arg-dependent projected content too:

```typescript
// Component under test is <tedi-card>, which composes header/body sub-components.
render: (args) => ({
props: { ...args },
template: `
<tedi-card ${argsToTemplate(args)}>
<tedi-card-content>
<p tedi-text [modifiers]="textModifier">{{ label }}</p>
</tedi-card-content>
</tedi-card>
`,
}),
```

- For two-way / local state (e.g. selection, open/close), keep a local `signal` in `props` and bind it via `[value]`/`(valueChange)` — leave the rest of the args spread in:

```typescript
render: (args) => ({
props: { ...args, value: signal('2') },
template: `<tedi-select ${argsToTemplate(args)} [value]="value()" (valueChange)="value.set($event)" />`,
}),
```

**Showcase stories are the exception.** Stories that render several fixed configurations at once (all variants/sizes/states) should **disable** the panel rather than fake controls: `parameters: { controls: { disable: true } }`. Only the `Default` (single-instance) story must have working controls.

### 6. Story Checklist

- [ ] Every Figma section has a corresponding story export, in the same order
- [ ] Example content (labels, data, item count) matches Figma exactly
- [ ] Every public input/model has a corresponding `argTypes` entry with description, control, type summary, and default value
- [ ] `Default` story has all controls wired up via `args`
- [ ] `Default` story has all controls wired up via `args` and they **functionally drive the rendered component** — verified by changing a control and seeing the output update, including for nested/composed components (see section 5)
- [ ] States story covers all visual states shown in Figma (default, hover, active, focus, disabled)
- [ ] Reactive forms example included if the component implements ControlValueAccessor
- [ ] Figma link is in the JSDoc comment above `export default` (format: `<a href="..." target="_blank">Figma ↗</a>`)

### 6. argTypes Convention
### 7. argTypes Convention

**Every public input/model must have an argTypes entry.** Do not skip any — all props must appear in the Storybook controls panel with correct typing and descriptions.

Expand All @@ -187,7 +243,7 @@ argTypes: {
}
```

### 7. Verify
### 8. Verify

Run Storybook to visually confirm stories render correctly:
```bash
Expand All @@ -198,5 +254,5 @@ Check that:
- All stories appear in the correct category
- Story order matches Figma section order
- Example content matches Figma
- Controls work interactively
- Controls work interactively — change a control in the `Default` story and confirm the rendered component updates (including nested/composed components)
- No console errors
6 changes: 3 additions & 3 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ const config: StorybookConfig = {
"../src/docs/css-utilities.mdx",
"../src/docs/colors/colors.mdx",
"../src/docs/**/*.stories.@(js|jsx|mjs|ts|tsx)",
"../tedi/**/*.stories.@(js|jsx|mjs|ts|tsx)",
"../tedi/**/*.mdx",
"../community/**/*.stories.@(js|jsx|mjs|ts|tsx)",
"../src/tedi/**/*.stories.@(js|jsx|mjs|ts|tsx)",
"../src/tedi/**/*.mdx",
"../src/community/**/*.stories.@(js|jsx|mjs|ts|tsx)",
],
addons: [
"@storybook/addon-docs",
Expand Down
8 changes: 4 additions & 4 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { applicationConfig, Preview, StoryContext } from "@storybook/angular";
import { provideRouter, withDisabledInitialNavigation } from "@angular/router";
import { Theme } from "../tedi/services/theme/theme.service";
import { Theme } from "../src/tedi/services/theme/theme.service";
import {
Controls,
Description,
Expand All @@ -9,9 +9,9 @@ import {
Subtitle,
Title,
} from "@storybook/addon-docs/blocks";
import { TEDI_TRANSLATION_DEFAULT_TOKEN } from "../tedi/tokens/translation.token";
import { TEDI_THEME_DEFAULT_TOKEN } from "../tedi/tokens/theme.token";
import { THEME_FALLBACK_VALUE } from "../tedi/services/theme/theme.service";
import { TEDI_TRANSLATION_DEFAULT_TOKEN } from "../src/tedi/tokens/translation.token";
import { TEDI_THEME_DEFAULT_TOKEN } from "../src/tedi/tokens/theme.token";
import { THEME_FALLBACK_VALUE } from "../src/tedi/services/theme/theme.service";

export const globalTypes = {
theme: {
Expand Down
26 changes: 14 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ npm run build # Build library to dist/

### Directory Layout

- `tedi/components/` — All UI components, organized by category (form, buttons, overlay, etc.)
- `tedi/directives/` — Attribute/structural directives
- `tedi/services/` — Services (translation, theme, breakpoint, toast)
- `tedi/utils/` — Utility functions (date, cookies, elements)
- `tedi/tokens/` — Injection tokens (theme, translation)
- `tedi/providers/` — Angular providers
- `community/` — Community-contributed components (separate entry point, NOT a reference for TEDI patterns)
- `src/tedi/components/` — All UI components, organized by category (form, buttons, overlay, etc.)
- `src/tedi/directives/` — Attribute/structural directives
- `src/tedi/services/` — Services (translation, theme, breakpoint, toast)
- `src/tedi/utils/` — Utility functions (date, cookies, elements)
- `src/tedi/tokens/` — Injection tokens (theme, translation)
- `src/tedi/providers/` — Angular providers
- `src/community/` — Community-contributed components (separate entry point, NOT a reference for TEDI patterns)

> The library lives under `src/` (`src/tedi`, `src/community`), with `src/package.json` + `src/ng-package.json` as the ng-packagr primary entry point. The root `package.json` is the workspace/tooling manifest. This mirrors the React repo's `src/` layout — see issue #166.

### Component File Convention

Expand All @@ -54,11 +56,11 @@ Form controls implement `ControlValueAccessor` with `NG_VALUE_ACCESSOR` provider

### Export Chain

`public-api.ts` → `tedi/index.ts` → category `index.ts` → component `index.ts`
`src/public-api.ts` → `src/tedi/index.ts` → category `index.ts` → component `index.ts`

Path aliases:
- `@tedi-design-system/angular/tedi` → `./tedi/index.ts`
- `@tedi-design-system/angular/community` → `./community/index.ts`
- `@tedi-design-system/angular/tedi` → `./src/tedi/index.ts`
- `@tedi-design-system/angular/community` → `./src/community/index.ts`

## Styling

Expand All @@ -79,7 +81,7 @@ Path aliases:

## Storybook

- Stories at `tedi/**/*.stories.ts`
- Stories at `src/tedi/**/*.stories.ts`
- Title format: `"TEDI-Ready/Components/<Category>/<ComponentName>"` (e.g. `Buttons`, `Form`, `Navigation`). Top-level groups `Content`, `Layout`, and `Base` skip the `Components` segment — `"TEDI-Ready/Content/<ComponentName>"`, etc. Check sibling stories in the same folder to confirm the prefix.
- Uses `moduleMetadata` decorator for imports
- Status parameters: `partiallyTediReady`, `existsInTediReady`, `devComponent`, etc.
Expand Down Expand Up @@ -117,7 +119,7 @@ Path aliases:
- Use regular methods for event handlers that need cleanup — use `handleX = (e: Event) => {}` property syntax
- Forget to provide `TediTranslationService` mock and `TEDI_TRANSLATION_DEFAULT_TOKEN` in tests
- Use `fakeAsync`/`tick` without cleaning up in `afterEach`
- Use `community/` components as reference for coding patterns or style — they are community-contributed and not always reviewed
- Use `src/community/` components as reference for coding patterns or style — they are community-contributed and not always reviewed
- Forget to add `.parent__button:hover .tedi-icon { color: inherit; }` (and similar for selected/active states) in components that contain icons and change text color on state — without this, the icon's color modifier class wins over the parent's color. Use `color="inherit"` for component-owned icons; for projected icons, the CSS override ensures the consumer's color applies in default state but inherits on hover/selected
- Style Angular element selectors directly (e.g., `tedi-modal-header { ... }`) — add a CSS class to the host and style the class instead. Exception: third-party elements you can't add classes to (e.g., `cdk-dialog-container`)

Expand Down
10 changes: 5 additions & 5 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"build": {
"builder": "@angular/build:ng-packagr",
"options": {
"project": "ng-package.json",
"project": "src/ng-package.json",
"tsConfig": "tsconfig.json"
},
"configurations": {
Expand Down Expand Up @@ -63,10 +63,10 @@
"lintFilePatterns": [
"./src/docs/**/*.ts",
"./src/docs/**/*.html",
"./tedi/**/*.ts",
"./tedi/**/*.html",
"./community/**/*.ts",
"./community/**/*.html"
"./src/tedi/**/*.ts",
"./src/tedi/**/*.html",
"./src/community/**/*.ts",
"./src/community/**/*.html"
]
}
}
Expand Down
6 changes: 0 additions & 6 deletions community/ng-package.json

This file was deleted.

2 changes: 1 addition & 1 deletion jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default {
// in jest 29 workers on recent Node versions
workerIdleMemoryLimit: "1GB",
collectCoverage: true,
collectCoverageFrom: ["./tedi/components/**/*.{js,ts,tsx}"],
collectCoverageFrom: ["./src/tedi/components/**/*.{js,ts,tsx}"],
coveragePathIgnorePatterns: ["\\.stories\\.ts$"],
coverageThreshold: {
global: {
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"build": "ng build angular-components --configuration production && sass --load-path=node_modules src/styles/index.scss dist/index.css --style=compressed --no-source-map && replace-in-file //fonts//g \"./fonts/\" dist/index.css --isRegex && mkdir -p dist/fonts && cp -a node_modules/@tedi-design-system/core/fonts/* dist/fonts/",
"build:sb": "ng run angular-components:build-storybook && replace-in-file //fonts//g \"./fonts/\" \"dist/storybook-static/*.css\" --isRegex",
"build:sb:chromatic": "ng run angular-components:build-storybook",
"lint": "stylelint \"tedi/**/*.scss\" --fix && ng lint angular-components --fix",
"lint": "stylelint \"src/tedi/**/*.scss\" --fix && ng lint angular-components --fix",
"test": "jest --passWithNoTests",
"test:watch": "jest --watch",
"test:coverage": "jest --config ./jest.config.ts --coverage",
Expand Down Expand Up @@ -117,10 +117,10 @@
}
},
"lint-staged": {
"{src,tedi}/**/*.{css,scss}": [
"src/!(community)/**/*.{css,scss}": [
"stylelint --fix"
],
"src/**/*.{ts,html}": [
"src/!(community)/**/*.{ts,html}": [
"prettier --write"
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
StoryObj,
} from "@storybook/angular";
import { FloatingButtonComponent } from "./floating-button.component";
import { IconComponent } from "tedi/components";
import { IconComponent } from "../../../../tedi/components";

const buttonSizeArray = ["default", "large"];
const buttonStateArray = ["Default", "Hover", "Active", "Focus"];
Expand Down Expand Up @@ -83,7 +83,7 @@
}),
};

export const sizesVertical: Story = {

Check warning on line 86 in src/community/components/buttons/floating-button/floating-button.stories.ts

View workflow job for this annotation

GitHub Actions / lint

The story should use PascalCase notation: sizesVertical
...Default,
args: {
axis: "vertical",
Expand All @@ -108,7 +108,7 @@
}),
};

export const statesVertical: Story = {

Check warning on line 111 in src/community/components/buttons/floating-button/floating-button.stories.ts

View workflow job for this annotation

GitHub Actions / lint

The story should use PascalCase notation: statesVertical
...Default,
args: {
axis: "vertical",
Expand All @@ -133,7 +133,7 @@
}),
};

export const sizesHorizontal: Story = {

Check warning on line 136 in src/community/components/buttons/floating-button/floating-button.stories.ts

View workflow job for this annotation

GitHub Actions / lint

The story should use PascalCase notation: sizesHorizontal
...sizesVertical,
args: {
axis: "horizontal",
Expand All @@ -141,7 +141,7 @@
},
};

export const statesHorizontal: Story = {

Check warning on line 144 in src/community/components/buttons/floating-button/floating-button.stories.ts

View workflow job for this annotation

GitHub Actions / lint

The story should use PascalCase notation: statesHorizontal
...statesVertical,
args: {
axis: "horizontal",
Expand Down
Loading
Loading