Menu

Change language

GitHub repo

Contact Us

blockmarketing

Two-column contact section. Left: eyebrow, headline, lede, icon contact rows (email / phone / office) and a live Mapbox map pinned to your office (pass access-token + location; falls back to a placeholder without a token). Right: a Card-wrapped form (name / email / company / subject Select / message Textarea) that swaps to a success state. Emits "submit" with the payload.

Installation

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

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

ContactUsPayload
interface ContactUsPayload {
  name: string
  email: string
  company: string
  subject: string
  message: string
}

Theming

CSS custom properties referenced in this item. Override any of them in your :root or per-element to retheme.

--background

Files installed (2)

  • src/app/components/blocks/contact-us/contact-us.component.ts9.7 kB
    // Two-column contact section. Left: eyebrow, headline, lede, icon contact rows (email /
    // phone / office) and a live Mapbox map pinned to your office (pass accessToken +
    // location; falls back to a placeholder without a token). Right: a Card-wrapped form
    // (name / email / company / subject Select / message Textarea) that swaps to a success
    // state. Emits "submit" with the payload. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, computed, signal } from '@angular/core'
    import { CheckCircle2, LucideAngularModule, Mail, MapPin, Phone, Send } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiInputComponent } from '@/ui/input/input.component'
    import { UiLabelComponent } from '@/ui/label/label.component'
    import { UiMapComponent, UiMapMarkerComponent } from '@/ui/map/map.component'
    import {
      UiSelectComponent,
      UiSelectContentComponent,
      UiSelectItemComponent,
      UiSelectTriggerComponent,
      UiSelectValueComponent,
    } from '@/ui/select/select.component'
    import { UiTextareaComponent } from '@/ui/textarea/textarea.component'
    
    export interface ContactUsPayload {
      name: string
      email: string
      company: string
      subject: string
      message: string
    }
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-contact-us, [ui-contact-us]',
      standalone: true,
      // React renders the root <section> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiInputComponent,
        UiLabelComponent,
        UiMapComponent,
        UiMapMarkerComponent,
        UiSelectComponent,
        UiSelectContentComponent,
        UiSelectItemComponent,
        UiSelectTriggerComponent,
        UiSelectValueComponent,
        UiTextareaComponent,
      ],
      template: `
        <section data-slot="contact-us" [class]="rootClass">
          <div class="mx-auto max-w-6xl px-6 py-20">
            <div class="grid gap-10 lg:grid-cols-2 lg:items-start">
              <!-- Left: copy + contact rows + map -->
              <div class="space-y-6">
                <p class="text-primary text-sm font-medium tracking-widest uppercase">Contact</p>
                <h2 class="text-3xl font-semibold tracking-tight sm:text-4xl">Talk to our team</h2>
                <p class="text-muted-foreground max-w-md">
                  Questions about pricing, onboarding, or a custom plan? Send a note and we'll get back within one business
                  day.
                </p>
    
                <ul class="space-y-3">
                  <li class="flex items-center gap-3">
                    <span class="bg-muted text-foreground flex size-9 items-center justify-center rounded-lg">
                      <lucide-icon [img]="Mail" class="size-4" />
                    </span>
                    <span class="text-sm">[email protected]</span>
                  </li>
                  <li class="flex items-center gap-3">
                    <span class="bg-muted text-foreground flex size-9 items-center justify-center rounded-lg">
                      <lucide-icon [img]="Phone" class="size-4" />
                    </span>
                    <span class="text-sm">+1 (555) 010-2030</span>
                  </li>
                  <li class="flex items-center gap-3">
                    <span class="bg-muted text-foreground flex size-9 items-center justify-center rounded-lg">
                      <lucide-icon [img]="MapPin" class="size-4" />
                    </span>
                    <span class="text-sm">{{ locationLabel }}</span>
                  </li>
                </ul>
    
                <div class="h-56 overflow-hidden rounded-xl border">
                  @if (accessToken) {
                    <div ui-map [accessToken]="accessToken" [center]="location" [zoom]="11" muted class="size-full">
                      <ui-map-marker [lngLat]="location" anchor="bottom">
                        <svg viewBox="0 0 24 24" width="26" height="34" class="text-primary drop-shadow">
                          <path
                            d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"
                            fill="currentColor"
                            stroke="currentColor"
                            stroke-width="1.5"
                          />
                          <circle cx="12" cy="9" r="2.6" fill="var(--background)" />
                        </svg>
                      </ui-map-marker>
                    </div>
                  } @else {
                    <div class="bg-muted text-muted-foreground flex size-full items-center justify-center gap-2 text-sm">
                      <lucide-icon [img]="MapPin" class="size-4" /> {{ locationLabel }}
                    </div>
                  }
                </div>
              </div>
    
              <!-- Right: form -->
              <div ui-card>
                @if (!sent()) {
                  <div ui-card-header>
                    <h3 ui-card-title>Send a message</h3>
                    <p ui-card-description>We'll route it to the right person.</p>
                  </div>
                  <div ui-card-content class="space-y-4">
                    <div class="grid gap-4 sm:grid-cols-2">
                      <div class="space-y-1.5">
                        <label ui-label for="cu-name">Name</label>
                        <ui-input
                          id="cu-name"
                          [value]="name()"
                          (valueChange)="name.set($event)"
                          placeholder="Jane Cooper"
                        />
                      </div>
                      <div class="space-y-1.5">
                        <label ui-label for="cu-email">Email</label>
                        <ui-input
                          id="cu-email"
                          [value]="email()"
                          (valueChange)="email.set($event)"
                          type="email"
                          placeholder="[email protected]"
                        />
                      </div>
                    </div>
                    <div class="space-y-1.5">
                      <label ui-label for="cu-company">Company</label>
                      <ui-input
                        id="cu-company"
                        [value]="company()"
                        (valueChange)="company.set($event)"
                        placeholder="Acme Inc."
                      />
                    </div>
                    <div class="space-y-1.5">
                      <label ui-label>Subject</label>
                      <ui-select [value]="subject()" (valueChange)="subject.set($event)">
                        <button ui-select-trigger>
                          <ui-select-value placeholder="Choose a topic" />
                        </button>
                        <ui-select-content>
                          <ui-select-item value="sales">Sales</ui-select-item>
                          <ui-select-item value="support">Support</ui-select-item>
                          <ui-select-item value="partnership">Partnership</ui-select-item>
                        </ui-select-content>
                      </ui-select>
                    </div>
                    <div class="space-y-1.5">
                      <label ui-label for="cu-message">Message</label>
                      <ui-textarea
                        id="cu-message"
                        [value]="message()"
                        (valueChange)="message.set($event)"
                        [rows]="4"
                        placeholder="How can we help?"
                      />
                    </div>
                    <button ui-button class="w-full" [disabled]="!canSubmit()" (click)="send()">
                      <lucide-icon [img]="Send" class="size-4" /> Send message
                    </button>
                  </div>
                } @else {
                  <div ui-card-content class="flex flex-col items-center gap-3 py-16 text-center">
                    <span class="bg-success/10 text-success flex size-12 items-center justify-center rounded-full">
                      <lucide-icon [img]="CheckCircle2" class="size-6" />
                    </span>
                    <p class="text-lg font-semibold">Message sent</p>
                    <p class="text-muted-foreground max-w-xs text-sm">
                      Thanks, {{ name() }} — we'll reply to {{ email() }} within one business day.
                    </p>
                    <button ui-button variant="outline" (click)="sent.set(false)">Send another</button>
                  </div>
                }
              </div>
            </div>
          </div>
        </section>
      `,
    })
    export class UiContactUsComponent {
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly Mail = Mail
      protected readonly MapPin = MapPin
      protected readonly Phone = Phone
      protected readonly Send = Send
    
      /** Mapbox token. Without it the map area shows a styled placeholder. */
      @Input() accessToken = ''
      /** Office [lng, lat]. */
      @Input() location: [number, number] = [-122.4194, 37.7749]
      @Input() locationLabel = 'San Francisco, CA'
      @Input('class') className?: string
      @Output() readonly submit = new EventEmitter<ContactUsPayload>()
    
      readonly name = signal('')
      readonly email = signal('')
      readonly company = signal('')
      readonly subject = signal('sales')
      readonly message = signal('')
      readonly sent = signal(false)
    
      readonly canSubmit = computed(() => !!this.name() && !!this.email() && !!this.message())
    
      get rootClass(): string {
        return cn('bg-background', this.className)
      }
    
      send(): ContactUsPayload | null {
        if (!this.canSubmit()) return null
        const payload: ContactUsPayload = {
          name: this.name(),
          email: this.email(),
          company: this.company(),
          subject: this.subject(),
          message: this.message(),
        }
        this.sent.set(true)
        this.submit.emit(payload)
        return payload
      }
    }
    
  • src/app/components/blocks/contact-us/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/contact-us.json