Skip to content

002 shared UI primitives#7

Merged
ElbekDeveloper merged 4 commits intomasterfrom
002-shared-ui-primitives
Apr 30, 2026
Merged

002 shared UI primitives#7
ElbekDeveloper merged 4 commits intomasterfrom
002-shared-ui-primitives

Conversation

@ElbekDeveloper
Copy link
Copy Markdown
Owner

@ElbekDeveloper ElbekDeveloper commented Apr 30, 2026

Closes #3

Summary by CodeRabbit

  • New Features

    • Added three reusable UI primitive components—Button, TextInput, and Spinner—with built-in accessibility features, loading states, and form integration.
  • Chores

    • Updated project configuration and TypeScript path alias settings for improved developer experience.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Warning

Rate limit exceeded

@ElbekDeveloper has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 28 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 968c61ae-671d-4cda-ba39-59e2828bf72f

📥 Commits

Reviewing files that changed from the base of the PR and between d33eae6 and ece9b42.

📒 Files selected for processing (4)
  • src/app/shared/ui/button/button.component.scss
  • src/app/shared/ui/spinner/spinner.component.scss
  • src/app/shared/ui/text-input/text-input.component.scss
  • src/styles/tokens.scss
📝 Walkthrough

Walkthrough

This PR introduces three reusable Angular UI primitives—Button, TextInput, and Spinner—with comprehensive specifications and implementations. It includes component definitions with accessibility features, design token styling, reactive forms integration, and a barrel export. Configuration updated with tsconfig path alias and feature directory references.

Changes

Cohort / File(s) Summary
Configuration
.specify/feature.json, CLAUDE.md, tsconfig.json
Updated feature directory references to specs/002-shared-ui-primitives and added @app/* tsconfig path alias mapping to src/app/*. Removed experimentalDecorators compiler option.
Specification Documents
specs/002-shared-ui-primitives/*
Added complete specification suite covering requirements checklist, component contracts (button, spinner, text-input), data model, implementation plan, quickstart guide, research decisions, feature spec, and task breakdown. Approximately 2000 lines of documentation defining component APIs, accessibility requirements, styling constraints, and implementation patterns.
Spinner Component
src/app/shared/ui/spinner/spinner.component.ts, .scss
Implements accessible spinner with role="status", aria-label binding, size variants (sm/md), and CSS keyframe animation. OnPush change detection strategy.
Button Component
src/app/shared/ui/button/button.component.ts, .scss
Implements button with variant/type/loading/disabled inputs, computed disabled state, conditional spinner rendering, aria-busy attribute, and token-based styling with focus-visible and modifier variants.
TextInput Component
src/app/shared/ui/text-input/text-input.component.ts, .html, .scss
Implements ControlValueAccessor with label, type, errorMessage, placeholder inputs. Features reactive value management via signals, password toggle with accessible labeling, error display with role="alert", and token-driven styling with hardcoded fallbacks for error states.
UI Barrel Export
src/app/shared/ui/index.ts
Introduces barrel module re-exporting ButtonComponent, TextInputComponent, and SpinnerComponent for centralized consumption.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Three primitives hop into view,
Button loads and spinner spins true,
TextInput dances with forms so tight,
@app paths make imports just right—
UI foundations, now tried and tested bright! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title '002 shared UI primitives' is vague and generic, using feature numbering without describing the actual implementation scope or what was changed. Use a more descriptive title that conveys the main change, e.g., 'Implement shared UI primitives (button, input, spinner)' or 'Add button, input, and spinner UI components'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The pull request implements all three required UI components (button, input, spinner) with proper specifications, component implementations, and accessibility requirements as outlined in issue #3.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing shared UI primitives: specification documents, component implementations, styling, barrel exports, and necessary configuration updates (tsconfig path alias).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 002-shared-ui-primitives

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 47 minutes and 28 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
specs/002-shared-ui-primitives/data-model.md (1)

54-62: ⚡ Quick win

Keep the Button contract in sync with the implementation.

The contract currently documents only the base and variant classes, but the component also applies btn--disabled and btn--loading. If this spec is meant to be authoritative, include those state classes here so downstream consumers and tests do not rely on a partial contract.

📘 Suggested doc update
-Template binding: `[class]="'btn btn--' + variant()"`
+Template binding: `[class]="'btn btn--' + variant() + (isDisabled() ? ' btn--disabled' : '') + (loading() ? ' btn--loading' : '')"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/002-shared-ui-primitives/data-model.md` around lines 54 - 62, The spec
for the Button CSS class strategy is out of sync with the implementation: update
the documented CSS Class Strategy to include the state classes `btn--disabled`
and `btn--loading` alongside the base `btn` and variant classes; explicitly
describe when those classes are applied (e.g., when the component's `disabled`
or `loading` signals/props are truthy) and reflect the template binding pattern
that composes them from `variant()` and state flags (the existing `[class]="'btn
btn--' + variant()"` entry should be expanded to show how `btn--disabled` and
`btn--loading` are added based on `disabled`/`loading` signals).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@specs/002-shared-ui-primitives/contracts/button.md`:
- Line 62: The table row for the `disabled` contract is breaking Markdown
because the code span contains a pipe character; update the cell to avoid '||' —
e.g. replace the cell content "When `loading() || disabled()`" with a
Markdown-safe phrase such as "When loading() or disabled()" or "When `loading()`
or `disabled()`" so the `disabled` row (the `disabled` contract) no longer
contains an unescaped pipe and the table remains valid.

In `@specs/002-shared-ui-primitives/contracts/spinner.md`:
- Around line 78-95: The two fenced code blocks showing the spinner directory
listing and the ButtonComponent snippet are missing a language tag; update the
markdown examples to use a language identifier (use "text") for the directory
block that references spinner.component.ts and spinner.component.scss and for
the HTML snippet that demonstrates the ButtonComponent usage (the block showing
the `@if` (loading()) { <app-spinner ... /> } inside button.component.html) so
markdownlint stops flagging them.

In `@specs/002-shared-ui-primitives/contracts/text-input.md`:
- Around line 107-123: The markdown examples in the documentation use fenced
code blocks without a language tag which triggers markdownlint; update the two
fenced blocks showing the errorMessage behavior and the File Structure to
include a language identifier (e.g., ```text) so the linter stops flagging
them—edit the examples in text-input.md where the blocks contain "errorMessage()
=== '' ..." and the tree listing "src/app/shared/ui/text-input/ ..." and change
the opening backticks to ```text for each block.

In `@src/app/shared/ui/button/button.component.scss`:
- Around line 20-23: The current &:focus-visible rule uses a thin accent outline
that may fail contrast; update the focus style to a higher-contrast, more
prominent ring by increasing thickness and using a high-contrast token (or
fallback) and optional halo: replace outline: 2px solid tokens.$color-accent
with something like outline: 3px solid tokens.$color-focus (or
tokens.$color-on-surface if a dedicated focus token isn't available), increase
outline-offset to 3px, and add a subtle halo using box-shadow: 0 0 0 3px
tokens.$color-accent to preserve accent while ensuring the visible ring meets
contrast requirements.

In `@src/app/shared/ui/spinner/spinner.component.scss`:
- Line 13: Add a prefers-reduced-motion fallback in spinner.component.scss to
stop the infinite rotation for users who request reduced motion: target the
spinner selector that currently uses "animation: lexico-spin 0.75s linear
infinite" and inside an `@media` (prefers-reduced-motion: reduce) rule disable the
animation (set animation: none or animation-duration: 0s) and optionally remove
transforms so the spinner is static; update the rule alongside the existing
"animation: lexico-spin..." declaration so it only applies for reduced-motion
users.

In `@src/app/shared/ui/text-input/text-input.component.scss`:
- Around line 21-40: The input currently removes the browser outline in &__input
and only changes border-color on &:focus which is insufficient; add a
:focus-visible rule for &__input that restores an accessible visible ring (e.g.,
using box-shadow or outline with tokens.$color-accent and appropriate spread)
and ensure it is only applied on :focus-visible to avoid mouse-click outlines;
update the rules adjacent to &__input { ... &:focus { ... } } so the new
&:focus-visible selector uses tokens.$color-accent and a subtle spread (and
respects tokens.$focus-ring-width if available) to meet the visible-ring
requirement.
- Around line 41-68: Replace the duplicated hard-coded error hex (`#e53e3e`) with
the shared token tokens.$color-error: update the border rule in the
[aria-invalid='true'] selector to use tokens.$color-error and update the color
in the &__error block to use tokens.$color-error as well; ensure you reference
the same token name (tokens.$color-error) in both places so the error color is
driven by the design tokens rather than a literal hex value.

---

Nitpick comments:
In `@specs/002-shared-ui-primitives/data-model.md`:
- Around line 54-62: The spec for the Button CSS class strategy is out of sync
with the implementation: update the documented CSS Class Strategy to include the
state classes `btn--disabled` and `btn--loading` alongside the base `btn` and
variant classes; explicitly describe when those classes are applied (e.g., when
the component's `disabled` or `loading` signals/props are truthy) and reflect
the template binding pattern that composes them from `variant()` and state flags
(the existing `[class]="'btn btn--' + variant()"` entry should be expanded to
show how `btn--disabled` and `btn--loading` are added based on
`disabled`/`loading` signals).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d25c8837-5254-4c07-93db-d43f32576b3a

📥 Commits

Reviewing files that changed from the base of the PR and between 3dba460 and d33eae6.

📒 Files selected for processing (21)
  • .specify/feature.json
  • CLAUDE.md
  • specs/002-shared-ui-primitives/checklists/requirements.md
  • specs/002-shared-ui-primitives/contracts/button.md
  • specs/002-shared-ui-primitives/contracts/spinner.md
  • specs/002-shared-ui-primitives/contracts/text-input.md
  • specs/002-shared-ui-primitives/data-model.md
  • specs/002-shared-ui-primitives/plan.md
  • specs/002-shared-ui-primitives/quickstart.md
  • specs/002-shared-ui-primitives/research.md
  • specs/002-shared-ui-primitives/spec.md
  • specs/002-shared-ui-primitives/tasks.md
  • src/app/shared/ui/button/button.component.scss
  • src/app/shared/ui/button/button.component.ts
  • src/app/shared/ui/index.ts
  • src/app/shared/ui/spinner/spinner.component.scss
  • src/app/shared/ui/spinner/spinner.component.ts
  • src/app/shared/ui/text-input/text-input.component.html
  • src/app/shared/ui/text-input/text-input.component.scss
  • src/app/shared/ui/text-input/text-input.component.ts
  • tsconfig.json

| Attribute | Value | Condition |
|-----------|-------|-----------|
| `type` | `"button"` or `"submit"` | Always (from `type` input) |
| `disabled` | present | When `loading() || disabled()` |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Rewrite the disabled contract row so the Markdown table stays valid.

The || inside the code span is being parsed as extra table separators, which is why markdownlint reports the row as having too many columns. Rephrase it to avoid pipe characters in the cell.

📝 Proposed wording fix
-| `disabled` | present | When `loading() || disabled()` |
+| `disabled` | present | When `loading` or `disabled` is true |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `disabled` | present | When `loading() || disabled()` |
| `disabled` | present | When `loading` or `disabled` is true |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 62-62: Table column count
Expected: 3; Actual: 5; Too many cells, extra data will be missing

(MD056, table-column-count)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/002-shared-ui-primitives/contracts/button.md` at line 62, The table row
for the `disabled` contract is breaking Markdown because the code span contains
a pipe character; update the cell to avoid '||' — e.g. replace the cell content
"When `loading() || disabled()`" with a Markdown-safe phrase such as "When
loading() or disabled()" or "When `loading()` or `disabled()`" so the `disabled`
row (the `disabled` contract) no longer contains an unescaped pipe and the table
remains valid.

Comment on lines +78 to +95
```
src/app/shared/ui/spinner/
├── spinner.component.ts # Component class (inline template)
└── spinner.component.scss # Component styles
```

Spinner uses an inline template because the template is minimal (2 elements).

## Composition Note

`ButtonComponent` imports `SpinnerComponent` and renders it internally:

```html
<!-- Inside button.component.html -->
@if (loading()) {
<app-spinner size="sm" label="Loading…" aria-hidden="true" />
}
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tag the fenced examples with a language.

Both fenced blocks are missing a language identifier, which will keep markdownlint failing here. Use text for these examples so the contract stays lint-clean.

Suggested fix
-```
+```text
 src/app/shared/ui/spinner/
 ├── spinner.component.ts        # Component class (inline template)
 └── spinner.component.scss      # Component styles
-```
+```

-```
+```text
 <!-- Inside button.component.html -->
 `@if` (loading()) {
   <app-spinner size="sm" label="Loading…" aria-hidden="true" />
 }
-```
+```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
src/app/shared/ui/spinner/
├── spinner.component.ts # Component class (inline template)
└── spinner.component.scss # Component styles
```
Spinner uses an inline template because the template is minimal (2 elements).
## Composition Note
`ButtonComponent` imports `SpinnerComponent` and renders it internally:
```html
<!-- Inside button.component.html -->
@if (loading()) {
<app-spinner size="sm" label="Loading…" aria-hidden="true" />
}
```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 78-78: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/002-shared-ui-primitives/contracts/spinner.md` around lines 78 - 95,
The two fenced code blocks showing the spinner directory listing and the
ButtonComponent snippet are missing a language tag; update the markdown examples
to use a language identifier (use "text") for the directory block that
references spinner.component.ts and spinner.component.scss and for the HTML
snippet that demonstrates the ButtonComponent usage (the block showing the `@if`
(loading()) { <app-spinner ... /> } inside button.component.html) so
markdownlint stops flagging them.

Comment on lines +107 to +123
```
errorMessage() === '' or undefined → @if block not rendered (zero height, zero CLS)
errorMessage() !== '' → <span role="alert"> renders with error text
```

The error span has `role="alert"` so that when it is inserted into the DOM (which happens
when `errorMessage` changes from empty to non-empty), screen readers announce it immediately
(`aria-live="assertive"` implied by `role="alert"`).

## File Structure

```
src/app/shared/ui/text-input/
├── text-input.component.ts # Component class + template (external)
├── text-input.component.html # External template
└── text-input.component.scss # Component styles
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tag the fenced examples with a language.

These blocks will keep tripping the markdownlint fenced-code-language rule. Add a language identifier such as text so the docs stay lint-clean.

Suggested fix
-```
+```text
 errorMessage() === '' or undefined  →  `@if` block not rendered (zero height, zero CLS)
 errorMessage() !== ''               →  <span role="alert"> renders with error text
-```
+```

-```
+```text
 src/app/shared/ui/text-input/
 ├── text-input.component.ts        # Component class + template (external)
 ├── text-input.component.html      # External template
 └── text-input.component.scss      # Component styles
-```
+```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 107-107: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 118-118: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/002-shared-ui-primitives/contracts/text-input.md` around lines 107 -
123, The markdown examples in the documentation use fenced code blocks without a
language tag which triggers markdownlint; update the two fenced blocks showing
the errorMessage behavior and the File Structure to include a language
identifier (e.g., ```text) so the linter stops flagging them—edit the examples
in text-input.md where the blocks contain "errorMessage() === '' ..." and the
tree listing "src/app/shared/ui/text-input/ ..." and change the opening
backticks to ```text for each block.

Comment thread src/app/shared/ui/button/button.component.scss
Comment thread src/app/shared/ui/spinner/spinner.component.scss
Comment thread src/app/shared/ui/text-input/text-input.component.scss
Comment thread src/app/shared/ui/text-input/text-input.component.scss Outdated
…ments

- Add \$color-error token to tokens.scss; replace hard-coded #e53e3e in
  text-input.component.scss (FR-015 / SC-003 compliance)
- Change text-input &:focus to &:focus-visible and add outline ring for
  WCAG 2.4.7 keyboard focus visibility
- Add prefers-reduced-motion media query to spinner.component.scss (WCAG 2.3.3)
- Increase button focus-visible ring to 3px + box-shadow halo for contrast

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ElbekDeveloper ElbekDeveloper merged commit 701e36b into master Apr 30, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Shared UI component library (button, input, spinner)

1 participant