Menu

Change language

GitHub repo

Author Bio Box

blockmedia

Editorial author signature card with avatar, verified badge, social links row, recent articles list, and interactive RSS subscribe button.

Installation

$npx uipkge-ng@latest add author-bio-box
Manifest:https://uipkge.dev/r/angular/author-bio-box.jsonInstalls to:src/app/components/blocks/author-bio-box/

Variants

Loading interactive previews…

Files installed (2)

  • src/app/components/blocks/author-bio-box/author-bio-box.component.ts31.6 kB
    // Editorial author signature card with avatar, verified badge, social links row,
    // recent articles list, and interactive follow / RSS subscribe buttons. Port of the
    // Vue/React block 1:1 — `variant` picks 'default', 'split', or 'compact'.
    import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core'
    import {
      ArrowUpRight,
      BadgeCheck,
      BookOpen,
      Check,
      Globe,
      LucideAngularModule,
      MapPin,
      Rss,
      UserPlus,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } from '@/ui/avatar/avatar.component'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    
    export type AuthorBioBoxVariant = 'default' | 'split' | 'compact'
    
    const AVATAR_SRC = 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?q=80&w=256&auto=format&fit=crop'
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-author-bio-box, [ui-author-bio-box]',
      standalone: true,
      // The block renders its own root <div>: keep this wrapper out of the layout.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiAvatarImageComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiSeparatorComponent,
      ],
      template: `
        <div ui-card data-slot="author-bio-box" [attr.data-variant]="variant" [class]="rootClass">
          <!-- Subtle gradient background highlight -->
          <div
            class="from-primary/10 via-primary/5 pointer-events-none absolute inset-x-0 top-0 h-32 bg-gradient-to-br to-transparent"
            aria-hidden="true"
          ></div>
    
          @if (variant === 'split') {
            <div ui-card-content class="relative p-6 sm:p-8">
              <div class="grid gap-8 lg:grid-cols-12 lg:items-start">
                <div class="space-y-5 lg:col-span-7">
                  <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:gap-5">
                    <div class="relative size-16 shrink-0 sm:size-20">
                      <ui-avatar size="2xl" class="ring-background size-16 shadow-xs ring-2 sm:size-20">
                        <ui-avatar-image [src]="avatarSrc" alt="Elena Rostova" />
                        <ui-avatar-fallback class="text-lg font-semibold sm:text-xl">ER</ui-avatar-fallback>
                      </ui-avatar>
                      <div
                        class="bg-primary text-primary-foreground ring-background absolute -right-1 -bottom-1 flex size-5 items-center justify-center rounded-full shadow-xs ring-2 sm:size-6"
                        title="Verified Author"
                      >
                        <lucide-icon [img]="BadgeCheck" class="size-3.5 sm:size-4" />
                      </div>
                    </div>
                    <div class="min-w-0 space-y-1">
                      <div class="flex flex-wrap items-center gap-2">
                        <h3 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">Elena Rostova</h3>
                        <span ui-badge variant="secondary" class="gap-1 text-xs font-medium">
                          <lucide-icon [img]="BadgeCheck" class="text-primary size-3.5" /> Verified
                        </span>
                      </div>
                      <p class="text-muted-foreground text-sm font-medium">
                        Principal Design Engineer &amp; Core Contributor
                      </p>
                      <div class="text-muted-foreground flex items-center gap-1.5 text-xs">
                        <lucide-icon [img]="MapPin" class="size-3.5 shrink-0" />
                        <span>San Francisco, CA</span>
                      </div>
                    </div>
                  </div>
    
                  <p class="text-muted-foreground text-sm leading-relaxed sm:text-base">
                    Specializing in design token mathematics, headless accessibility primitives, and high-performance
                    dual-framework monorepos. Former Staff Engineer at Vercel.
                  </p>
    
                  <div class="flex flex-wrap items-center justify-between gap-4 pt-1">
                    <div class="flex flex-wrap items-center gap-2.5">
                      <button
                        ui-button
                        [variant]="isFollowing() ? 'secondary' : 'default'"
                        size="sm"
                        class="gap-2"
                        (click)="toggleFollow()"
                      >
                        @if (isFollowing()) {
                          <lucide-icon [img]="Check" class="text-success size-4" />
                        } @else {
                          <lucide-icon [img]="UserPlus" class="size-4" />
                        }
                        <span>{{ isFollowing() ? 'Following' : 'Follow Author' }}</span>
                      </button>
                      <button ui-button variant="outline" size="sm" [class]="rssClass('gap-2')" (click)="toggleRss()">
                        <lucide-icon [img]="Rss" class="text-warning size-4" />
                        <span>{{ isSubscribedRss() ? 'Subscribed via RSS' : 'RSS Feed' }}</span>
                      </button>
                    </div>
    
                    <div class="flex items-center gap-1">
                      <a
                        ui-button
                        variant="ghost"
                        size="icon-sm"
                        href="https://x.com"
                        target="_blank"
                        rel="noreferrer"
                        aria-label="Elena Rostova on X (Twitter)"
                      >
                        <svg viewBox="0 0 24 24" fill="currentColor" class="size-4" aria-hidden="true">
                          <path
                            d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
                          />
                        </svg>
                      </a>
                      <a
                        ui-button
                        variant="ghost"
                        size="icon-sm"
                        href="https://github.com"
                        target="_blank"
                        rel="noreferrer"
                        aria-label="Elena Rostova on GitHub"
                      >
                        <svg viewBox="0 0 24 24" fill="currentColor" class="size-4" aria-hidden="true">
                          <path
                            d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.55v-2.15c-3.2.69-3.87-1.36-3.87-1.36-.52-1.33-1.28-1.68-1.28-1.68-1.04-.71.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.02 1.76 2.69 1.25 3.35.96.1-.75.4-1.25.72-1.54-2.55-.29-5.24-1.28-5.24-5.68 0-1.26.45-2.28 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11.1 11.1 0 0 1 5.78 0c2.21-1.49 3.18-1.18 3.18-1.18.63 1.59.23 2.76.11 3.05.74.81 1.19 1.83 1.19 3.09 0 4.41-2.69 5.38-5.26 5.66.41.35.78 1.05.78 2.12v3.14c0 .31.21.67.8.55A11.51 11.51 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5z"
                          />
                        </svg>
                      </a>
                      <a
                        ui-button
                        variant="ghost"
                        size="icon-sm"
                        href="https://linkedin.com"
                        target="_blank"
                        rel="noreferrer"
                        aria-label="Elena Rostova on LinkedIn"
                      >
                        <svg viewBox="0 0 24 24" fill="currentColor" class="size-4" aria-hidden="true">
                          <path
                            d="M20.45 20.45h-3.55v-5.57c0-1.33-.03-3.04-1.85-3.04-1.85 0-2.14 1.45-2.14 2.94v5.67H9.36V9h3.41v1.56h.05c.47-.9 1.63-1.85 3.36-1.85 3.6 0 4.27 2.37 4.27 5.46zM5.34 7.43a2.06 2.06 0 1 1 0-4.12 2.06 2.06 0 0 1 0 4.12zM7.12 20.45H3.56V9h3.56zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.55C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.73V1.72C24 .77 23.2 0 22.22 0z"
                          />
                        </svg>
                      </a>
                      <a
                        ui-button
                        variant="ghost"
                        size="icon-sm"
                        href="https://elenarostova.dev"
                        target="_blank"
                        rel="noreferrer"
                        aria-label="Elena Rostova's Website"
                      >
                        <lucide-icon [img]="Globe" class="size-4" />
                      </a>
                      <a
                        ui-button
                        variant="ghost"
                        size="icon-sm"
                        href="https://substack.com"
                        target="_blank"
                        rel="noreferrer"
                        aria-label="Elena Rostova on Substack"
                      >
                        <svg viewBox="0 0 24 24" fill="currentColor" class="size-4" aria-hidden="true">
                          <path
                            d="M22.539 8.242H1.46V5.406h21.08v2.836zM1.46 10.812V24L12 18.11 22.54 24V10.812H1.46zM22.54 0H1.46v2.836h21.08V0z"
                          />
                        </svg>
                      </a>
                    </div>
                  </div>
                </div>
    
                <div class="space-y-4 lg:col-span-5">
                  <div class="flex items-center justify-between">
                    <div
                      class="text-muted-foreground flex items-center gap-2 text-xs font-semibold tracking-wider uppercase"
                    >
                      <lucide-icon [img]="BookOpen" class="text-primary size-4" />
                      <span>Recent Articles</span>
                    </div>
                    <span class="text-muted-foreground text-xs">3 published</span>
                  </div>
    
                  <div class="grid gap-3">
                    <a
                      href="#article-1"
                      class="group border-border/60 bg-muted/20 hover:border-border hover:bg-muted/60 focus-visible:ring-ring flex items-center justify-between gap-3 rounded-lg border p-3.5 transition-colors hover:shadow-xs focus-visible:ring-2 focus-visible:outline-none"
                    >
                      <div class="min-w-0 space-y-1">
                        <span class="text-muted-foreground text-xs">5 min read · Aug 18</span>
                        <h4 class="text-foreground group-hover:text-primary text-sm font-semibold transition-colors">
                          Why Zero-Dependency Registries Are Winning
                        </h4>
                      </div>
                      <div
                        class="border-border/60 bg-background text-muted-foreground group-hover:border-border group-hover:text-foreground flex size-7 shrink-0 items-center justify-center rounded-md border transition-colors"
                      >
                        <lucide-icon
                          [img]="ArrowUpRight"
                          class="size-3.5 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
                        />
                      </div>
                    </a>
    
                    <a
                      href="#article-2"
                      class="group border-border/60 bg-muted/20 hover:border-border hover:bg-muted/60 focus-visible:ring-ring flex items-center justify-between gap-3 rounded-lg border p-3.5 transition-colors hover:shadow-xs focus-visible:ring-2 focus-visible:outline-none"
                    >
                      <div class="min-w-0 space-y-1">
                        <span class="text-muted-foreground text-xs">8 min read · Aug 10</span>
                        <h4 class="text-foreground group-hover:text-primary text-sm font-semibold transition-colors">
                          Deconstructing OKLCH Color Palettes in Tailwind v4
                        </h4>
                      </div>
                      <div
                        class="border-border/60 bg-background text-muted-foreground group-hover:border-border group-hover:text-foreground flex size-7 shrink-0 items-center justify-center rounded-md border transition-colors"
                      >
                        <lucide-icon
                          [img]="ArrowUpRight"
                          class="size-3.5 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
                        />
                      </div>
                    </a>
    
                    <a
                      href="#article-3"
                      class="group border-border/60 bg-muted/20 hover:border-border hover:bg-muted/60 focus-visible:ring-ring flex items-center justify-between gap-3 rounded-lg border p-3.5 transition-colors hover:shadow-xs focus-visible:ring-2 focus-visible:outline-none"
                    >
                      <div class="min-w-0 space-y-1">
                        <span class="text-muted-foreground text-xs">12 min read · Jul 28</span>
                        <h4 class="text-foreground group-hover:text-primary text-sm font-semibold transition-colors">
                          Polymorphic Reka UI Primitives Architecture
                        </h4>
                      </div>
                      <div
                        class="border-border/60 bg-background text-muted-foreground group-hover:border-border group-hover:text-foreground flex size-7 shrink-0 items-center justify-center rounded-md border transition-colors"
                      >
                        <lucide-icon
                          [img]="ArrowUpRight"
                          class="size-3.5 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
                        />
                      </div>
                    </a>
                  </div>
                </div>
              </div>
            </div>
          } @else if (variant === 'compact') {
            <div ui-card-content class="relative space-y-5 p-5">
              <div class="flex items-center gap-3.5">
                <div class="relative size-12 shrink-0">
                  <ui-avatar size="lg" class="ring-background size-12 ring-2">
                    <ui-avatar-image [src]="avatarSrc" alt="Elena Rostova" />
                    <ui-avatar-fallback class="text-sm font-semibold">ER</ui-avatar-fallback>
                  </ui-avatar>
                  <div
                    class="bg-primary text-primary-foreground ring-background absolute -right-0.5 -bottom-0.5 flex size-4 items-center justify-center rounded-full ring-1"
                  >
                    <lucide-icon [img]="BadgeCheck" class="size-3" />
                  </div>
                </div>
                <div class="min-w-0 space-y-0.5">
                  <div class="flex items-center gap-1.5">
                    <h3 class="text-foreground truncate text-base font-bold tracking-tight">Elena Rostova</h3>
                    <span ui-badge variant="secondary" class="h-4 px-1.5 text-xs font-medium">Verified</span>
                  </div>
                  <p class="text-muted-foreground truncate text-xs">Principal Design Engineer</p>
                </div>
              </div>
    
              <p class="text-muted-foreground line-clamp-2 text-xs leading-relaxed">
                Specializing in design token mathematics, headless accessibility primitives, and high-performance
                dual-framework monorepos. Former Staff Engineer at Vercel.
              </p>
    
              <div class="flex items-center justify-between gap-2">
                <div class="flex items-center gap-2">
                  <button
                    ui-button
                    [variant]="isFollowing() ? 'secondary' : 'default'"
                    size="sm"
                    class="h-7 gap-1.5 px-2.5 text-xs"
                    (click)="toggleFollow()"
                  >
                    @if (isFollowing()) {
                      <lucide-icon [img]="Check" class="text-success size-3" />
                    } @else {
                      <lucide-icon [img]="UserPlus" class="size-3" />
                    }
                    <span>{{ isFollowing() ? 'Following' : 'Follow' }}</span>
                  </button>
                  <button
                    ui-button
                    variant="outline"
                    size="sm"
                    [class]="rssClass('h-7 gap-1.5 px-2.5 text-xs')"
                    (click)="toggleRss()"
                  >
                    <lucide-icon [img]="Rss" class="text-warning size-3" />
                    <span>{{ isSubscribedRss() ? 'Subscribed' : 'RSS' }}</span>
                  </button>
                </div>
                <div class="flex items-center gap-0.5">
                  <a
                    ui-button
                    variant="ghost"
                    size="icon-sm"
                    class="size-7"
                    href="https://x.com"
                    target="_blank"
                    rel="noreferrer"
                    aria-label="Elena Rostova on X (Twitter)"
                  >
                    <svg viewBox="0 0 24 24" fill="currentColor" class="size-3.5" aria-hidden="true">
                      <path
                        d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
                      />
                    </svg>
                  </a>
                  <a
                    ui-button
                    variant="ghost"
                    size="icon-sm"
                    class="size-7"
                    href="https://github.com"
                    target="_blank"
                    rel="noreferrer"
                    aria-label="Elena Rostova on GitHub"
                  >
                    <svg viewBox="0 0 24 24" fill="currentColor" class="size-3.5" aria-hidden="true">
                      <path
                        d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.55v-2.15c-3.2.69-3.87-1.36-3.87-1.36-.52-1.33-1.28-1.68-1.28-1.68-1.04-.71.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.02 1.76 2.69 1.25 3.35.96.1-.75.4-1.25.72-1.54-2.55-.29-5.24-1.28-5.24-5.68 0-1.26.45-2.28 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11.1 11.1 0 0 1 5.78 0c2.21-1.49 3.18-1.18 3.18-1.18.63 1.59.23 2.76.11 3.05.74.81 1.19 1.83 1.19 3.09 0 4.41-2.69 5.38-5.26 5.66.41.35.78 1.05.78 2.12v3.14c0 .31.21.67.8.55A11.51 11.51 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5z"
                      />
                    </svg>
                  </a>
                  <a
                    ui-button
                    variant="ghost"
                    size="icon-sm"
                    class="size-7"
                    href="https://elenarostova.dev"
                    target="_blank"
                    rel="noreferrer"
                    aria-label="Elena Rostova's Website"
                  >
                    <lucide-icon [img]="Globe" class="size-3.5" />
                  </a>
                </div>
              </div>
    
              <ui-separator />
    
              <div class="space-y-2.5">
                <div class="text-muted-foreground flex items-center justify-between text-xs font-medium">
                  <span class="tracking-wider uppercase">Latest Article</span>
                  <span>Aug 18</span>
                </div>
                <a
                  href="#article-1"
                  class="group border-border/60 bg-muted/20 hover:bg-muted/60 focus-visible:ring-ring flex items-center justify-between gap-2 rounded-md border p-2.5 transition-colors focus-visible:ring-2 focus-visible:outline-none"
                >
                  <span class="text-foreground group-hover:text-primary truncate text-xs font-semibold transition-colors">
                    Why Zero-Dependency Registries Are Winning
                  </span>
                  <lucide-icon
                    [img]="ArrowUpRight"
                    class="text-muted-foreground group-hover:text-foreground size-3.5 shrink-0 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
                  />
                </a>
              </div>
            </div>
          } @else {
            <div ui-card-content class="relative space-y-6 p-6 sm:p-8">
              <!-- Top Author Profile -->
              <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:gap-5">
                <div class="relative size-16 shrink-0 sm:size-20">
                  <ui-avatar size="2xl" class="ring-background size-16 shadow-xs ring-2 sm:size-20">
                    <ui-avatar-image [src]="avatarSrc" alt="Elena Rostova" />
                    <ui-avatar-fallback class="text-lg font-semibold sm:text-xl">ER</ui-avatar-fallback>
                  </ui-avatar>
                  <div
                    class="bg-primary text-primary-foreground ring-background absolute -right-1 -bottom-1 flex size-5 items-center justify-center rounded-full shadow-xs ring-2 sm:size-6"
                    title="Verified Author"
                  >
                    <lucide-icon [img]="BadgeCheck" class="size-3.5 sm:size-4" />
                  </div>
                </div>
                <div class="min-w-0 space-y-1.5">
                  <div class="flex flex-wrap items-center gap-2">
                    <h3 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">Elena Rostova</h3>
                    <span ui-badge variant="secondary" class="gap-1 text-xs font-medium">
                      <lucide-icon [img]="BadgeCheck" class="text-primary size-3.5" /> Verified Author
                    </span>
                  </div>
                  <p class="text-muted-foreground text-sm font-medium">Principal Design Engineer &amp; Core Contributor</p>
                  <div class="text-muted-foreground flex items-center gap-1.5 text-xs">
                    <lucide-icon [img]="MapPin" class="size-3.5 shrink-0" />
                    <span>San Francisco, CA</span>
                  </div>
                </div>
              </div>
    
              <!-- Author Bio Paragraph -->
              <p class="text-muted-foreground text-sm leading-relaxed sm:text-base">
                Specializing in design token mathematics, headless accessibility primitives, and high-performance
                dual-framework monorepos. Former Staff Engineer at Vercel.
              </p>
    
              <!-- Actions & Social Links Row -->
              <div class="flex flex-wrap items-center justify-between gap-4 pt-1">
                <div class="flex flex-wrap items-center gap-2.5">
                  <button
                    ui-button
                    [variant]="isFollowing() ? 'secondary' : 'default'"
                    size="sm"
                    class="gap-2"
                    (click)="toggleFollow()"
                  >
                    @if (isFollowing()) {
                      <lucide-icon [img]="Check" class="text-success size-4" />
                    } @else {
                      <lucide-icon [img]="UserPlus" class="size-4" />
                    }
                    <span>{{ isFollowing() ? 'Following' : 'Follow Author' }}</span>
                  </button>
                  <button ui-button variant="outline" size="sm" [class]="rssClass('gap-2')" (click)="toggleRss()">
                    <lucide-icon [img]="Rss" class="text-warning size-4" />
                    <span>{{ isSubscribedRss() ? 'Subscribed via RSS' : 'RSS Feed' }}</span>
                  </button>
                </div>
    
                <div class="flex items-center gap-1">
                  <a
                    ui-button
                    variant="ghost"
                    size="icon-sm"
                    href="https://x.com"
                    target="_blank"
                    rel="noreferrer"
                    aria-label="Elena Rostova on X (Twitter)"
                  >
                    <svg viewBox="0 0 24 24" fill="currentColor" class="size-4" aria-hidden="true">
                      <path
                        d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
                      />
                    </svg>
                  </a>
                  <a
                    ui-button
                    variant="ghost"
                    size="icon-sm"
                    href="https://github.com"
                    target="_blank"
                    rel="noreferrer"
                    aria-label="Elena Rostova on GitHub"
                  >
                    <svg viewBox="0 0 24 24" fill="currentColor" class="size-4" aria-hidden="true">
                      <path
                        d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.55v-2.15c-3.2.69-3.87-1.36-3.87-1.36-.52-1.33-1.28-1.68-1.28-1.68-1.04-.71.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.02 1.76 2.69 1.25 3.35.96.1-.75.4-1.25.72-1.54-2.55-.29-5.24-1.28-5.24-5.68 0-1.26.45-2.28 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11.1 11.1 0 0 1 5.78 0c2.21-1.49 3.18-1.18 3.18-1.18.63 1.59.23 2.76.11 3.05.74.81 1.19 1.83 1.19 3.09 0 4.41-2.69 5.38-5.26 5.66.41.35.78 1.05.78 2.12v3.14c0 .31.21.67.8.55A11.51 11.51 0 0 0 23.5 12C23.5 5.65 18.35.5 12 .5z"
                      />
                    </svg>
                  </a>
                  <a
                    ui-button
                    variant="ghost"
                    size="icon-sm"
                    href="https://linkedin.com"
                    target="_blank"
                    rel="noreferrer"
                    aria-label="Elena Rostova on LinkedIn"
                  >
                    <svg viewBox="0 0 24 24" fill="currentColor" class="size-4" aria-hidden="true">
                      <path
                        d="M20.45 20.45h-3.55v-5.57c0-1.33-.03-3.04-1.85-3.04-1.85 0-2.14 1.45-2.14 2.94v5.67H9.36V9h3.41v1.56h.05c.47-.9 1.63-1.85 3.36-1.85 3.6 0 4.27 2.37 4.27 5.46zM5.34 7.43a2.06 2.06 0 1 1 0-4.12 2.06 2.06 0 0 1 0 4.12zM7.12 20.45H3.56V9h3.56zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.55C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.73V1.72C24 .77 23.2 0 22.22 0z"
                      />
                    </svg>
                  </a>
                  <a
                    ui-button
                    variant="ghost"
                    size="icon-sm"
                    href="https://elenarostova.dev"
                    target="_blank"
                    rel="noreferrer"
                    aria-label="Elena Rostova's Website"
                  >
                    <lucide-icon [img]="Globe" class="size-4" />
                  </a>
                  <a
                    ui-button
                    variant="ghost"
                    size="icon-sm"
                    href="https://substack.com"
                    target="_blank"
                    rel="noreferrer"
                    aria-label="Elena Rostova on Substack"
                  >
                    <svg viewBox="0 0 24 24" fill="currentColor" class="size-4" aria-hidden="true">
                      <path
                        d="M22.539 8.242H1.46V5.406h21.08v2.836zM1.46 10.812V24L12 18.11 22.54 24V10.812H1.46zM22.54 0H1.46v2.836h21.08V0z"
                      />
                    </svg>
                  </a>
                </div>
              </div>
    
              <!-- Separator -->
              <ui-separator />
    
              <!-- Recent Articles by Author List (3 items) -->
              <div class="space-y-4">
                <div class="flex items-center justify-between">
                  <div class="text-muted-foreground flex items-center gap-2 text-xs font-semibold tracking-wider uppercase">
                    <lucide-icon [img]="BookOpen" class="text-primary size-4" />
                    <span>Recent Articles by Author</span>
                  </div>
                  <span class="text-muted-foreground text-xs">3 published</span>
                </div>
    
                <div class="grid gap-3 sm:grid-cols-1">
                  <!-- Article 1 -->
                  <a
                    href="#article-1"
                    class="group border-border/60 bg-muted/20 hover:border-border hover:bg-muted/60 focus-visible:ring-ring flex items-center justify-between gap-4 rounded-lg border p-4 transition-colors hover:shadow-xs focus-visible:ring-2 focus-visible:outline-none"
                  >
                    <div class="min-w-0 space-y-1">
                      <div class="text-muted-foreground flex items-center gap-2 text-xs">
                        <span ui-badge variant="outline" class="text-xs font-normal">Registry</span>
                        <span>5 min read · Aug 18</span>
                      </div>
                      <h4 class="text-foreground group-hover:text-primary text-sm font-semibold transition-colors">
                        Why Zero-Dependency Registries Are Winning
                      </h4>
                    </div>
                    <div
                      class="border-border/60 bg-background text-muted-foreground group-hover:border-border group-hover:text-foreground flex size-8 shrink-0 items-center justify-center rounded-md border transition-colors"
                    >
                      <lucide-icon
                        [img]="ArrowUpRight"
                        class="size-4 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
                      />
                    </div>
                  </a>
    
                  <!-- Article 2 -->
                  <a
                    href="#article-2"
                    class="group border-border/60 bg-muted/20 hover:border-border hover:bg-muted/60 focus-visible:ring-ring flex items-center justify-between gap-4 rounded-lg border p-4 transition-colors hover:shadow-xs focus-visible:ring-2 focus-visible:outline-none"
                  >
                    <div class="min-w-0 space-y-1">
                      <div class="text-muted-foreground flex items-center gap-2 text-xs">
                        <span ui-badge variant="outline" class="text-xs font-normal">Design Tokens</span>
                        <span>8 min read · Aug 10</span>
                      </div>
                      <h4 class="text-foreground group-hover:text-primary text-sm font-semibold transition-colors">
                        Deconstructing OKLCH Color Palettes in Tailwind v4
                      </h4>
                    </div>
                    <div
                      class="border-border/60 bg-background text-muted-foreground group-hover:border-border group-hover:text-foreground flex size-8 shrink-0 items-center justify-center rounded-md border transition-colors"
                    >
                      <lucide-icon
                        [img]="ArrowUpRight"
                        class="size-4 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
                      />
                    </div>
                  </a>
    
                  <!-- Article 3 -->
                  <a
                    href="#article-3"
                    class="group border-border/60 bg-muted/20 hover:border-border hover:bg-muted/60 focus-visible:ring-ring flex items-center justify-between gap-4 rounded-lg border p-4 transition-colors hover:shadow-xs focus-visible:ring-2 focus-visible:outline-none"
                  >
                    <div class="min-w-0 space-y-1">
                      <div class="text-muted-foreground flex items-center gap-2 text-xs">
                        <span ui-badge variant="outline" class="text-xs font-normal">Architecture</span>
                        <span>12 min read · Jul 28</span>
                      </div>
                      <h4 class="text-foreground group-hover:text-primary text-sm font-semibold transition-colors">
                        Polymorphic Reka UI Primitives Architecture
                      </h4>
                    </div>
                    <div
                      class="border-border/60 bg-background text-muted-foreground group-hover:border-border group-hover:text-foreground flex size-8 shrink-0 items-center justify-center rounded-md border transition-colors"
                    >
                      <lucide-icon
                        [img]="ArrowUpRight"
                        class="size-4 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
                      />
                    </div>
                  </a>
                </div>
              </div>
            </div>
          }
        </div>
      `,
    })
    export class UiAuthorBioBoxComponent {
      protected readonly ArrowUpRight = ArrowUpRight
      protected readonly BadgeCheck = BadgeCheck
      protected readonly BookOpen = BookOpen
      protected readonly Check = Check
      protected readonly Globe = Globe
      protected readonly MapPin = MapPin
      protected readonly Rss = Rss
      protected readonly UserPlus = UserPlus
      protected readonly avatarSrc = AVATAR_SRC
    
      /** 'default' is the standard editorial signature card; 'split' arranges profile and articles side-by-side; 'compact' tightens padding. */
      @Input() variant: AuthorBioBoxVariant = 'default'
      @Input('class') className?: string
    
      readonly isFollowing = signal(false)
      readonly isSubscribedRss = signal(false)
    
      get rootClass(): string {
        return cn('border-border bg-card text-card-foreground relative overflow-hidden shadow-xs', this.className)
      }
    
      rssClass(extra: string): string {
        return cn(extra, this.isSubscribedRss() && 'border-warning/30 bg-warning/10 text-warning')
      }
    
      toggleFollow(): void {
        this.isFollowing.update((v) => !v)
      }
    
      toggleRss(): void {
        this.isSubscribedRss.update((v) => !v)
      }
    }
    
  • src/app/components/blocks/author-bio-box/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/author-bio-box.json