Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions demo/admin/src/common/blocks/ContactFormBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { type BlockInterface, createBlockSkeleton } from "@comet/cms-admin";
import { type ContactFormBlockData, type ContactFormBlockInput } from "@src/blocks.generated";
import { FormattedMessage } from "react-intl";

export const ContactFormBlock: BlockInterface<ContactFormBlockData, Record<string, never>, ContactFormBlockInput> = {
...createBlockSkeleton(),
name: "ContactForm",
displayName: <FormattedMessage id="blocks.contactForm" defaultMessage="Contact Form" />,
defaultValues: () => ({}),
};
2 changes: 2 additions & 0 deletions demo/admin/src/documents/pages/blocks/PageContentBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AnchorBlock, createBlocksBlock, DamImageBlock } from "@comet/cms-admin";
import { AccordionBlock } from "@src/common/blocks/AccordionBlock";
import { ContactFormBlock } from "@src/common/blocks/ContactFormBlock";
import { LayoutBlock } from "@src/common/blocks/LayoutBlock";
import { MediaGalleryBlock } from "@src/common/blocks/MediaGalleryBlock";
import { PageTreeIndexBlock } from "@src/common/blocks/PageTreeIndexBlock";
Expand Down Expand Up @@ -50,6 +51,7 @@ export const PageContentBlock = createBlocksBlock({
fullWidthImage: FullWidthImageBlock,
productList: ProductListBlock,
pageTreeIndex: PageTreeIndexBlock,
contactForm: ContactFormBlock,
},
additionalItemFields: {
...userGroupAdditionalItemFields,
Expand Down
11 changes: 9 additions & 2 deletions demo/api/block-meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,11 @@
}
]
},
{
"name": "ContactForm",
"fields": [],
"inputFields": []
},
{
"name": "ContentGroup",
"fields": [
Expand Down Expand Up @@ -2106,7 +2111,8 @@
"textImage": "TextImage",
"fullWidthImage": "FullWidthImage",
"productList": "ProductList",
"pageTreeIndex": "PageTreeIndex"
"pageTreeIndex": "PageTreeIndex",
"contactForm": "ContactForm"
},
"nullable": false
},
Expand Down Expand Up @@ -2171,7 +2177,8 @@
"textImage": "TextImage",
"fullWidthImage": "FullWidthImage",
"productList": "ProductList",
"pageTreeIndex": "PageTreeIndex"
"pageTreeIndex": "PageTreeIndex",
"contactForm": "ContactForm"
},
"nullable": false
},
Expand Down
11 changes: 11 additions & 0 deletions demo/api/src/common/blocks/contact-form.block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BlockData, BlockInput, blockInputToData, createBlock } from "@comet/cms-api";

class ContactFormBlockData extends BlockData {}

class ContactFormBlockInput extends BlockInput {
transformToBlockData(): ContactFormBlockData {
return blockInputToData(ContactFormBlockData, this);
}
}

export const ContactFormBlock = createBlock(ContactFormBlockData, ContactFormBlockInput, "ContactForm");
2 changes: 2 additions & 0 deletions demo/api/src/db/fixtures/fixtures.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { YouTubeVideoBlockFixtureService } from "./generators/blocks/media/youtu
import { AnchorBlockFixtureService } from "./generators/blocks/navigation/anchor-block-fixture.service";
import { CallToActionBlockFixtureService } from "./generators/blocks/navigation/call-to-action-block-fixture.service";
import { CallToActionListBlockFixtureService } from "./generators/blocks/navigation/call-to-action-list-block.service";
import { ContactFormBlockFixtureService } from "./generators/blocks/navigation/contact-form-block-fixture.service";
import { LinkBlockFixtureService } from "./generators/blocks/navigation/link-block-fixture.service";
import { LinkListBlockFixtureService } from "./generators/blocks/navigation/link-list-block-fixture.service";
import { PageTreeIndexBlockFixtureService } from "./generators/blocks/navigation/page-tree-index-block-fixture.service";
Expand Down Expand Up @@ -79,6 +80,7 @@ import { VideoFixtureService } from "./generators/video-fixture.service";
CallToActionBlockFixtureService,
CallToActionListBlockFixtureService,
ColumnsBlockFixtureService,
ContactFormBlockFixtureService,
ContentGroupBlockFixtureService,
DamImageBlockFixtureService,
DamVideoBlockFixtureService,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ExtractBlockInputFactoryProps } from "@comet/cms-api";
import { Injectable } from "@nestjs/common";
import { ContactFormBlock } from "@src/common/blocks/contact-form.block";

@Injectable()
export class ContactFormBlockFixtureService {
async generateBlockInput(): Promise<ExtractBlockInputFactoryProps<typeof ContactFormBlock>> {
return {};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { FullWidthImageBlockFixtureService } from "./blocks/media/full-width-ima
import { MediaGalleryBlockFixtureService } from "./blocks/media/media-gallery-block-fixture.service";
import { StandaloneMediaBlockFixtureService } from "./blocks/media/standalone-media-block-fixture.service";
import { AnchorBlockFixtureService } from "./blocks/navigation/anchor-block-fixture.service";
import { ContactFormBlockFixtureService } from "./blocks/navigation/contact-form-block-fixture.service";
import { LinkListBlockFixtureService } from "./blocks/navigation/link-list-block-fixture.service";
import { PageTreeIndexBlockFixtureService } from "./blocks/navigation/page-tree-index-block-fixture.service";
import { StandaloneCallToActionListBlockFixtureService } from "./blocks/navigation/standalone-call-to-action-list-block-fixture.service";
Expand Down Expand Up @@ -53,6 +54,7 @@ export class PageContentBlockFixtureService {
private readonly standaloneRichTextBlockFixtureService: StandaloneRichTextBlockFixtureService,
private readonly productListBlockFixtureService: ProductListBlockFixtureService,
private readonly pageTreeIndexBlockFixtureService: PageTreeIndexBlockFixtureService,
private readonly contactFormBlockFixtureService: ContactFormBlockFixtureService,
) {}

async generateBlockInput(blockCategory?: BlockCategory): Promise<ExtractBlockInputFactoryProps<typeof PageContentBlock>> {
Expand Down Expand Up @@ -82,6 +84,7 @@ export class PageContentBlockFixtureService {
richtext: ["textAndContent", this.standaloneRichTextBlockFixtureService],
textImage: ["textAndContent", this.textImageBlockFixtureService],
productList: ["textAndContent", this.productListBlockFixtureService],
contactForm: ["textAndContent", this.contactFormBlockFixtureService],
};

const supportedBlocksFixtureGenerators = Object.entries(fixtures)
Expand Down
2 changes: 2 additions & 0 deletions demo/api/src/documents/pages/blocks/page-content.block.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AnchorBlock, BaseBlocksBlockItemData, BaseBlocksBlockItemInput, BlockField, createBlocksBlock, DamImageBlock } from "@comet/cms-api";
import { AccordionBlock } from "@src/common/blocks/accordion.block";
import { ContactFormBlock } from "@src/common/blocks/contact-form.block";
import { MediaGalleryBlock } from "@src/common/blocks/media-gallery.block";
import { PageTreeIndexBlock } from "@src/common/blocks/page-tree-index.block";
import { SpaceBlock } from "@src/common/blocks/space.block";
Expand Down Expand Up @@ -47,6 +48,7 @@ const supportedBlocks = {
fullWidthImage: FullWidthImageBlock,
productList: ProductListBlock,
pageTreeIndex: PageTreeIndexBlock,
contactForm: ContactFormBlock,
};

class BlocksBlockItemData extends BaseBlocksBlockItemData(supportedBlocks) {
Expand Down
71 changes: 71 additions & 0 deletions demo/site/src/common/blocks/ContactFormBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use client";
import { type PropsWithData, withPreview } from "@comet/site-nextjs";
import { type ContactFormBlockData } from "@src/blocks.generated";
import { FormattedMessage, useIntl } from "react-intl";

import { Button } from "../components/Button";
import { CheckboxField } from "../components/form/CheckboxField";
import { InputField } from "../components/form/InputField";
import { SelectField } from "../components/form/SelectField";

const subjectOptions = [
{ value: "Option 1", label: "Option 1" },
{ value: "Option 2", label: "Option 2" },
{ value: "Option 3", label: "Option 3" },
];

export const ContactFormBlock = withPreview(
({ data }: PropsWithData<ContactFormBlockData>) => {
const intl = useIntl();
return (
<>
<InputField
label={intl.formatMessage({ id: "contactForm.name.label", defaultMessage: "Name" })}
placeholder={intl.formatMessage({ id: "contactForm.name.placeholder", defaultMessage: "First and last name" })}
required
/>
<InputField
label={intl.formatMessage({ id: "contactForm.company.label", defaultMessage: "Company" })}
placeholder={intl.formatMessage({ id: "contactForm.company.placeholder", defaultMessage: "Company name" })}
/>
<InputField
label={intl.formatMessage({ id: "contactForm.email.label", defaultMessage: "Email" })}
placeholder={intl.formatMessage({ id: "contactForm.email.placeholder", defaultMessage: "Your email address" })}
required
/>
<InputField
label={intl.formatMessage({ id: "contactForm.phoneNumber.label", defaultMessage: "Phone Number" })}
placeholder={intl.formatMessage({ id: "contactForm.phoneNumber.placeholder", defaultMessage: "0043123456789" })}
helperText={intl.formatMessage({
id: "contactForm.phoneNumber.helperText",
defaultMessage: "Please enter without special characters and spaces",
})}
/>
<SelectField
label={intl.formatMessage({ id: "contactForm.subject.label", defaultMessage: "Subject" })}
required
placeholder={intl.formatMessage({ id: "contactForm.subject.placeholder", defaultMessage: "Please select" })}
options={subjectOptions}
/>
<InputField
label={intl.formatMessage({ id: "contactForm.message.label", defaultMessage: "Message" })}
placeholder={intl.formatMessage({ id: "contactForm.message.placeholder", defaultMessage: "Your message" })}
textArea
required
/>
<CheckboxField
label={intl.formatMessage({
id: "contactForm.privacyConsent.label",
defaultMessage:
"I agree that my information from the contact form will be collected and processed to answer my inquiry. Note: You can revoke your consent at any time by email to [email protected]. For more information, please see our privacy policy.",
})}
required
/>
<Button type="submit" variant="contained">
<FormattedMessage id="contactForm.submitButton.label" defaultMessage="Submit" />
</Button>
</>
);
},
{ label: "Contact Form" },
);
57 changes: 57 additions & 0 deletions demo/site/src/common/components/form/CheckboxField.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
.checkboxWrapper {
display: flex;
align-items: center;
}

.checkboxInput {
appearance: none;
cursor: pointer;
content: "";
top: 2px;
left: 0;
width: 24px;
height: 24px;
min-width: 24px;
min-height: 24px;
margin: 0;
border-radius: 4px;
border: 1px solid var(--contact-form-checkbox-border);
background-color: var(--contact-form-checkbox-bg-default);
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 4%);

&:hover {
border: 1px solid var(--contact-form-checkbox-border-hover);
background-color: var(--contact-form-checkbox-bg-hover);
box-shadow: 0 8px 16px 0 rgb(0 0 0 / 8%);
}

&:focus {
border: 2px solid var(--contact-form-checkbox-border-focus);
background-color: var(--contact-form-checkbox-bg-default);
outline: none;
}

&:checked {
background-color: var(--contact-form-checkbox-bg-selected);
background-image: url("/assets/checked.svg");
background-repeat: no-repeat;
background-position: center;
background-size: 16px 16px;
box-shadow: none;

&:hover {
background-color: var(--contact-form-checkbox-bg-selected-hover);
}
}
}

.checkboxLabel {
width: 100%;
height: 100%;
padding-left: 12px;
}

.helperText {
color: var(--contact-form-text-helper-text);
margin-top: var(--spacing-s200);
}
35 changes: 35 additions & 0 deletions demo/site/src/common/components/form/CheckboxField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Typography } from "../Typography";
import styles from "./CheckboxField.module.scss";

interface CheckboxFieldProps {
label: React.ReactNode;
helperText?: string;
required?: boolean;
}

export function CheckboxField({ label, helperText, required }: CheckboxFieldProps): React.ReactElement {
return (
<div>
<div className={styles.checkboxWrapper}>
<input type="checkbox" className={styles.checkboxInput} />
<label>
<Typography as="span" variant="paragraph300" className={styles.checkboxLabel}>
{label}
</Typography>
{!required && (
<span>
<Typography as="span" variant="paragraph200" color="var(--text-secondary)">
(optional)
</Typography>
</span>
)}
</label>
</div>
{helperText && (
<div className={styles.helperText}>
<Typography variant="paragraph200">{helperText}</Typography>
</div>
)}
</div>
);
}
51 changes: 51 additions & 0 deletions demo/site/src/common/components/form/InputField.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@use "@src/styles/variables" as *;

.input {
position: relative;
margin-bottom: var(--spacing-s200);
}

.labelWrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-s100);
font-size: 14px;
}

.inputField {
display: flex;
height: 48px;
padding: 0 var(--spacing-s300);
align-items: center;
gap: var(--spacing-s100);
align-self: stretch;
border-radius: 4px;
border: 1px solid var(--contact-form-border-border);
width: 100%;
color: var(--contact-form-text-input-text);
font-size: 16px;
line-height: 24px;
text-overflow: ellipsis;
box-sizing: border-box;
background-color: transparent;
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 4%);

&:hover {
box-shadow: 0 8px 16px 0 rgb(0 0 0 / 8%);
}

&::placeholder {
color: var(--contact-form-text-placeholder);
}
}

.inputLabel {
font-weight: 700;
color: var(--contact-form-text-label);
}

.helperText {
color: var(--contact-form-text-helper-text);
margin-top: var(--spacing-s200);
}
39 changes: 39 additions & 0 deletions demo/site/src/common/components/form/InputField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Typography } from "../Typography";
import styles from "./InputField.module.scss";

interface InputProps {
label: React.ReactNode;
required?: boolean;
placeholder?: string;
helperText?: string;
textArea?: boolean;
}

export function InputField({ label, required = false, placeholder, helperText, textArea = false }: InputProps): React.ReactElement {
return (
<div className={styles.input}>
<label className={styles.labelWrapper}>
<Typography as="span" variant="paragraph300" className={styles.inputLabel}>
{label}
</Typography>
{!required && (
<span>
<Typography as="span" variant="paragraph200" color="var(--text-secondary)">
(optional)
</Typography>
</span>
)}
</label>
{textArea ? (
<textarea required={required} placeholder={placeholder} className={styles.inputField} />
) : (
<input required={required} placeholder={placeholder} className={styles.inputField} />
)}
{helperText && (
<div className={styles.helperText}>
<Typography variant="paragraph200">{helperText}</Typography>
</div>
)}
</div>
);
}
Loading