@eagami/ui 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,388 +1,388 @@
1
- # @eagami/ui
2
-
3
- A lightweight, accessible Angular UI component library built on CSS custom properties. Zero runtime dependencies beyond Angular itself.
4
-
5
1
  <p align="center">
6
- <img src="logo.svg" alt="eagami-ui logo" width="150" />
2
+ <img src="docs/images/header.png" alt="eagami design system — elegant web design." width="560" />
7
3
  </p>
8
4
 
5
+ A lightweight, accessible Angular component library built on CSS custom properties. Ready to use out of the box — install, import, and start building.
6
+
7
+ Every component is standalone, signal-based, and fully themed via design tokens. No wrapping modules, no complex setup, no runtime style conflicts. Designed to be AI-friendly with clear APIs, consistent patterns, and comprehensive documentation that makes it easy for both developers and AI assistants to work with.
8
+
9
9
  ## Why @eagami/ui?
10
10
 
11
- | | **@eagami/ui** | Angular Material | PrimeNG | ng-bootstrap | ng-zorro (Ant Design) |
11
+ **Approx. component sizes (gzipped)¹**
12
+
13
+ | Component | **@eagami/ui** | Angular Material | PrimeNG | ng-bootstrap | ng-zorro |
12
14
  |---|---|---|---|---|---|
13
- | Approx. bundle (Button + Input, gzipped)¹ | ~8 KB | ~60 KB | ~35 KB | ~55 KB | ~120 KB |
14
- | External CSS dependency | | | Optional | Bootstrap (~30 KB) | |
15
- | CSS custom property theming | | Partial (MDC) | | (Sass vars) | ❌ |
16
- | Signals-first API | | Partial | | | |
17
- | `OnPush` by default | | Partial | | | |
18
- | Runtime dependencies | 0 | CDK + animations | PrimeIcons² | Bootstrap CSS | CDK |
15
+ | Button | ~2 KB | ~12 KB | ~8 KB | ~10 KB | ~18 KB |
16
+ | Input | ~4 KB | ~25 KB | ~14 KB | ~20 KB | ~35 KB |
17
+ | Checkbox | ~2 KB | ~15 KB | ~9 KB | ~12 KB | ~22 KB |
18
+ | Dropdown / Select | ~4 KB | ~30 KB | ~20 KB | ~25 KB | ~40 KB |
19
+ | Dialog / Modal | ~2 KB | ~20 KB | ~15 KB | ~18 KB | ~30 KB |
19
20
 
20
- > ¹ All numbers are approximate and depend on configuration, tree-shaking, and Angular version. Measured with production builds and `@angular/build`.
21
+ > ¹ Approximate depends on configuration, tree-shaking, and Angular version. @eagami/ui sizes measured from production build.
21
22
 
23
+ | | **@eagami/ui** | Angular Material | PrimeNG | ng-bootstrap | ng-zorro |
24
+ |---|---|---|---|---|---|
25
+ | External CSS dependency | No | No | Optional | Bootstrap (~30 KB) | No |
26
+ | CSS custom property theming | Yes | Partial (MDC) | Yes | No (Sass vars) | No |
27
+ | Signals-first API | Yes | Partial | No | No | No |
28
+ | `OnPush` by default | Yes | Partial | No | No | No |
29
+ | Runtime dependencies | 0 | CDK + animations | PrimeIcons² | Bootstrap CSS | CDK |
22
30
  > ² PrimeNG components are tree-shakable but PrimeIcons font (~50 KB) is typically included globally.
23
31
 
24
- ## Requirements
32
+ ## Features
25
33
 
26
- - Angular 21+
27
- - A CSS preprocessor that supports SCSS (standard with Angular CLI)
34
+ - **Zero configuration** — works immediately after install with sensible defaults
35
+ - **Standalone components** no `NgModule` boilerplate, just import and use
36
+ - **Signal-based** — built on Angular's modern reactivity primitives (`input()`, `model()`, `output()`)
37
+ - **Full theming via CSS custom properties** — override any design token on `:root` or scope overrides to individual components
38
+ - **Dark mode built in** — automatic via `prefers-color-scheme`, no extra setup
39
+ - **Accessible** — ARIA attributes, keyboard navigation, focus management, and screen reader support throughout
40
+ - **Form-ready** — `ControlValueAccessor` on all form components (Input, Textarea, Checkbox, Switch, Radio, Dropdown)
41
+ - **Lightweight** — no third-party runtime dependencies beyond Angular and `tslib`
42
+ - **Tree-shakeable** — only the components you import end up in your bundle
28
43
 
29
44
  ## Installation
30
45
 
31
- ```sh
46
+ ```bash
32
47
  npm install @eagami/ui
48
+ # or
49
+ pnpm add @eagami/ui
33
50
  ```
34
51
 
35
- ## Setup
36
-
37
- ### 1. Import the global stylesheet
52
+ Add the global stylesheet to your `angular.json` (or import it in your root SCSS):
38
53
 
39
- In `angular.json`:
40
54
  ```json
41
55
  "styles": ["node_modules/@eagami/ui/src/styles/eagami-ui.scss"]
42
56
  ```
43
57
 
44
- Or in your root `styles.scss`:
45
- ```scss
46
- @use '@eagami/ui/src/styles/eagami-ui';
47
- ```
58
+ Load the fonts in your `index.html`:
48
59
 
49
- ### 2. Import components
60
+ ```html
61
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
62
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
63
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&family=Syne:wght@400;500;600;700&display=swap" />
64
+ ```
50
65
 
51
- Components are standalone — import only what you use:
66
+ ## Quick start
52
67
 
53
- ```ts
54
- import { ButtonComponent, InputComponent, CheckboxComponent } from '@eagami/ui';
68
+ ```typescript
69
+ import { ButtonComponent } from '@eagami/ui';
55
70
 
56
71
  @Component({
57
- imports: [ButtonComponent, InputComponent, CheckboxComponent],
58
- ...
72
+ imports: [ButtonComponent],
73
+ template: `<ea-button variant="primary" (clicked)="save()">Save</ea-button>`,
59
74
  })
75
+ export class MyComponent {
76
+ save() { /* ... */ }
77
+ }
60
78
  ```
61
79
 
62
- ## Usage
80
+ No modules to register, no providers to configure. Every component works the same way — import it, drop it in your template.
63
81
 
64
- ### Button
82
+ ## Components
65
83
 
66
- ```html
67
- <ea-button variant="primary" (clicked)="onSave($event)">Save</ea-button>
84
+ ### Button
68
85
 
69
- <ea-button variant="secondary" size="sm" [disabled]="true">Cancel</ea-button>
86
+ Variants: `primary` | `secondary` | `ghost` | `danger`. Sizes: `sm` | `md` | `lg`. Supports `loading`, `disabled`, and `fullWidth` states.
70
87
 
71
- <ea-button variant="danger" [loading]="isDeleting()">Delete</ea-button>
88
+ ```html
89
+ <ea-button variant="primary" size="md" [loading]="isSaving" (clicked)="save()">
90
+ Save changes
91
+ </ea-button>
72
92
  ```
73
93
 
74
- **Inputs**
75
-
76
- | Input | Type | Default | Description |
77
- |---|---|---|---|
78
- | `variant` | `primary \| secondary \| ghost \| danger` | `primary` | Visual style |
79
- | `size` | `sm \| md \| lg` | `md` | Button size |
80
- | `type` | `button \| submit \| reset` | `button` | Native button type |
81
- | `disabled` | `boolean` | `false` | Disables the button |
82
- | `loading` | `boolean` | `false` | Shows a spinner, disables interaction |
83
- | `fullWidth` | `boolean` | `false` | Stretches to fill container |
84
- | `aria-label` | `string` | — | Accessible label for icon-only buttons |
85
-
86
- **Outputs**
87
-
88
- | Output | Type | Description |
89
- |---|---|---|
90
- | `clicked` | `MouseEvent` | Emitted on click (not emitted when disabled or loading) |
94
+ <img src="docs/images/button.png" alt="Button component" width="560" />
91
95
 
92
96
  ---
93
97
 
94
98
  ### Input
95
99
 
100
+ Full `ControlValueAccessor` support. Types: `text` | `email` | `password` | `number` | `search` | `tel` | `url`. Built-in password visibility toggle.
101
+
96
102
  ```html
97
103
  <ea-input
98
104
  label="Email"
99
105
  type="email"
100
106
  placeholder="you@example.com"
107
+ hint="We'll never share your email"
101
108
  [(value)]="email" />
102
-
103
- <ea-input
104
- label="Password"
105
- type="password"
106
- hint="At least 8 characters"
107
- [required]="true" />
108
-
109
- <ea-input
110
- label="Username"
111
- error="This username is already taken"
112
- [(value)]="username" />
113
109
  ```
114
110
 
115
- Works with Angular reactive forms and template-driven forms via `ControlValueAccessor`:
111
+ <img src="docs/images/input.png" alt="Input component" width="560" />
116
112
 
117
- ```ts
118
- // Reactive
119
- this.form = this.fb.group({ email: ['', Validators.required] });
120
- ```
121
- ```html
122
- <ea-input label="Email" formControlName="email" />
123
- ```
113
+ ---
124
114
 
125
- **Inputs**
115
+ ### Textarea
126
116
 
127
- | Input | Type | Default | Description |
128
- |---|---|---|---|
129
- | `label` | `string` | — | Field label |
130
- | `type` | `text \| email \| password \| number \| search \| tel \| url` | `text` | Input type |
131
- | `placeholder` | `string` | `''` | Placeholder text |
132
- | `size` | `sm \| md \| lg` | `md` | Field size |
133
- | `status` | `default \| error \| success` | `default` | Visual validation state |
134
- | `hint` | `string` | — | Helper text below the input |
135
- | `error` | `string` | — | Error message (also sets status to `error`) |
136
- | `disabled` | `boolean` | `false` | Disables the input |
137
- | `readonly` | `boolean` | `false` | Makes the input read-only |
138
- | `required` | `boolean` | `false` | Marks as required |
117
+ Mirrors the Input API with `ControlValueAccessor`. Configurable `rows`, `resize` (`none` | `vertical` | `horizontal` | `both`), and `maxlength`.
139
118
 
140
- **Two-way binding**
119
+ ```html
120
+ <ea-textarea
121
+ label="Message"
122
+ placeholder="Enter your message…"
123
+ hint="Maximum 500 characters"
124
+ [rows]="4"
125
+ [(value)]="message" />
126
+ ```
141
127
 
142
- | Binding | Type | Description |
143
- |---|---|---|
144
- | `[(value)]` | `string` | Two-way signal-based value binding |
128
+ <img src="docs/images/textarea.png" alt="Textarea component" width="560" />
145
129
 
146
130
  ---
147
131
 
148
132
  ### Checkbox
149
133
 
150
- ```html
151
- <ea-checkbox label="Accept terms" [(checked)]="accepted" />
134
+ `ControlValueAccessor` with `indeterminate` state support. Sizes: `sm` | `md` | `lg`.
152
135
 
153
- <ea-checkbox label="Select all" [indeterminate]="true" />
154
-
155
- <ea-checkbox label="Disabled" [disabled]="true" />
136
+ ```html
137
+ <ea-checkbox label="Accept terms and conditions" [(checked)]="accepted" />
156
138
  ```
157
139
 
158
- Works with Angular forms via `ControlValueAccessor`.
159
-
160
- **Inputs**
140
+ <img src="docs/images/checkbox.png" alt="Checkbox component" width="560" />
161
141
 
162
- | Input | Type | Default | Description |
163
- |---|---|---|---|
164
- | `label` | `string` | — | Checkbox label |
165
- | `size` | `sm \| md \| lg` | `md` | Checkbox size |
166
- | `disabled` | `boolean` | `false` | Disables the checkbox |
167
- | `required` | `boolean` | `false` | Marks as required |
168
- | `indeterminate` | `boolean` | `false` | Shows indeterminate (minus) state |
142
+ ---
169
143
 
170
- **Two-way binding**
144
+ ### Switch
171
145
 
172
- | Binding | Type | Description |
173
- |---|---|---|
174
- | `[(checked)]` | `boolean` | Two-way checked state |
146
+ Toggle switch with `ControlValueAccessor`. Sizes: `sm` | `md` | `lg`.
175
147
 
176
- **Outputs**
148
+ ```html
149
+ <ea-switch label="Enable notifications" [(checked)]="notificationsOn" />
150
+ ```
177
151
 
178
- | Output | Type | Description |
179
- |---|---|---|
180
- | `changed` | `boolean` | Emitted when the checked state changes |
152
+ <img src="docs/images/switch.png" alt="Switch component" width="560" />
181
153
 
182
154
  ---
183
155
 
184
- ### Radio
156
+ ### Radio Group
185
157
 
186
- Radio buttons are composed of a `ea-radio-group` wrapper and `ea-radio` items:
158
+ Composite pattern with `ControlValueAccessor`. Supports `vertical` and `horizontal` orientation.
187
159
 
188
160
  ```html
189
- <ea-radio-group [(value)]="colour">
190
- <ea-radio value="red" label="Red" />
191
- <ea-radio value="green" label="Green" />
192
- <ea-radio value="blue" label="Blue" />
161
+ <ea-radio-group [(value)]="plan">
162
+ <ea-radio value="free" label="Free" />
163
+ <ea-radio value="pro" label="Pro" />
164
+ <ea-radio value="enterprise" label="Enterprise" />
193
165
  </ea-radio-group>
194
166
  ```
195
167
 
196
- The group implements `ControlValueAccessor` for Angular forms integration.
197
-
198
- **RadioGroup Inputs**
168
+ <img src="docs/images/radio.png" alt="Radio group component" width="560" />
199
169
 
200
- | Input | Type | Default | Description |
201
- |---|---|---|---|
202
- | `name` | `string` | auto | Shared name for all radios |
203
- | `size` | `sm \| md \| lg` | `md` | Size of all radios |
204
- | `orientation` | `vertical \| horizontal` | `vertical` | Layout direction |
205
- | `disabled` | `boolean` | `false` | Disables all radios |
170
+ ---
206
171
 
207
- **RadioGroup Two-way binding**
172
+ ### Dropdown
208
173
 
209
- | Binding | Type | Description |
210
- |---|---|---|
211
- | `[(value)]` | `string` | Currently selected value |
174
+ Select dropdown with `ControlValueAccessor` and keyboard navigation (Arrow keys, Enter/Space, Escape).
212
175
 
213
- **Radio Inputs**
176
+ ```html
177
+ <ea-dropdown
178
+ label="Country"
179
+ placeholder="Select a country…"
180
+ [options]="countries"
181
+ [(value)]="selectedCountry" />
182
+ ```
214
183
 
215
- | Input | Type | Default | Description |
216
- |---|---|---|---|
217
- | `value` | `string` | *required* | Option value |
218
- | `label` | `string` | — | Radio label |
219
- | `disabled` | `boolean` | `false` | Disables this radio |
184
+ <img src="docs/images/dropdown.png" alt="Dropdown component" width="560" />
220
185
 
221
186
  ---
222
187
 
223
188
  ### Card
224
189
 
190
+ Content container with variants: `elevated` | `outlined` | `filled`. Padding: `none` | `sm` | `md` | `lg` | `xl`. Customizable shadow via `--ea-card-shadow`.
191
+
225
192
  ```html
226
193
  <ea-card variant="elevated">
227
- <span slot="header">Card Title</span>
228
- Body content goes here.
229
- <span slot="footer">
230
- <ea-button size="sm">Action</ea-button>
194
+ <span eaCardHeader>Card Title</span>
195
+ Card body content goes here.
196
+ <span eaCardFooter>
197
+ <ea-button variant="secondary" size="sm">Cancel</ea-button>
198
+ <ea-button size="sm">Save</ea-button>
231
199
  </span>
232
200
  </ea-card>
233
201
  ```
234
202
 
235
- **Inputs**
203
+ <img src="docs/images/card.png" alt="Card component" width="560" />
204
+
205
+ ---
236
206
 
237
- | Input | Type | Default | Description |
238
- |---|---|---|---|
239
- | `variant` | `elevated \| outlined \| filled` | `elevated` | Visual style |
240
- | `padding` | `none \| sm \| md \| lg` | `md` | Inner padding |
241
- | `fullWidth` | `boolean` | `false` | Stretches to fill container |
207
+ ### Avatar
242
208
 
243
- **Content slots**
209
+ Image with initials or icon fallback. Sizes: `xs` | `sm` | `md` | `lg` | `xl`. Shapes: `circle` | `square`.
244
210
 
245
- | Slot | Description |
246
- |---|---|
247
- | `[slot=header]` | Card header / title area |
248
- | *(default)* | Card body content |
249
- | `[slot=footer]` | Card footer / actions area |
211
+ ```html
212
+ <ea-avatar src="/photo.jpg" alt="User" size="lg" />
213
+ <ea-avatar initials="MW" shape="square" />
214
+ <ea-avatar /> <!-- shows fallback user icon -->
215
+ ```
250
216
 
251
- Header and footer are hidden when empty.
217
+ <img src="docs/images/avatar.png" alt="Avatar component" width="560" />
252
218
 
253
219
  ---
254
220
 
255
- ### Dropdown
221
+ ### Avatar Editor
222
+
223
+ Canvas-based image editor with drag-and-drop upload, pan, zoom (slider + scroll wheel), and crop export. Outputs a `Blob` and data URL for use with the Avatar component.
256
224
 
257
225
  ```html
258
- <ea-dropdown
259
- label="Country"
260
- placeholder="Select a country…"
261
- [options]="countries"
262
- [(value)]="selectedCountry" />
226
+ <ea-avatar-editor
227
+ shape="circle"
228
+ [canvasSize]="200"
229
+ (cropped)="onCropped($event)" />
230
+ ```
231
+
232
+ <img src="docs/images/avatar-editor.png" alt="Avatar editor component" width="560" />
233
+
234
+ ---
235
+
236
+ ### Badge
237
+
238
+ Semantic status indicators. Variants: `default` | `success` | `warning` | `error` | `info`. Sizes: `sm` | `md` | `lg`.
239
+
240
+ ```html
241
+ <ea-badge variant="success">Active</ea-badge>
242
+ <ea-badge variant="error">Failed</ea-badge>
263
243
  ```
264
244
 
265
- Options are passed as an array of `{ value, label, disabled? }` objects. Supports keyboard navigation (arrow keys, Enter, Escape). Implements `ControlValueAccessor`.
245
+ <img src="docs/images/badge.png" alt="Badge component" width="560" />
266
246
 
267
- **Inputs**
247
+ ---
248
+
249
+ ### Spinner
250
+
251
+ SVG loading indicator with `role="status"` for accessibility. Sizes: `sm` | `md` | `lg`.
252
+
253
+ ```html
254
+ <ea-spinner size="md" label="Loading data" />
255
+ ```
256
+
257
+ <img src="docs/images/spinner.png" alt="Spinner component" width="560" />
268
258
 
269
- | Input | Type | Default | Description |
270
- |---|---|---|---|
271
- | `label` | `string` | — | Field label |
272
- | `placeholder` | `string` | `'Select…'` | Placeholder text |
273
- | `options` | `DropdownOption[]` | `[]` | Array of `{ value, label, disabled? }` |
274
- | `size` | `sm \| md \| lg` | `md` | Trigger size |
275
- | `disabled` | `boolean` | `false` | Disables the dropdown |
276
- | `required` | `boolean` | `false` | Marks as required |
277
- | `hint` | `string` | — | Helper text |
278
- | `error` | `string` | — | Error message |
259
+ ---
279
260
 
280
- **Two-way binding**
261
+ ### Divider
281
262
 
282
- | Binding | Type | Description |
283
- |---|---|---|
284
- | `[(value)]` | `string` | Selected option value |
263
+ Visual separator with optional label. Orientation: `horizontal` | `vertical`.
285
264
 
286
- **Outputs**
265
+ ```html
266
+ <ea-divider />
267
+ <ea-divider label="or" />
268
+ <ea-divider orientation="vertical" />
269
+ ```
287
270
 
288
- | Output | Type | Description |
289
- |---|---|---|
290
- | `changed` | `string` | Emitted when selection changes |
271
+ <img src="docs/images/divider.png" alt="Divider component" width="560" />
291
272
 
292
273
  ---
293
274
 
294
275
  ### Dialog
295
276
 
277
+ Built on the native `<dialog>` element for built-in focus trapping. Sizes: `sm` | `md` | `lg` | `full`. Two-way `open` binding.
278
+
296
279
  ```html
297
- <ea-button (clicked)="isOpen = true">Open</ea-button>
280
+ <ea-button (clicked)="dialogOpen.set(true)">Open</ea-button>
298
281
 
299
- <ea-dialog [(open)]="isOpen">
300
- <span slot="header">Confirm Action</span>
301
- <p>Are you sure you want to proceed?</p>
282
+ <ea-dialog [(open)]="dialogOpen" size="md">
283
+ <span slot="header">Confirm</span>
284
+ <p>Are you sure?</p>
302
285
  <span slot="footer">
303
- <ea-button variant="secondary" (clicked)="isOpen = false">Cancel</ea-button>
304
- <ea-button (clicked)="onConfirm()">Confirm</ea-button>
286
+ <ea-button variant="secondary" (clicked)="dialogOpen.set(false)">Cancel</ea-button>
287
+ <ea-button (clicked)="confirm()">Confirm</ea-button>
305
288
  </span>
306
289
  </ea-dialog>
307
290
  ```
308
291
 
309
- Uses the native `<dialog>` element with `showModal()` for proper focus trapping, backdrop, and accessibility.
292
+ ---
310
293
 
311
- **Inputs**
294
+ ### Tooltip
312
295
 
313
- | Input | Type | Default | Description |
314
- |---|---|---|---|
315
- | `size` | `sm \| md \| lg \| full` | `md` | Dialog panel width |
316
- | `closeOnBackdrop` | `boolean` | `true` | Close on backdrop click |
317
- | `closeOnEscape` | `boolean` | `true` | Close on Escape key |
318
- | `showClose` | `boolean` | `true` | Show the close (×) button |
319
- | `aria-label` | `string` | — | Accessible label for the dialog |
296
+ Directive that shows a positioned tooltip on hover and focus. Positions: `top` | `bottom` | `left` | `right`.
320
297
 
321
- **Two-way binding**
298
+ ```html
299
+ <ea-button eaTooltip="Save your changes" tooltipPosition="top">Save</ea-button>
300
+ ```
322
301
 
323
- | Binding | Type | Description |
324
- |---|---|---|
325
- | `[(open)]` | `boolean` | Controls dialog visibility |
302
+ ---
326
303
 
327
- **Outputs**
304
+ ### Toast
328
305
 
329
- | Output | Type | Description |
330
- |---|---|---|
331
- | `opened` | `void` | Emitted when the dialog opens |
332
- | `closed` | `void` | Emitted when the dialog closes |
306
+ Notification system via injectable `ToastService`. Variants: `default` | `success` | `warning` | `error` | `info`. Auto-dismiss with configurable duration. Full-width on mobile, independent widths on desktop.
333
307
 
334
- **Content slots**
308
+ ```typescript
309
+ import { ToastService } from '@eagami/ui';
335
310
 
336
- | Slot | Description |
337
- |---|---|
338
- | `[slot=header]` | Dialog title |
339
- | *(default)* | Dialog body |
340
- | `[slot=footer]` | Dialog actions |
311
+ export class MyComponent {
312
+ private toast = inject(ToastService);
341
313
 
342
- ---
314
+ save() {
315
+ this.toast.success('Changes saved');
316
+ }
317
+
318
+ handleError() {
319
+ this.toast.error('Something went wrong');
320
+ }
321
+ }
322
+ ```
323
+
324
+ Add the toast outlet once in your root template:
325
+
326
+ ```html
327
+ <ea-toast />
328
+ ```
329
+
330
+ <img src="docs/images/toast.png" alt="Toast component" width="560" />
331
+
332
+ ## Icons
333
+
334
+ Built-in SVG icon components following the Feather icon style (24x24 viewBox, stroke-based, inherits `currentColor`):
335
+
336
+ `ea-icon-check` | `ea-icon-x` | `ea-icon-user` | `ea-icon-info` | `ea-icon-loader` | `ea-icon-alert-circle` | `ea-icon-eye` | `ea-icon-eye-off` | `ea-icon-google`
337
+
338
+ ```html
339
+ <ea-icon-check />
340
+ <ea-icon-info />
341
+ ```
343
342
 
344
343
  ## Theming
345
344
 
346
- All components use CSS custom properties. Override them after importing the stylesheet:
345
+ All visual properties are controlled through CSS custom properties defined on `:root`. Override any token to customize the entire library:
347
346
 
348
347
  ```css
349
348
  :root {
350
- /* Brand colour — affects primary buttons, focus rings, etc. */
351
- --color-brand-default: #7c3aed;
352
- --color-brand-hover: #6d28d9;
353
- --color-brand-active: #5b21b6;
354
- --color-brand-subtle: #f5f3ff;
355
- --color-brand-muted: #ede9fe;
349
+ --color-primary-600: #2563eb;
350
+ --font-family-sans: 'Inter', sans-serif;
351
+ --radius-md: 0.5rem;
356
352
  }
357
353
  ```
358
354
 
359
- See [`src/styles/tokens/`](src/styles/tokens/) for the full token reference.
355
+ Component-level overrides are available where useful:
360
356
 
361
- ---
362
-
363
- ## Development
357
+ ```css
358
+ .my-card {
359
+ --ea-card-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
360
+ --ea-button-font-weight: 600;
361
+ }
362
+ ```
364
363
 
365
- ```sh
366
- # Install dependencies
367
- pnpm install
364
+ See [`src/styles/tokens/`](src/styles/tokens/) for the full token reference.
368
365
 
369
- # Run the sandbox dev app
370
- pnpm sandbox
366
+ ## Peer dependencies
371
367
 
372
- # Run Storybook (component explorer)
373
- pnpm storybook
368
+ | Package | Version |
369
+ |---------|---------|
370
+ | `@angular/common` | `^21.0.0` |
371
+ | `@angular/core` | `^21.0.0` |
372
+ | `@angular/forms` | `^21.0.0` |
373
+ | `rxjs` | `^7.0.0` |
374
374
 
375
- # Run tests
376
- pnpm test
375
+ ## Development
377
376
 
378
- # Build the library
379
- pnpm build
377
+ ```bash
378
+ pnpm install # Install dependencies
379
+ pnpm start # Run sandbox dev app
380
+ pnpm storybook # Run Storybook
381
+ pnpm test # Run tests
382
+ pnpm build # Build the library
383
+ pnpm lint # Lint
380
384
  ```
381
385
 
382
- ## Contributing
383
-
384
- Issues and PRs are welcome. Please open an issue before submitting large changes.
385
-
386
386
  ## License
387
387
 
388
388
  MIT