Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/notes-label-placeholder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@godaddy/react": patch
---

Add optional `notes` prop to `Checkout` for overriding the notes-collection field's label and placeholder. Both fall back to localization (`t.general.notes` / `t.shipping.notesPlaceholder`) when omitted; pass an empty `placeholder` to render none. The override applies to the standalone notes section header and the `NotesForm` field.
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,63 @@ describe('Checkout notes UI', () => {
).not.toBeInTheDocument();
});

it('uses the custom notes label and placeholder from the notes prop (standalone)', async () => {
renderCheckout({
sessionOverrides: {
enableNotesCollection: true,
enableShipping: false,
enableLocalPickup: false,
},
checkoutProps: {
notes: { label: 'Gift message', placeholder: 'Say something nice' },
},
});
await waitForCheckoutReady();

const notes = document.querySelector(
'textarea[name="notes"]'
) as HTMLTextAreaElement;
expect(notes).toBeInTheDocument();
expect(notes).toHaveAttribute('placeholder', 'Say something nice');
expect(
screen.getByRole('heading', { name: 'Gift message' })
).toBeInTheDocument();
});

it('renders no placeholder when notes.placeholder is an empty string', async () => {
renderCheckout({
sessionOverrides: {
enableNotesCollection: true,
enableShipping: false,
enableLocalPickup: false,
},
checkoutProps: { notes: { placeholder: '' } },
});
await waitForCheckoutReady();

const notes = document.querySelector(
'textarea[name="notes"]'
) as HTMLTextAreaElement;
expect(notes).toHaveAttribute('placeholder', '');
});

it('falls back to localized notes label and placeholder without an override', async () => {
renderCheckout({
sessionOverrides: {
enableNotesCollection: true,
enableShipping: false,
enableLocalPickup: false,
},
});
await waitForCheckoutReady();

const notes = document.querySelector(
'textarea[name="notes"]'
) as HTMLTextAreaElement;
expect(notes).toHaveAttribute('placeholder', 'Notes or special instructions');
expect(screen.getByRole('heading', { name: 'Notes' })).toBeInTheDocument();
});

it('renders notes in both shipping and pickup flows', async () => {
const { user } = renderCheckout();
await waitForCheckoutReady();
Expand Down
9 changes: 9 additions & 0 deletions packages/react/src/components/checkout/checkout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,15 @@ export interface CheckoutProps {
defaultValues?: Pick<CheckoutFormData, 'contactEmail'>;
isLoading?: boolean;
loadingFallback?: ReactNode;
/**
* Overrides for the notes-collection field (rendered when the session has
* `enableNotesCollection`). Each falls back to localization when omitted;
* pass an empty `placeholder` to render no placeholder.
*/
notes?: {
label?: string;
placeholder?: string;
};
}

export function Checkout(props: CheckoutProps) {
Expand Down
14 changes: 11 additions & 3 deletions packages/react/src/components/checkout/form/checkout-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,10 @@ export function CheckoutForm({
{session?.enableNotesCollection ? (
<>
<Target id='checkout.form.notes.before' />
<NotesForm />
<NotesForm
label={props.notes?.label}
placeholder={props.notes?.placeholder}
/>
<Target id='checkout.form.notes.after' />
</>
) : null}
Expand All @@ -494,8 +497,13 @@ export function CheckoutForm({
{enableStandaloneNotes ? (
<CheckoutSection style={{ gridArea: 'notes' }}>
<Target id='checkout.form.notes.before' />
<CheckoutSectionHeader title={t.general.notes} />
<NotesForm />
<CheckoutSectionHeader
title={props.notes?.label ?? t.general.notes}
/>
<NotesForm
label={props.notes?.label}
placeholder={props.notes?.placeholder}
/>
<Target id='checkout.form.notes.after' />
</CheckoutSection>
) : null}
Expand Down
16 changes: 13 additions & 3 deletions packages/react/src/components/checkout/notes/notes-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@ import { useGoDaddyContext } from '@/godaddy-provider';
import { eventIds } from '@/tracking/events';
import { TrackingEventType, track } from '@/tracking/track';

export function NotesForm() {
export interface NotesFormProps {
/** Overrides the visually-hidden field label; falls back to `t.general.notes`. */
label?: string;
/**
* Overrides the textarea placeholder; falls back to
* `t.shipping.notesPlaceholder`. Pass an empty string for no placeholder.
*/
placeholder?: string;
}

export function NotesForm({ label, placeholder }: NotesFormProps = {}) {
const form = useFormContext();
const { t } = useGoDaddyContext();
const { isConfirmingCheckout, requiredFields } = useCheckoutContext();
Expand Down Expand Up @@ -78,9 +88,9 @@ export function NotesForm() {
name='notes'
render={({ field, fieldState }) => (
<FormItem>
<FormLabel className='sr-only'>{t.general.notes}</FormLabel>
<FormLabel className='sr-only'>{label ?? t.general.notes}</FormLabel>
<Textarea
placeholder={t.shipping.notesPlaceholder}
placeholder={placeholder ?? t.shipping.notesPlaceholder}
{...field}
hasError={!!fieldState.error}
aria-required={requiredFields?.notes}
Expand Down