Announcement — Rotating Strip
blockmarketingAnnouncement bar that cycles through several messages on a timer, pausing on hover and on focus, with dot controls for stepping between them manually.
Installation
$pnpm dlx uipkge-ng@latest add announcement-rotating-strip$npx uipkge-ng@latest add announcement-rotating-strip$yarn dlx uipkge-ng@latest add announcement-rotating-strip$bunx uipkge-ng@latest add announcement-rotating-stripManifest:
https://uipkge.dev/r/angular/announcement-rotating-strip.jsonInstalls to:src/app/components/blocks/announcement-rotating-strip/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
RotatingAnnouncementinterface RotatingAnnouncement {
label: string
text: string
cta: string
}npm dependencies
Files installed (2)
src/app/components/blocks/announcement-rotating-strip/announcement-rotating-strip.component.ts4 kB
// Announcement bar that cycles through several messages on a timer, pausing on // hover and on focus, with dot controls for stepping between them manually. Port of // the Vue/React block 1:1. Edit `messages` to change the rotation; `interval` sets // the dwell time per message in milliseconds. import { ChangeDetectionStrategy, Component, DestroyRef, Input, OnDestroy, OnInit, inject, signal } from '@angular/core' import { ArrowRight, LucideAngularModule } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' export interface RotatingAnnouncement { label: string text: string cta: string } const messages: RotatingAnnouncement[] = [ { label: 'New', text: 'Row-level scoping now evaluates against SCIM groups.', cta: 'Changelog' }, { label: 'Event', text: 'Live walkthrough of the five-week rollout, every Thursday.', cta: 'Save a seat' }, { label: 'Docs', text: 'The reconciliation checklist for your first close is published.', cta: 'Read it' }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-announcement-rotating-strip, [ui-announcement-rotating-strip]', standalone: true, // The block renders its own root <div>: keep this wrapper out of the layout. host: { '[attr.class]': '"contents"' }, imports: [LucideAngularModule, UiBadgeComponent, UiButtonComponent], template: ` <div data-slot="announcement-rotating-strip" class="border-border bg-muted/60 border-b backdrop-blur" (mouseenter)="paused.set(true)" (mouseleave)="paused.set(false)" (focusin)="paused.set(true)" (focusout)="paused.set(false)" > <div class="mx-auto flex h-11 max-w-6xl items-center gap-3 px-6 text-sm"> <!-- aria-live announces the rotation to screen readers without stealing focus. --> <div class="flex min-w-0 flex-1 items-center gap-3" aria-live="polite" aria-atomic="true"> <span ui-badge variant="secondary" class="shrink-0">{{ messages[index()].label }}</span> <p class="min-w-0 truncate">{{ messages[index()].text }}</p> <button ui-button variant="link" size="sm" class="hidden h-auto shrink-0 p-0 text-xs sm:inline-flex"> {{ messages[index()].cta }} <lucide-icon [img]="ArrowRight" [class]="'ml-1 size-3'" aria-hidden="true" /> </button> </div> <div class="flex shrink-0 items-center gap-1.5"> @for (message of messages; track message.text; let i = $index) { <button type="button" [class]="dotClass(i)" [attr.aria-label]="'Show announcement ' + (i + 1) + ' of ' + messages.length" [attr.aria-current]="i === index() ? 'true' : null" (click)="goTo(i)" ></button> } </div> </div> </div> `, }) export class UiAnnouncementRotatingStripComponent implements OnInit, OnDestroy { @Input() interval = 6000 readonly index = signal(0) readonly paused = signal(false) readonly messages = messages protected readonly ArrowRight = ArrowRight private timer: ReturnType<typeof setInterval> | undefined constructor() { inject(DestroyRef).onDestroy(() => this.stop()) } ngOnInit(): void { this.start() } ngOnDestroy(): void { this.stop() } advance(): void { this.index.update((i) => (i + 1) % messages.length) } goTo(i: number): void { this.index.set(i) } start(): void { this.stop() this.timer = setInterval(() => { if (!this.paused()) this.advance() }, this.interval) } stop(): void { clearInterval(this.timer) this.timer = undefined } dotClass(i: number): string { return cn( 'focus-visible:ring-ring size-1.5 rounded-full transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none', i === this.index() ? 'bg-foreground' : 'bg-muted-foreground/40 hover:bg-muted-foreground', ) } }src/app/components/blocks/announcement-rotating-strip/index.ts0.1 kB
export { UiAnnouncementRotatingStripComponent, type RotatingAnnouncement, } from './announcement-rotating-strip.component'
Raw manifest:https://uipkge.dev/r/angular/announcement-rotating-strip.json