forked from cms/tibi-svelte-starter
✨ feat: add new contact form, hero, features, and richtext blocks; implement scroll-reveal action and update styles
- Introduced ContactFormBlock, FeaturesBlock, HeroBlock, and RichtextBlock components. - Implemented a scroll-reveal action for animations on element visibility. - Enhanced CSS styles for better theming and prose formatting. - Added localization support for new components and updated existing translations. - Created e2e tests for demo pages including contact form validation and navigation. - Added a video tour showcasing the demo pages and interactions.
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
<script lang="ts">
|
||||
import { _ } from "../lib/i18n/index"
|
||||
import { reveal } from "../lib/actions/reveal"
|
||||
import { addToast } from "../lib/toast"
|
||||
import Form from "../widgets/Form.svelte"
|
||||
import Input from "../widgets/Input.svelte"
|
||||
import Select from "../widgets/Select.svelte"
|
||||
import Button from "../widgets/Button.svelte"
|
||||
|
||||
let { block }: { block: ContentBlockEntry } = $props()
|
||||
|
||||
const paddingTop = $derived(
|
||||
block.padding?.top === "lg"
|
||||
? "pt-20"
|
||||
: block.padding?.top === "md"
|
||||
? "pt-12"
|
||||
: block.padding?.top === "sm"
|
||||
? "pt-8"
|
||||
: "pt-4"
|
||||
)
|
||||
const paddingBottom = $derived(
|
||||
block.padding?.bottom === "lg"
|
||||
? "pb-20"
|
||||
: block.padding?.bottom === "md"
|
||||
? "pb-12"
|
||||
: block.padding?.bottom === "sm"
|
||||
? "pb-8"
|
||||
: "pb-4"
|
||||
)
|
||||
|
||||
let name = $state("")
|
||||
let email = $state("")
|
||||
let subject = $state("")
|
||||
let message = $state("")
|
||||
let sending = $state(false)
|
||||
let formRef = $state<{ validate: () => Promise<boolean> } | null>(null)
|
||||
|
||||
const subjectOptions = $derived([
|
||||
{ value: "", label: $_("form.selectSubject") },
|
||||
{ value: "general", label: $_("form.subjects.general") },
|
||||
{ value: "project", label: $_("form.subjects.project") },
|
||||
{ value: "support", label: $_("form.subjects.support") },
|
||||
{ value: "feedback", label: $_("form.subjects.feedback") },
|
||||
])
|
||||
|
||||
async function handleSubmit(e: SubmitEvent) {
|
||||
e.preventDefault()
|
||||
if (!formRef) return
|
||||
|
||||
const isValid = await formRef.validate()
|
||||
if (!isValid) return
|
||||
|
||||
sending = true
|
||||
|
||||
// Simulate API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 1200))
|
||||
|
||||
addToast($_("form.success"), "success", 5000)
|
||||
|
||||
// Reset form
|
||||
name = ""
|
||||
email = ""
|
||||
subject = ""
|
||||
message = ""
|
||||
sending = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<section
|
||||
data-block="contact-form"
|
||||
class="contact-form-section {paddingTop} {paddingBottom}"
|
||||
id={block.anchorId || undefined}
|
||||
>
|
||||
<div class="max-w-2xl mx-auto px-6">
|
||||
{#if block.headline}
|
||||
<div use:reveal>
|
||||
<h2 class="text-3xl font-display font-bold text-gray-900 mb-8">
|
||||
{block.headline}
|
||||
</h2>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div use:reveal={{ delay: 150 }}>
|
||||
<Form bind:this={formRef} onsubmit={handleSubmit} class="space-y-6">
|
||||
<div class="grid sm:grid-cols-2 gap-6">
|
||||
<Input
|
||||
label={$_("form.name")}
|
||||
hideLabel={false}
|
||||
bind:value={name}
|
||||
placeholder={$_("form.name")}
|
||||
required
|
||||
name="name"
|
||||
messages={{ valueMissing: $_("form.validation.required") }}
|
||||
/>
|
||||
<Input
|
||||
label={$_("form.email")}
|
||||
hideLabel={false}
|
||||
bind:value={email}
|
||||
placeholder={$_("form.email")}
|
||||
type="email"
|
||||
required
|
||||
name="email"
|
||||
messages={{
|
||||
valueMissing: $_("form.validation.required"),
|
||||
typeMismatch: $_("form.validation.email"),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
label={$_("form.subject")}
|
||||
hideLabel={false}
|
||||
bind:value={subject}
|
||||
options={subjectOptions}
|
||||
required
|
||||
name="subject"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<label for="contact-message" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{$_("form.message")}
|
||||
</label>
|
||||
<textarea
|
||||
id="contact-message"
|
||||
bind:value={message}
|
||||
rows="5"
|
||||
required
|
||||
name="message"
|
||||
placeholder={$_("form.message")}
|
||||
class="w-full rounded-lg border border-gray-300 px-4 py-3 text-gray-900 shadow-sm focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20 focus:outline-none transition-colors resize-y"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
text={sending ? $_("loading") : $_("form.send")}
|
||||
disabled={sending}
|
||||
class="bg-brand-600! hover:bg-brand-700! rounded-xl!"
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
Reference in New Issue
Block a user