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
91 changes: 0 additions & 91 deletions examples/astro-forms-demo/src/components/ContactsPhone.tsx

This file was deleted.

24 changes: 24 additions & 0 deletions examples/astro-forms-demo/src/components/Phone.tsx
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how the component will be used on vibe

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { PhoneField } from '@wix/headless-forms/react';

const Phone = ({ id }: { id: string }) => {
return (
<PhoneField id={id}>
<PhoneField.Label className="text-foreground font-paragraph mb-2">
<PhoneField.Label.Required asChild className="text-destructive ml-1" />
</PhoneField.Label>
<div className="flex gap-2">
<PhoneField.CountryCode
asChild
className="px-4 py-2 bg-background text-foreground border border-foreground/20 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary disabled:opacity-50 disabled:cursor-not-allowed"
/>
<PhoneField.Input
asChild
className="flex-1 px-4 py-2 bg-background text-foreground border border-foreground/20 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary disabled:opacity-50 disabled:cursor-not-allowed"
/>
</div>
<PhoneField.Error className="text-destructive text-sm font-paragraph" />
</PhoneField>
);
};

export default Phone;
4 changes: 2 additions & 2 deletions examples/astro-forms-demo/src/react-pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {

import TextInput from '../components/TextInput';
import TextArea from '../components/TextArea';
import ContactsPhone from '../components/ContactsPhone';
import Phone from '../components/Phone';
import MultilineAddress from '../components/MultilineAddress';
import DateInput from '../components/DateInput';
import DatePicker from '../components/DatePicker';
Expand Down Expand Up @@ -38,7 +38,7 @@ interface FormsPageProps {
const FIELD_MAP = {
TEXT_INPUT: TextInput,
TEXT_AREA: TextArea,
PHONE_INPUT: ContactsPhone,
PHONE_INPUT: Phone,
MULTILINE_ADDRESS: MultilineAddress,
DATE_INPUT: DateInput,
DATE_PICKER: DatePicker,
Expand Down
149 changes: 120 additions & 29 deletions packages/headless-components/forms/src/react/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,7 @@ export const Fields = React.forwardRef<HTMLDivElement, FieldsProps>(

return (
<div ref={ref}>
<FormProvider>
<FormProvider currency={'USD' as any} locale={'en'}>
<FieldsWithForm
form={form}
values={formValues}
Expand Down Expand Up @@ -930,31 +930,33 @@ const FieldsWithForm = ({
// TODO: use readOnly, isDisabled
// TODO: step title a11y support
// TODO: mobile support?
<FieldLayoutProvider value={fieldsLayout}>
<form onSubmit={(e) => e.preventDefault()}>
<fieldset
style={{ display: 'flex', flexDirection: 'column' }}
className={rowGapClassname}
>
{fieldElements.map((rowElements, index) => {
return (
<div
key={index}
style={{
display: 'grid',
width: '100%',
gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
gridAutoRows: 'minmax(min-content, max-content)',
}}
className={columnGapClassname}
>
{rowElements}
</div>
);
})}
</fieldset>
</form>
</FieldLayoutProvider>
// <FieldsPropsProvider value={undefined}>
<FieldLayoutProvider value={fieldsLayout}>
<form onSubmit={(e) => e.preventDefault()}>
<fieldset
style={{ display: 'flex', flexDirection: 'column' }}
className={rowGapClassname}
>
{fieldElements.map((rowElements, index) => {
return (
<div
key={index}
style={{
display: 'grid',
width: '100%',
gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
gridAutoRows: 'minmax(min-content, max-content)',
}}
className={columnGapClassname}
>
{rowElements}
</div>
);
})}
</fieldset>
</form>
</FieldLayoutProvider>
// </FieldsPropsProvider>
);
};

Expand Down Expand Up @@ -997,6 +999,20 @@ export interface FieldLabelProps {
className?: string;
}

/**
* Props for Field.Label.Required component
*/
export interface FieldLabelRequiredProps {
/** Whether to show the required indicator */
required?: boolean;
/** Custom content to display (defaults to red asterisk) */
children?: React.ReactNode;
/** Whether to render as a child component */
asChild?: boolean;
/** CSS classes to apply to the required indicator */
className?: string;
}

/**
* Props for Field.InputWrapper component
*/
Expand Down Expand Up @@ -1130,7 +1146,10 @@ FieldRoot.displayName = 'Form.Field';
*
* <Form.Field id="email">
* <Form.Field.Label>
* <label className="text-foreground font-paragraph">Email Address</label>
* <label className="text-foreground font-paragraph">
* Email Address
* <Form.Field.Label.Required required={true} />
* </label>
* </Form.Field.Label>
* <Form.Field.InputWrapper>
* <Form.Field.Input>
Expand All @@ -1140,7 +1159,7 @@ FieldRoot.displayName = 'Form.Field';
* </Form.Field>
* ```
*/
export const FieldLabel = React.forwardRef<HTMLDivElement, FieldLabelProps>(
const FieldLabelRoot = React.forwardRef<HTMLDivElement, FieldLabelProps>(
(props, ref) => {
const { children, asChild, className, ...otherProps } = props;
const { gridStyles } = useFieldContext();
Expand All @@ -1162,7 +1181,79 @@ export const FieldLabel = React.forwardRef<HTMLDivElement, FieldLabelProps>(
},
);

FieldLabel.displayName = 'Form.Field.Label';
FieldLabelRoot.displayName = 'Form.Field.Label';

/**
* Required indicator component for form field labels.
* Must be used within a Form.Field.Label component.
*
* @component
* @example
* ```tsx
* import { Form } from '@wix/headless-forms/react';
*
* // Basic usage with required prop
* <Form.Field.Label>
* <label className="text-foreground font-paragraph">
* Email Address
* <Form.Field.Label.Required />
* </label>
* </Form.Field.Label>
*
* // Custom styling
* <Form.Field.Label>
* <label className="text-foreground font-paragraph">
* Username
* <Form.Field.Label.Required required={true} className="text-destructive ml-2" />
* </label>
* </Form.Field.Label>
*/
export const FieldLabelRequired = React.forwardRef<
HTMLSpanElement,
FieldLabelRequiredProps
>((props, ref) => {
const {
required = false,
children,
asChild,
className,
...otherProps
} = props;
const requiredIndicator: 'asterisk' | 'text' | 'none' = 'asterisk';

// @ts-expect-error
if (!required || requiredIndicator === 'none') return null;

return (
<AsChildSlot
ref={ref}
asChild={asChild}
className={className}
customElement={children}
{...otherProps}
>
<span>
{requiredIndicator === 'asterisk'
? '*'
: requiredIndicator === 'text'
? '(Required)'
: null}
</span>
</AsChildSlot>
);
});

FieldLabelRequired.displayName = 'Form.Field.Label.Required';

interface FieldLabelComponent
extends React.ForwardRefExoticComponent<
FieldLabelProps & React.RefAttributes<HTMLDivElement>
> {
Required: typeof FieldLabelRequired;
}

export const FieldLabel = FieldLabelRoot as FieldLabelComponent;
FieldLabel.Required = FieldLabelRequired;

/**
* InputWrapper component that wraps input and error elements with grid positioning.
Expand Down
Loading
Loading