Contact Us
blockmarketingTwo-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
$pnpm dlx uipkge-ng@latest add contact-us$npx uipkge-ng@latest add contact-us$yarn dlx uipkge-ng@latest add contact-us$bunx uipkge-ng@latest add contact-usManifest:
https://uipkge.dev/r/angular/contact-us.jsonInstalls to:src/app/components/blocks/contact-us/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.
ContactUsPayloadinterface 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.
--backgroundFiles 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
export { UiContactUsComponent, type ContactUsPayload } from './contact-us.component'
Raw manifest:https://uipkge.dev/r/angular/contact-us.json