Skip to content

feat(time-field,time-picker,scroll-fade): new tedi-ready components #374#397

Open
mart-sessman wants to merge 11 commits into
feat/329-filter-tedi-readyfrom
feat/374-time-field-picker-tedi-ready
Open

feat(time-field,time-picker,scroll-fade): new tedi-ready components #374#397
mart-sessman wants to merge 11 commits into
feat/329-filter-tedi-readyfrom
feat/374-time-field-picker-tedi-ready

Conversation

@mart-sessman
Copy link
Copy Markdown
Contributor

@mart-sessman mart-sessman commented Apr 7, 2026

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a time field component with support for native and custom time pickers featuring scroll, dropdown, and slots display modes.
    • Added a scroll fade helper component for enhanced visual feedback on scrollable content.
    • Introduced configurable time input with minute step controls, disabled states, and form validation support.
    • Added support for multiple display sizes and error/valid states in time field.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 7, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • rc
  • main

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: db6620a7-b3f6-4254-848b-bb550afd67c4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces three new Angular components (TimeFieldComponent, TimePickerComponent, and ScrollFadeComponent) into the form and helpers modules, along with comprehensive test suites, Storybook documentation, and supporting infrastructure. The time field and picker components integrate with Angular Reactive Forms via ControlValueAccessor, while the scroll fade helper provides a reusable scroll-fading container. Export statements are updated to expose these new components.

Changes

Cohort / File(s) Summary
Time Field Component
tedi/components/form/time-field/time-field.component.ts, time-field.component.html, time-field.component.scss, time-field.component.spec.ts, time-field.stories.ts, time-field/index.ts
Added new standalone TimeFieldComponent implementing ControlValueAccessor and FormFieldControl<string | null>. Features include text input with HH:mm parsing/validation on blur, optional clear button, native and custom time picker support via popover, signal-based configuration (size, state, disabled, clearable), and comprehensive test coverage. Storybook stories demonstrate multiple picker variants and reactive forms integration.
Time Picker Component
tedi/components/form/time-picker/time-picker.component.ts, time-picker.component.html, time-picker.component.scss, time-picker.component.spec.ts, time-picker.stories.ts, time-picker/index.ts
Added new standalone TimePickerComponent implementing ControlValueAccessor with three interactive variants: scroll (hour/minute columns), dropdown (time slots), and slots (grid radiogroup). Includes roving tabindex keyboard navigation with focus trapping option, scroll position management, and extensive test coverage for selection, keyboard behavior, and form integration.
Scroll Fade Helper Component
tedi/components/helpers/scroll-fade/scroll-fade.component.ts, scroll-fade.component.html, scroll-fade.component.scss, scroll-fade.component.spec.ts, scroll-fade.stories.ts, scroll-fade/index.ts
Added new reusable ScrollFadeComponent that creates scroll-fading containers with configurable fade size/position and custom scrollbar styling. Emits scrolledToTop/scrolledToBottom outputs on reaching scroll boundaries. Includes mask-image gradient effects and comprehensive test coverage for fade visibility, overflow detection, and output emissions.
Module Exports and Configuration
tedi/components/form/index.ts, tedi/components/form/form-field/form-field.component.html, tedi/components/helpers/index.ts
Extended module exports to include new time-field, time-picker, and scroll-fade re-exports. Updated form-field template to project tedi-time-field elements alongside text fields in input container.
Translations and Documentation
tedi/services/translation/translations.ts, .claude/skills/contributing/references/best-practices.md
Added "time-field.clear" and "time-field.select-time" translation keys with localization for Estonian, English, and Russian. Updated best-practices documentation regarding Storybook story configuration and required module metadata.

Sequence Diagram(s)

sequenceDiagram
    participant User as User Input
    participant TimeField as TimeFieldComponent
    participant Parser as Validation/Parsing
    participant Picker as TimePickerComponent
    participant FormControl as FormControl
    participant OnChange as onChange Handler

    User->>TimeField: Input text (e.g., "14:30")
    TimeField->>TimeField: blur event triggered
    TimeField->>Parser: Validate HH:mm format
    Parser-->>TimeField: Valid/Invalid result
    alt Valid Input
        TimeField->>TimeField: Update internal value
        TimeField->>FormControl: Sync via writeValue
        TimeField->>OnChange: Emit value change
    else Invalid Input
        TimeField->>TimeField: Revert to previous value
    end
    
    User->>TimeField: Click picker trigger
    TimeField->>Picker: Open time picker
    Picker->>Picker: Render scroll/dropdown/slots variant
    User->>Picker: Select time (e.g., click hour 14)
    Picker->>Picker: Update selected hour/minute
    Picker->>OnChange: Emit value change
    Picker->>FormControl: Sync value
    alt closeOnSelect enabled
        Picker->>TimeField: Close popover
    end
    TimeField->>TimeField: Update input display with selected time
Loading
sequenceDiagram
    participant User as User/Content
    participant ScrollFade as ScrollFadeComponent
    participant Inner as Inner Wrapper div
    participant Mask as CSS Mask-Image
    participant Output as scrolledToTop/Bottom Output

    User->>ScrollFade: Provide scrollable content
    ScrollFade->>Inner: Set overflow: auto
    Inner->>Mask: Apply linear-gradient mask-image
    Mask-->>Inner: Create fade effect at edges
    
    User->>Inner: Scroll down
    Inner->>ScrollFade: Dispatch scroll event
    ScrollFade->>ScrollFade: Calculate scroll position
    alt At Bottom
        ScrollFade->>Output: Emit scrolledToBottom
    else At Top
        ScrollFade->>Output: Emit scrolledToTop
    else Middle
        ScrollFade->>ScrollFade: Update fade classes dynamically
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #343: Modifies tedi/components/form/index.ts exports in parallel, adding select component and reordering form module exports—shares the same export infrastructure and form module scope.

Suggested reviewers

  • ly-tempel-bitweb
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: introducing three new tedi-ready components (time-field, time-picker, and scroll-fade) as indicated by the feat prefix and component names.
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 feat/374-time-field-picker-tedi-ready

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

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

@mart-sessman
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 7, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tedi/components/form/index.ts (1)

9-15: ⚠️ Potential issue | 🟡 Minor

Duplicate export of date-picker.component.

Lines 9 and 15 both export "./date-picker/date-picker.component". This will cause a TypeScript compilation error or unexpected behavior.

🐛 Proposed fix
 export * from "./radio-card-group/radio-card-group.component";
 export * from "./date-picker/date-picker.component";
 export * from "./feedback-text/feedback-text.component";
 export * from "./label/label.component";
 export * from "./number-field/number-field.component";
 export * from "./select";
 export * from "./toggle/toggle.component";
-export * from "./date-picker/date-picker.component";
 export * from "./form-field/form-field.component";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tedi/components/form/index.ts` around lines 9 - 15, There are duplicate
exports of "./date-picker/date-picker.component" in the form barrel; remove the
redundant export so the module is only exported once (keep a single export line
for "./date-picker/date-picker.component" and delete the other), and verify the
remaining exports (e.g., "./feedback-text/feedback-text.component",
"./label/label.component", "./number-field/number-field.component", "./select",
"./toggle/toggle.component") are correct.
🧹 Nitpick comments (5)
tedi/components/form/time-field/time-field.component.scss (1)

53-62: Remove !important declarations to resolve specificity issues.

The !important overrides on lines 54–57 indicate specificity conflicts with base button styles and can lead to maintenance issues. Consider:

  1. Increasing selector specificity naturally (e.g., .tedi-time-field .tedi-time-field__icon)
  2. Using CSS custom properties that the button component respects
  3. Adjusting the base button component to be more customizable

The tedi-time-field__icon--static modifier class used in the HTML template (non-interactive state) does not have explicit styles, but this appears intentional—it's a semantic marker that correctly inherits from the base &__icon styles.

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

In `@tedi/components/form/time-field/time-field.component.scss` around lines 53 -
62, Remove the four `!important` overrides on the `&__icon` rules to avoid
specificity fights; instead increase selector specificity (e.g., target
`.tedi-time-field .tedi-time-field__icon`) or rely on configurable CSS custom
properties the base button consumes (use the existing --button-xs-icon-size,
--form-field-button-height-sm, --button-radius-sm variables) so the icon styles
inherit normally; keep the `tedi-time-field__icon--static` modifier as-is since
it intentionally inherits from `&__icon`.
tedi/components/helpers/scroll-fade/scroll-fade.component.scss (1)

26-44: CSS nesting depth exceeds maximum of 4 levels.

The :hover pseudo-class inside &::-webkit-scrollbar-thumb creates 5 levels of nesting (.tedi-scroll-fade&__inner&--custom-scroll&::-webkit-scrollbar-thumb&:hover). As per coding guidelines, max CSS nesting depth is 4.

♻️ Proposed fix to flatten nesting
   &__inner {
     flex: 1;
     min-height: 0;
     max-height: inherit;
     overflow: auto;
     mask-image: linear-gradient(
       to bottom,
       transparent 0%,
       black var(--_tedi-scroll-fade-top),
       black calc(100% - var(--_tedi-scroll-fade-bottom)),
       transparent 100%
     );
-
-    &--custom-scroll {
-      &::-webkit-scrollbar {
-        width: 6px;
-        background-color: var(--general-surface-primary);
-      }
-
-      &::-webkit-scrollbar-thumb {
-        background: var(--general-border-primary);
-        border-radius: 100px;
-
-        &:hover {
-          background-color: var(--general-border-secondary);
-        }
-      }
-
-      &::-webkit-scrollbar-track {
-        background-color: var(--general-surface-primary);
-      }
-    }
   }
+
+  &__inner--custom-scroll {
+    &::-webkit-scrollbar {
+      width: 6px;
+      background-color: var(--general-surface-primary);
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: var(--general-border-primary);
+      border-radius: 100px;
+    }
+
+    &::-webkit-scrollbar-thumb:hover {
+      background-color: var(--general-border-secondary);
+    }
+
+    &::-webkit-scrollbar-track {
+      background-color: var(--general-surface-primary);
+    }
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tedi/components/helpers/scroll-fade/scroll-fade.component.scss` around lines
26 - 44, The nesting depth exceeds the max because the &:hover pseudo inside
&::-webkit-scrollbar-thumb creates five levels; move the hover selector out to
flatten it. Concretely, remove the nested &:hover inside
&::-webkit-scrollbar-thumb and add a top-level selector targeting
&--custom-scroll::-webkit-scrollbar-thumb:hover (or the equivalent flattened
selector using &--custom-scroll and ::-webkit-scrollbar-thumb:hover) so you keep
the same styles but reduce nesting; update selectors referencing
&--custom-scroll, &::-webkit-scrollbar-thumb, and &:hover accordingly.
tedi/components/form/time-field/time-field.component.html (1)

29-49: Consider moving inline styles to CSS classes.

Lines 31 and 49 use inline style attributes. For consistency with the BEM approach and maintainability, consider using CSS classes instead.

♻️ Suggested approach

Add classes to the SCSS file:

.tedi-time-field__popover-trigger {
  display: flex;
  align-items: center;
}

.tedi-time-field__popover-content {
  padding: 0;
}

Then update the template:

     <tedi-popover
       `#popover`
-      style="display: flex; align-items: center"
+      class="tedi-time-field__popover-trigger"
       position="bottom-end"
...
-      <tedi-popover-content maxWidth="none" style="padding: 0">
+      <tedi-popover-content maxWidth="none" class="tedi-time-field__popover-content">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tedi/components/form/time-field/time-field.component.html` around lines 29 -
49, Replace the inline styles on the tedi-popover host element and
tedi-popover-content with CSS classes: create classes (e.g.,
.tedi-time-field__popover-trigger and .tedi-time-field__popover-content) in the
component SCSS containing the styles "display: flex; align-items: center" and
"padding: 0" respectively, then remove the style attributes from the
<tedi-popover `#popover` ...> and <tedi-popover-content ...> elements and add the
corresponding class names to those elements to follow the BEM approach and keep
styling in SCSS.
tedi/components/form/time-field/time-field.component.ts (1)

194-203: Nested setTimeout pattern is fragile for timing coordination.

The double setTimeout relies on specific timing to wait for popover rendering and focus setup. This could be flaky if rendering takes longer than expected.

Consider using a more robust approach such as observing DOM changes or using Angular's lifecycle hooks if possible.

♻️ Consider using requestAnimationFrame or AfterViewInit pattern
   onPickerOpen() {
-    // First timeout waits for popover to render content in DOM.
-    // Nested timeout runs after popover's own focus setup (which also uses setTimeout).
-    setTimeout(() => {
-      this.timePicker()?.scrollToSelected();
-      setTimeout(() => {
-        this.timePicker()?.focusActiveItem();
-      });
-    });
+    // Use requestAnimationFrame to ensure DOM is ready
+    requestAnimationFrame(() => {
+      this.timePicker()?.scrollToSelected();
+      requestAnimationFrame(() => {
+        this.timePicker()?.focusActiveItem();
+      });
+    });
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tedi/components/form/time-field/time-field.component.ts` around lines 194 -
203, The nested setTimeouts in onPickerOpen (which calls
this.timePicker()?.scrollToSelected() and this.timePicker()?.focusActiveItem())
are fragile; replace them with a more robust coordination such as waiting for
Angular stabilization or the next animation frame: e.g., use NgZone.onStable or
requestAnimationFrame (or a MutationObserver if the popover content is dynamic)
to call scrollToSelected() after the popover DOM is rendered and then schedule
focusActiveItem() on the next animation frame (or when the zone is stable)
instead of using nested setTimeouts.
tedi/components/helpers/scroll-fade/scroll-fade.component.ts (1)

76-99: Consider using an arrow function for onScroll to ensure stable reference.

While onScroll is currently called via template binding (scroll)="onScroll()" which works correctly, the coding guidelines recommend arrow function properties for event handlers. This is a minor suggestion since the current implementation is functional.

♻️ Optional: Convert to arrow function
-  onScroll(): void {
+  onScroll = (): void => {
     const el = this.innerRef().nativeElement;
     this.updateFade(el.scrollTop, el.scrollHeight, el.clientHeight);
-  }
+  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tedi/components/helpers/scroll-fade/scroll-fade.component.ts` around lines 76
- 99, The onScroll method should be converted to an arrow-function property to
ensure a stable function reference for template bindings; replace the class
method onScroll() with an arrow property (keeping the same logic that reads
const el = this.innerRef().nativeElement and calls this.updateFade) so it
becomes onScroll = (): void => { ... }, leaving updateFade, ngAfterViewInit,
fade, scrolledToTop and scrolledToBottom unchanged; ensure any places that call
onScroll (template bindings) remain unchanged since the signature and behavior
are identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tedi/components/form/time-picker/time-picker.component.html`:
- Around line 7-9: Replace the hardcoded aria-label attributes in the
time-picker template with the tediTranslate pipe: update the listbox/role
elements that currently use aria-label="Hours" and aria-label="Minutes" to use
the translation pipe (e.g., aria-label="{{ 'timePicker.hours' | tediTranslate
}}") so the labels are localized; ensure you update the template instances in
time-picker.component.html and add the corresponding translation keys (e.g.,
timePicker.hours, timePicker.minutes) to the translations map used by the
tediTranslate service.

---

Outside diff comments:
In `@tedi/components/form/index.ts`:
- Around line 9-15: There are duplicate exports of
"./date-picker/date-picker.component" in the form barrel; remove the redundant
export so the module is only exported once (keep a single export line for
"./date-picker/date-picker.component" and delete the other), and verify the
remaining exports (e.g., "./feedback-text/feedback-text.component",
"./label/label.component", "./number-field/number-field.component", "./select",
"./toggle/toggle.component") are correct.

---

Nitpick comments:
In `@tedi/components/form/time-field/time-field.component.html`:
- Around line 29-49: Replace the inline styles on the tedi-popover host element
and tedi-popover-content with CSS classes: create classes (e.g.,
.tedi-time-field__popover-trigger and .tedi-time-field__popover-content) in the
component SCSS containing the styles "display: flex; align-items: center" and
"padding: 0" respectively, then remove the style attributes from the
<tedi-popover `#popover` ...> and <tedi-popover-content ...> elements and add the
corresponding class names to those elements to follow the BEM approach and keep
styling in SCSS.

In `@tedi/components/form/time-field/time-field.component.scss`:
- Around line 53-62: Remove the four `!important` overrides on the `&__icon`
rules to avoid specificity fights; instead increase selector specificity (e.g.,
target `.tedi-time-field .tedi-time-field__icon`) or rely on configurable CSS
custom properties the base button consumes (use the existing
--button-xs-icon-size, --form-field-button-height-sm, --button-radius-sm
variables) so the icon styles inherit normally; keep the
`tedi-time-field__icon--static` modifier as-is since it intentionally inherits
from `&__icon`.

In `@tedi/components/form/time-field/time-field.component.ts`:
- Around line 194-203: The nested setTimeouts in onPickerOpen (which calls
this.timePicker()?.scrollToSelected() and this.timePicker()?.focusActiveItem())
are fragile; replace them with a more robust coordination such as waiting for
Angular stabilization or the next animation frame: e.g., use NgZone.onStable or
requestAnimationFrame (or a MutationObserver if the popover content is dynamic)
to call scrollToSelected() after the popover DOM is rendered and then schedule
focusActiveItem() on the next animation frame (or when the zone is stable)
instead of using nested setTimeouts.

In `@tedi/components/helpers/scroll-fade/scroll-fade.component.scss`:
- Around line 26-44: The nesting depth exceeds the max because the &:hover
pseudo inside &::-webkit-scrollbar-thumb creates five levels; move the hover
selector out to flatten it. Concretely, remove the nested &:hover inside
&::-webkit-scrollbar-thumb and add a top-level selector targeting
&--custom-scroll::-webkit-scrollbar-thumb:hover (or the equivalent flattened
selector using &--custom-scroll and ::-webkit-scrollbar-thumb:hover) so you keep
the same styles but reduce nesting; update selectors referencing
&--custom-scroll, &::-webkit-scrollbar-thumb, and &:hover accordingly.

In `@tedi/components/helpers/scroll-fade/scroll-fade.component.ts`:
- Around line 76-99: The onScroll method should be converted to an
arrow-function property to ensure a stable function reference for template
bindings; replace the class method onScroll() with an arrow property (keeping
the same logic that reads const el = this.innerRef().nativeElement and calls
this.updateFade) so it becomes onScroll = (): void => { ... }, leaving
updateFade, ngAfterViewInit, fade, scrolledToTop and scrolledToBottom unchanged;
ensure any places that call onScroll (template bindings) remain unchanged since
the signature and behavior are identical.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6e783f1b-a5bc-4af9-8e0b-aae44c0c937c

📥 Commits

Reviewing files that changed from the base of the PR and between d41718c and 31df16b.

📒 Files selected for processing (23)
  • .claude/skills/contributing/references/best-practices.md
  • tedi/components/form/form-field/form-field.component.html
  • tedi/components/form/index.ts
  • tedi/components/form/time-field/index.ts
  • tedi/components/form/time-field/time-field.component.html
  • tedi/components/form/time-field/time-field.component.scss
  • tedi/components/form/time-field/time-field.component.spec.ts
  • tedi/components/form/time-field/time-field.component.ts
  • tedi/components/form/time-field/time-field.stories.ts
  • tedi/components/form/time-picker/index.ts
  • tedi/components/form/time-picker/time-picker.component.html
  • tedi/components/form/time-picker/time-picker.component.scss
  • tedi/components/form/time-picker/time-picker.component.spec.ts
  • tedi/components/form/time-picker/time-picker.component.ts
  • tedi/components/form/time-picker/time-picker.stories.ts
  • tedi/components/helpers/index.ts
  • tedi/components/helpers/scroll-fade/index.ts
  • tedi/components/helpers/scroll-fade/scroll-fade.component.html
  • tedi/components/helpers/scroll-fade/scroll-fade.component.scss
  • tedi/components/helpers/scroll-fade/scroll-fade.component.spec.ts
  • tedi/components/helpers/scroll-fade/scroll-fade.component.ts
  • tedi/components/helpers/scroll-fade/scroll-fade.stories.ts
  • tedi/services/translation/translations.ts

Comment thread tedi/components/form/time-picker/time-picker.component.html Outdated
@mart-sessman
Copy link
Copy Markdown
Contributor Author

@coderabbitai resolve

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

✅ Actions performed

Comments resolved. Approval is disabled; enable reviews.request_changes_workflow to allow explicit top-level @coderabbitai resolve or @coderabbitai approve commands.

…cker-tedi-ready

# Conflicts:
#	tedi/components/helpers/scroll-fade/scroll-fade.component.scss
#	tedi/components/helpers/scroll-fade/scroll-fade.component.ts
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.

1 participant