From 70fdb0fcc273da503686353cc55e3c5a385880d0 Mon Sep 17 00:00:00 2001 From: MillanWangGadget Date: Fri, 18 Oct 2024 18:24:46 -0400 Subject: [PATCH] Added descriptions for AutoComponents --- packages/react/.changeset/fuzzy-nails-do.md | 5 + .../interfaces/AutoRelationshipInputProps.tsx | 22 +- .../inputs/LazyLoadedMUIAutoRichTextInput.tsx | 13 + .../auto/mui/inputs/MUIAutoBooleanInput.tsx | 53 ++-- .../auto/mui/inputs/MUIAutoDateTimePicker.tsx | 61 +++-- .../inputs/MUIAutoEncryptedStringInput.tsx | 35 +-- .../src/auto/mui/inputs/MUIAutoEnumInput.tsx | 15 +- .../src/auto/mui/inputs/MUIAutoFileInput.tsx | 23 +- .../auto/mui/inputs/MUIAutoFormControl.tsx | 10 +- .../auto/mui/inputs/MUIAutoHiddenInput.tsx | 26 +- .../src/auto/mui/inputs/MUIAutoIdInput.tsx | 55 ++-- .../src/auto/mui/inputs/MUIAutoInput.tsx | 17 +- .../src/auto/mui/inputs/MUIAutoJSONInput.tsx | 65 ++--- .../auto/mui/inputs/MUIAutoPasswordInput.tsx | 78 +++--- .../src/auto/mui/inputs/MUIAutoRolesInput.tsx | 90 +++---- .../src/auto/mui/inputs/MUIAutoTextInput.tsx | 97 +++++-- .../relationships/MUIAutoBelongsToInput.tsx | 16 ++ .../relationships/MUIAutoHasManyInput.tsx | 16 ++ .../relationships/MUIAutoHasOneInput.tsx | 16 ++ .../auto/mui/submit/MUISubmitResultBanner.tsx | 10 +- packages/react/src/auto/mui/test-index.ts | 8 +- .../src/auto/polaris/PolarisAutoForm.tsx | 10 + .../src/auto/polaris/PolarisAutoTable.tsx | 5 + .../polaris/PolarisFixedOptionsCombobox.tsx | 32 ++- packages/react/src/auto/polaris/index.ts | 6 +- .../LazyLoadedPolarisAutoRichTextInput.tsx | 13 + .../inputs/PolarisAutoBooleanInput.tsx | 18 +- .../inputs/PolarisAutoDateTimePicker.tsx | 222 ++++++++-------- .../PolarisAutoEncryptedStringInput.tsx | 53 ++-- .../polaris/inputs/PolarisAutoEnumInput.tsx | 238 +++++++++--------- .../polaris/inputs/PolarisAutoFileInput.tsx | 19 +- .../polaris/inputs/PolarisAutoHiddenInput.tsx | 26 +- .../polaris/inputs/PolarisAutoIdInput.tsx | 34 ++- .../auto/polaris/inputs/PolarisAutoInput.tsx | 17 +- .../polaris/inputs/PolarisAutoJSONInput.tsx | 63 ++--- .../polaris/inputs/PolarisAutoNumberInput.tsx | 53 ++-- .../inputs/PolarisAutoPasswordInput.tsx | 77 +++--- .../polaris/inputs/PolarisAutoRolesInput.tsx | 65 ++--- .../polaris/inputs/PolarisAutoTextInput.tsx | 93 +++++-- .../polaris/inputs/PolarisAutoTimePicker.tsx | 32 ++- .../PolarisAutoBelongsToInput.tsx | 16 ++ .../relationships/PolarisAutoHasManyInput.tsx | 16 ++ .../relationships/PolarisAutoHasOneInput.tsx | 16 ++ .../submit/PolarisSubmitResultBanner.tsx | 10 +- .../react/src/auto/shared/AutoInputTypes.tsx | 169 +++++++++++++ .../auto/shared/AutoRichTextInputProps.tsx | 17 +- 46 files changed, 1409 insertions(+), 642 deletions(-) create mode 100644 packages/react/.changeset/fuzzy-nails-do.md create mode 100644 packages/react/src/auto/shared/AutoInputTypes.tsx diff --git a/packages/react/.changeset/fuzzy-nails-do.md b/packages/react/.changeset/fuzzy-nails-do.md new file mode 100644 index 000000000..772970a2c --- /dev/null +++ b/packages/react/.changeset/fuzzy-nails-do.md @@ -0,0 +1,5 @@ +--- +"@gadgetinc/react": patch +--- + +Added descriptions for AutoComponents diff --git a/packages/react/src/auto/interfaces/AutoRelationshipInputProps.tsx b/packages/react/src/auto/interfaces/AutoRelationshipInputProps.tsx index 0cf591844..d120c0f12 100644 --- a/packages/react/src/auto/interfaces/AutoRelationshipInputProps.tsx +++ b/packages/react/src/auto/interfaces/AutoRelationshipInputProps.tsx @@ -1,14 +1,28 @@ import type { ReactNode } from "react"; -import type { Control } from "../../useActionForm.js"; +import { ControllableWithReactHookForm, InputLabel } from "../shared/AutoInputTypes.js"; -export interface AutoRelationshipInputProps { +export interface AutoRelationshipInputProps extends ControllableWithReactHookForm { + /** + * The API identifier of the field + */ field: string; - control?: Control; + + /** + * Controls how records on the related model are displayed as options in the relationship field input component + * When using a string, the string will indicate the field on the related model record to be displayed as the option label + * When using a function, the function will be called with the record to return a ReactNode to be displayed as the option label + */ optionLabel?: OptionLabel; - label?: string; + + /** + * The label of the field input component + */ + label?: InputLabel; } /** * Type for the option label when displaying a list of records from a related model + * When using a string, the string will indicate the field on the related model record to be displayed as the option label + * When using a function, the function will be called with the record to return a ReactNode to be displayed as the option label */ export type OptionLabel = string | ((record: Record) => ReactNode); diff --git a/packages/react/src/auto/mui/inputs/LazyLoadedMUIAutoRichTextInput.tsx b/packages/react/src/auto/mui/inputs/LazyLoadedMUIAutoRichTextInput.tsx index 7535c2d13..7c45540ca 100644 --- a/packages/react/src/auto/mui/inputs/LazyLoadedMUIAutoRichTextInput.tsx +++ b/packages/react/src/auto/mui/inputs/LazyLoadedMUIAutoRichTextInput.tsx @@ -5,6 +5,19 @@ import { autoInput } from "../../AutoInput.js"; // lazy import for smaller bundle size by default const LazyLoadedMUIAutoRichTextInput = React.lazy(() => import("./MUIAutoRichTextInput.js")); +/** + * A rich text input component for use within components. + * Requires `"@mdxeditor/editor"` as a peer dependency to be rendered + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The RichText field API identifier + * @param props.label - The label of the RichText input component + * @returns The rich text input component + */ export const MUIAutoRichTextInput = autoInput((props: AutoRichTextInputProps) => { return ( <> diff --git a/packages/react/src/auto/mui/inputs/MUIAutoBooleanInput.tsx b/packages/react/src/auto/mui/inputs/MUIAutoBooleanInput.tsx index 91870d682..5f75b9630 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoBooleanInput.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoBooleanInput.tsx @@ -1,28 +1,47 @@ import type { CheckboxProps } from "@mui/material"; import { Checkbox } from "@mui/material"; import React from "react"; -import { useController, type Control } from "../../../useActionForm.js"; +import { useController } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; import { useFieldMetadata } from "../../hooks/useFieldMetadata.js"; +import { AutoBooleanInputProps, InputLabel } from "../../shared/AutoInputTypes.js"; import { MUIAutoFormControl } from "./MUIAutoFormControl.js"; -export const MUIAutoBooleanInput = autoInput( - (props: { field: string; control?: Control; label?: string } & Partial) => { - const { field: fieldApiIdentifier, label, control, ...rest } = props; +export interface MUIAutoBooleanInputProps extends AutoBooleanInputProps, Partial { + /** + * The label of the checkbox + */ + label?: InputLabel; +} - const { path } = useFieldMetadata(fieldApiIdentifier); +/** + * A checkbox input for boolean inputs for use within components + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The API identifier of the Boolean field + * @param props.label - The label of the checkbox + * + * @returns The checkbox input component + */ +export const MUIAutoBooleanInput = autoInput((props: MUIAutoBooleanInputProps) => { + const { field: fieldApiIdentifier, label, control, ...rest } = props; - const { field: fieldProps } = useController({ - control, - name: path, - }); + const { path } = useFieldMetadata(fieldApiIdentifier); - const { value: _value, ...restFieldProps } = fieldProps; + const { field: fieldProps } = useController({ + control, + name: path, + }); - return ( - - - - ); - } -); + const { value: _value, ...restFieldProps } = fieldProps; + + return ( + + + + ); +}); diff --git a/packages/react/src/auto/mui/inputs/MUIAutoDateTimePicker.tsx b/packages/react/src/auto/mui/inputs/MUIAutoDateTimePicker.tsx index 2259823bd..04fb2356e 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoDateTimePicker.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoDateTimePicker.tsx @@ -6,38 +6,49 @@ import type { GadgetDateTimeConfig } from "../../../internal/gql/graphql.js"; import { useController } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; import { useFieldMetadata } from "../../hooks/useFieldMetadata.js"; +import { AutoDateTimeInputProps } from "../../shared/AutoInputTypes.js"; -export const MUIAutoDateTimePicker = autoInput( - (props: { field: string; value?: Date; onChange?: (value: Date) => void; error?: string; label?: string }) => { - const localTz = Intl.DateTimeFormat().resolvedOptions().timeZone; - const { path, metadata } = useFieldMetadata(props.field); - const config = metadata.configuration; - const isRequired = metadata.requiredArgumentForInput; - const label = (props.label ?? metadata.name) + (isRequired ? " *" : ""); +/** + * A date and time picker for use within components + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The API identifier of the DateTime field + * @param props.label - The label of the date and time picker + * @returns The date and time picker component + */ +export const MUIAutoDateTimePicker = autoInput((props: AutoDateTimeInputProps) => { + const localTz = Intl.DateTimeFormat().resolvedOptions().timeZone; + const { path, metadata } = useFieldMetadata(props.field); + const config = metadata.configuration; + const isRequired = metadata.requiredArgumentForInput; + const label = (props.label ?? metadata.name) + (isRequired ? " *" : ""); - const { field: fieldProps, fieldState } = useController({ name: path }); + const { field: fieldProps, fieldState } = useController({ name: path }); - return ( - - + { + props.onChange?.(zonedTimeToUtc(new Date(newValue ?? ""), localTz)); + fieldProps.onChange(zonedTimeToUtc(new Date(newValue ?? ""), localTz)); + }} + /> + {(config as GadgetDateTimeConfig).includeTime && ( + { props.onChange?.(zonedTimeToUtc(new Date(newValue ?? ""), localTz)); fieldProps.onChange(zonedTimeToUtc(new Date(newValue ?? ""), localTz)); }} /> - {(config as GadgetDateTimeConfig).includeTime && ( - { - props.onChange?.(zonedTimeToUtc(new Date(newValue ?? ""), localTz)); - fieldProps.onChange(zonedTimeToUtc(new Date(newValue ?? ""), localTz)); - }} - /> - )} - - ); - } -); + )} + + ); +}); export default MUIAutoDateTimePicker; diff --git a/packages/react/src/auto/mui/inputs/MUIAutoEncryptedStringInput.tsx b/packages/react/src/auto/mui/inputs/MUIAutoEncryptedStringInput.tsx index b5133d82e..070c298b6 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoEncryptedStringInput.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoEncryptedStringInput.tsx @@ -1,20 +1,25 @@ -import type { TextFieldProps } from "@mui/material"; -import { IconButton } from "@mui/material"; +import { IconButton, type TextFieldProps } from "@mui/material"; import React, { useState } from "react"; -import type { Control } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; +import type { AutoEncryptedStringInputProps } from "../../shared/AutoInputTypes.js"; import { MUIAutoTextInput } from "./MUIAutoTextInput.js"; -export const MUIAutoEncryptedStringInput = autoInput( - ( - props: { - field: string; // The field API identifier - control?: Control; - } & Partial - ) => { - const [isShown, setIsShown] = useState(false); - const showHideToggleButton = setIsShown(!isShown)}>{isShown ? `🔒` : `👁️`}; +type MUIAutoEncryptedStringInputProps = AutoEncryptedStringInputProps & Partial; +/** + * An encrypted string input for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The API identifier for the EncryptedString field. + * @param props.label - The label of the EncryptedString field. + * @returns The AutoEncryptedStringInput component. + */ +export const MUIAutoEncryptedStringInput = autoInput((props: MUIAutoEncryptedStringInputProps) => { + const [isShown, setIsShown] = useState(false); + const showHideToggleButton = setIsShown(!isShown)}>{isShown ? `🔒` : `👁️`}; - return ; - } -); + return ; +}); diff --git a/packages/react/src/auto/mui/inputs/MUIAutoEnumInput.tsx b/packages/react/src/auto/mui/inputs/MUIAutoEnumInput.tsx index 89654df9d..0a44553b8 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoEnumInput.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoEnumInput.tsx @@ -3,7 +3,20 @@ import { Autocomplete, TextField } from "@mui/material"; import React from "react"; import { autoInput } from "../../AutoInput.js"; import { useEnumInputController } from "../../hooks/useEnumInputController.js"; +import { AutoEnumInputProps } from "../../shared/AutoInputTypes.js"; +/** + * An enum option picker for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The API identifier for the Enum field. + * @param props.label - The label of the input. + * @returns The AutoEnumInput component. + */ export const MUIAutoEnumInput = autoInput( < Value, @@ -12,7 +25,7 @@ export const MUIAutoEnumInput = autoInput( FreeSolo extends boolean | undefined = false, ChipComponent extends React.ElementType = ChipTypeMap["defaultComponent"] >( - props: { field: string; label?: string } & Partial> + props: AutoEnumInputProps & Partial> ) => { const { allowMultiple, selectedOptions, onSelectionChange, allOptions, label } = useEnumInputController(props); diff --git a/packages/react/src/auto/mui/inputs/MUIAutoFileInput.tsx b/packages/react/src/auto/mui/inputs/MUIAutoFileInput.tsx index 6732afe99..b4c541540 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoFileInput.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoFileInput.tsx @@ -1,14 +1,14 @@ import { Button, styled } from "@mui/material"; import React from "react"; -import type { Control } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; import { useFileInputController } from "../../hooks/useFileInputController.js"; +import { AutoFileInputProps, InputLabel } from "../../shared/AutoInputTypes.js"; import { MUIAutoFormControl } from "./MUIAutoFormControl.js"; export interface MUIFileInputProps { - label?: string; + label?: InputLabel; value?: File; - onChange: (value: File) => void; + onChange?: (value: File) => void; } const VisuallyHiddenInput = styled("input")({ clip: "rect(0 0 0 0)", @@ -22,7 +22,22 @@ const VisuallyHiddenInput = styled("input")({ width: 1, }); -export const MUIAutoFileInput = autoInput((props: { field: string; control?: Control; label?: string }) => { +export type MUIAutoFileInputProps = AutoFileInputProps & MUIFileInputProps; + +/** + * A file input for use within components + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The API identifier of the File field + * @param props.label - The label of the File field + * @param props.onChange - called when the file input changes + * @returns The file input component + */ +export const MUIAutoFileInput = autoInput((props: MUIAutoFileInputProps) => { const { field: fieldApiIdentifier, control, label } = props; const { onFileUpload, metadata } = useFileInputController({ field: fieldApiIdentifier, diff --git a/packages/react/src/auto/mui/inputs/MUIAutoFormControl.tsx b/packages/react/src/auto/mui/inputs/MUIAutoFormControl.tsx index 24f9a1d5a..c66633ddf 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoFormControl.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoFormControl.tsx @@ -4,8 +4,16 @@ import React from "react"; import { useController } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; import { useFieldMetadata } from "../../hooks/useFieldMetadata.js"; +import { InputLabel } from "../../shared/AutoInputTypes.js"; -export const MUIAutoFormControl = autoInput((props: { field: string; children: ReactElement; label?: string }) => { +/** + * A form control wrapper used to wrap MUI form input components. + * @param props.field - The field API identifier + * @param props.label - The label of the form control input component + * @param props.children - The child elements to be rendered within the form control + * @returns The form control component + */ +export const MUIAutoFormControl = autoInput((props: { field: string; children: ReactElement; label?: InputLabel }) => { const { path, metadata } = useFieldMetadata(props.field); const { fieldState: { error }, diff --git a/packages/react/src/auto/mui/inputs/MUIAutoHiddenInput.tsx b/packages/react/src/auto/mui/inputs/MUIAutoHiddenInput.tsx index 6a0d78b2f..3d2a7a536 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoHiddenInput.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoHiddenInput.tsx @@ -1,14 +1,22 @@ import React from "react"; import { autoInput } from "../../AutoInput.js"; import { useHiddenInput } from "../../hooks/useHiddenInput.js"; +import { AutoHiddenInputProps } from "../../shared/AutoInputTypes.js"; -export const MUIAutoHiddenInput = autoInput( - (props: { - field: string; // The field API identifier - value: any; - }) => { - const fieldProps = useHiddenInput(props); +/** + * A hidden input for use within components. The value is included in form submission without rendering a visible input. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The field API identifier + * @param props.value - The value to be included in form submission + * @returns The hidden input component + */ +export const MUIAutoHiddenInput = autoInput((props: AutoHiddenInputProps) => { + const fieldProps = useHiddenInput(props); - return ; - } -); + return ; +}); diff --git a/packages/react/src/auto/mui/inputs/MUIAutoIdInput.tsx b/packages/react/src/auto/mui/inputs/MUIAutoIdInput.tsx index 86ea9db79..d1b71dc05 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoIdInput.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoIdInput.tsx @@ -3,31 +3,38 @@ import React from "react"; import { FieldType } from "../../../metadata.js"; import { autoInput } from "../../AutoInput.js"; import { useStringInputController } from "../../hooks/useStringInputController.js"; +import { AutoIdInputProps } from "../../shared/AutoInputTypes.js"; import { MUIAutoTextInput } from "./MUIAutoTextInput.js"; -export const MUIAutoIdInput = autoInput( - ( - props: { - field: string; - } & Partial - ) => { - const { field } = props; - const { name, metadata } = useStringInputController({ field }); +export type MUIAutoIdInputProps = AutoIdInputProps & Partial; +/** + * An id input component for use within components + * @example + * ```tsx + * + * + * + * ``` + * @param props.field The API identifier of the Id field + * @returns The id input component + */ +export const MUIAutoIdInput = autoInput((props: MUIAutoIdInputProps) => { + const { field } = props; + const { name, metadata } = useStringInputController({ field }); - if (metadata.fieldType !== FieldType.Id || field !== "id") { - throw new Error(`PolarisAutoIdInput: field ${field} is not of type Id`); - } - - return ( - - ); + if (metadata.fieldType !== FieldType.Id || field !== "id") { + throw new Error(`PolarisAutoIdInput: field ${field} is not of type Id`); } -); + + return ( + + ); +}); diff --git a/packages/react/src/auto/mui/inputs/MUIAutoInput.tsx b/packages/react/src/auto/mui/inputs/MUIAutoInput.tsx index 15061e83b..1334e8e34 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoInput.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoInput.tsx @@ -2,6 +2,7 @@ import React from "react"; import { FieldType } from "../../../metadata.js"; import { autoInput } from "../../AutoInput.js"; import { useFieldMetadata } from "../../hooks/useFieldMetadata.js"; +import { AutoInputProps } from "../../shared/AutoInputTypes.js"; import { MUIAutoRichTextInput } from "./LazyLoadedMUIAutoRichTextInput.js"; import { MUIAutoBooleanInput } from "./MUIAutoBooleanInput.js"; import MUIAutoDateTimePicker from "./MUIAutoDateTimePicker.js"; @@ -16,7 +17,21 @@ import { MUIAutoTextInput } from "./MUIAutoTextInput.js"; import { MUIAutoBelongsToInput } from "./relationships/MUIAutoBelongsToInput.js"; import { MUIAutoHasManyInput } from "./relationships/MUIAutoHasManyInput.js"; -export const MUIAutoInput = autoInput((props: { field: string; label?: string }) => { +/** + * An automatically generated input component based on the given field's type for use within components + * @example + * ```tsx + * + * + * + * + * + * ``` + * @param props.field The API identifier of the field + * @param props.label The label of the field + * @returns The input component + */ +export const MUIAutoInput = autoInput((props: AutoInputProps) => { const { metadata } = useFieldMetadata(props.field); const config = metadata.configuration; diff --git a/packages/react/src/auto/mui/inputs/MUIAutoJSONInput.tsx b/packages/react/src/auto/mui/inputs/MUIAutoJSONInput.tsx index 0498f8ff6..d72309895 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoJSONInput.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoJSONInput.tsx @@ -1,37 +1,44 @@ import type { TextFieldProps } from "@mui/material"; import { TextField } from "@mui/material"; import React from "react"; -import type { Control } from "../../../useActionForm.js"; import { useFocus } from "../../../useFocus.js"; import { autoInput } from "../../AutoInput.js"; import { useJSONInputController } from "../../hooks/useJSONInputController.js"; +import { AutoJSONInputProps } from "../../shared/AutoInputTypes.js"; -export const MUIAutoJSONInput = autoInput( - ( - props: { - field: string; // The field API identifier - control?: Control; - } & Partial> - ) => { - const [isFocused, focusProps] = useFocus(); - const { field: _field, control: _control, ...restOfProps } = props; - const { type: _type, errorMessage, ...controller } = useJSONInputController(props); +type MUIAutoJSONInputProps = AutoJSONInputProps & Partial>; - const inErrorState = !isFocused && !!errorMessage; - const label = props.label ?? controller.label; - return ( - controller.onChange(event.target.value)} - /> - ); - } -); +/** + * JSON field input component for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The JSON field API identifier + * @param props.label - The label of the JSON input component + * @returns The JSON input component + */ +export const MUIAutoJSONInput = autoInput((props: MUIAutoJSONInputProps) => { + const [isFocused, focusProps] = useFocus(); + const { field: _field, control: _control, ...restOfProps } = props; + const { type: _type, errorMessage, ...controller } = useJSONInputController(props); + + const inErrorState = !isFocused && !!errorMessage; + const label = props.label ?? controller.label; + return ( + controller.onChange(event.target.value)} + /> + ); +}); diff --git a/packages/react/src/auto/mui/inputs/MUIAutoPasswordInput.tsx b/packages/react/src/auto/mui/inputs/MUIAutoPasswordInput.tsx index 32bed9e26..c2f58ff82 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoPasswordInput.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoPasswordInput.tsx @@ -1,10 +1,11 @@ import type { TextFieldProps } from "@mui/material"; import { IconButton } from "@mui/material"; import React, { useState } from "react"; -import { useController, type Control } from "../../../useActionForm.js"; +import { useController } from "../../../useActionForm.js"; import { useAutoFormMetadata } from "../../AutoFormContext.js"; import { autoInput } from "../../AutoInput.js"; import { useFieldMetadata } from "../../hooks/useFieldMetadata.js"; +import { AutoPasswordInputProps } from "../../shared/AutoInputTypes.js"; import { MUIAutoEncryptedStringInput } from "./MUIAutoEncryptedStringInput.js"; /** @@ -14,41 +15,48 @@ import { MUIAutoEncryptedStringInput } from "./MUIAutoEncryptedStringInput.js"; const existingPasswordPlaceholder = "********"; const pencilEmoji = `✏️`; -export const MUIAutoPasswordInput = autoInput( - ( - props: { - field: string; // The field API identifier - control?: Control; - } & Partial - ) => { - const { findBy } = useAutoFormMetadata(); - const { path } = useFieldMetadata(props.field); - const { field: fieldProps } = useController({ name: path }); +type MUIAutoPasswordInputProps = AutoPasswordInputProps & Partial; - const [isEditing, setIsEditing] = useState(!findBy); +/** + * A password input component for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The password field API identifier + * @param props.label - The label of the password input component + * @returns The password input component + */ +export const MUIAutoPasswordInput = autoInput((props: MUIAutoPasswordInputProps) => { + const { findBy } = useAutoFormMetadata(); + const { path } = useFieldMetadata(props.field); + const { field: fieldProps } = useController({ name: path }); + + const [isEditing, setIsEditing] = useState(!findBy); - const startEditing = () => { - fieldProps.onChange(""); // Touch the field to mark it as dirty - setIsEditing(true); - }; + const startEditing = () => { + fieldProps.onChange(""); // Touch the field to mark it as dirty + setIsEditing(true); + }; - const startEditingButton = ( - - {pencilEmoji} - - ); + const startEditingButton = ( + + {pencilEmoji} + + ); - return ( - - ); - } -); + return ( + + ); +}); diff --git a/packages/react/src/auto/mui/inputs/MUIAutoRolesInput.tsx b/packages/react/src/auto/mui/inputs/MUIAutoRolesInput.tsx index 58be27123..dd93981d5 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoRolesInput.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoRolesInput.tsx @@ -1,54 +1,60 @@ import type { AutocompleteProps } from "@mui/material"; import { Autocomplete, TextField } from "@mui/material"; import React from "react"; -import type { Control } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; import { useRoleInputController } from "../../hooks/useRoleInputController.js"; +import { AutoRolesInputProps } from "../../shared/AutoInputTypes.js"; -export const MUIAutoRolesInput = autoInput( - ( - props: { - field: string; // Field API identifier - control?: Control; - label?: string; - } & Partial> - ) => { - const { options, loading, rolesError, fieldError, selectedRoleKeys, fieldProps, metadata } = useRoleInputController(props); +type MUIAutoRolesInputProps = AutoRolesInputProps & Partial>; - if (rolesError) { - throw rolesError; - } - if (fieldError) { - throw fieldError; - } +/** + * A role list input component for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The role list field API identifier + * @param props.label - The label of the role list field + * @returns The Polaris Auto Roles Input component + */ +export const MUIAutoRolesInput = autoInput((props: MUIAutoRolesInputProps) => { + const { options, loading, rolesError, fieldError, selectedRoleKeys, fieldProps, metadata } = useRoleInputController(props); - const label = props.label ?? metadata.name; - if (loading) { - return ; - } + if (rolesError) { + throw rolesError; + } + if (fieldError) { + throw fieldError; + } - return ( - } - renderOption={(optionAttributes, option) => ( -
  • - {option.label} -
  • - )} - {...fieldProps} - onChange={(_event, value) => { - const uniqueSelectedRoleKeys = new Set(value.map((option) => (typeof option === "string" ? option : option.id))); - fieldProps.onChange(Array.from(uniqueSelectedRoleKeys)); - }} - value={options.filter((option) => selectedRoleKeys.includes(option.value)).map(idLabelMapper)} - options={options.map(idLabelMapper)} - {...props} - /> - ); + const label = props.label ?? metadata.name; + if (loading) { + return ; } -); + + return ( + } + renderOption={(optionAttributes, option) => ( +
  • + {option.label} +
  • + )} + {...fieldProps} + onChange={(_event, value) => { + const uniqueSelectedRoleKeys = new Set(value.map((option) => (typeof option === "string" ? option : option.id))); + fieldProps.onChange(Array.from(uniqueSelectedRoleKeys)); + }} + value={options.filter((option) => selectedRoleKeys.includes(option.value)).map(idLabelMapper)} + options={options.map(idLabelMapper)} + {...props} + /> + ); +}); const idLabelMapper = (option: { value: string; label: string }) => ({ id: option.value, label: option.label }); diff --git a/packages/react/src/auto/mui/inputs/MUIAutoTextInput.tsx b/packages/react/src/auto/mui/inputs/MUIAutoTextInput.tsx index b80e90377..3d80c5128 100644 --- a/packages/react/src/auto/mui/inputs/MUIAutoTextInput.tsx +++ b/packages/react/src/auto/mui/inputs/MUIAutoTextInput.tsx @@ -1,30 +1,81 @@ import type { TextFieldProps } from "@mui/material"; import { TextField } from "@mui/material"; import React from "react"; -import type { Control } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; import { useStringInputController } from "../../hooks/useStringInputController.js"; +import { AutoTextInputProps } from "../../shared/AutoInputTypes.js"; -export const MUIAutoTextInput = autoInput( - ( - props: { - field: string; // The field API identifier - control?: Control; - } & Partial - ) => { - const { field, control } = props; - const stringInputController = useStringInputController({ field, control }); +type MUIAutoTextInputProps = AutoTextInputProps & Partial; - const isRequired = stringInputController.metadata.requiredArgumentForInput; - const label = (props.label ?? stringInputController.label) + (isRequired ? " *" : ""); - return ( - - ); - } -); +export const MUIAutoTextInput = autoInput((props: MUIAutoTextInputProps) => { + const { field, control } = props; + const stringInputController = useStringInputController({ field, control }); + + const isRequired = stringInputController.metadata.requiredArgumentForInput; + const label = (props.label ?? stringInputController.label) + (isRequired ? " *" : ""); + return ( + + ); +}); + +/** + * An input component for email fields for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The email field API identifier + * @param props.label - The label of the field + * @returns The Input component + */ +export const MUIAutoEmailInput = (props: MUIAutoTextInputProps) => ; + +/** + * A number input component for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The number field API identifier + * @param props.label - The label of the number input component + * @returns The number input component + */ +export const MUIAutoNumberInput = (props: MUIAutoTextInputProps) => ; + +/** + * A input component for string fields for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The string field API identifier + * @param props.label - The label of the string field + * @returns The Input component + */ +export const MUIAutoStringInput = (props: MUIAutoTextInputProps) => ; + +/** + * A input component for URL fields for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The url field API identifier + * @param props.label - The label of the url field + * @returns The Input component + */ +export const MUIAutoUrlInput = (props: MUIAutoTextInputProps) => ; diff --git a/packages/react/src/auto/mui/inputs/relationships/MUIAutoBelongsToInput.tsx b/packages/react/src/auto/mui/inputs/relationships/MUIAutoBelongsToInput.tsx index b039e9cde..7716e496f 100644 --- a/packages/react/src/auto/mui/inputs/relationships/MUIAutoBelongsToInput.tsx +++ b/packages/react/src/auto/mui/inputs/relationships/MUIAutoBelongsToInput.tsx @@ -4,6 +4,22 @@ import { autoInput } from "../../../AutoInput.js"; import { useBelongsToInputController } from "../../../hooks/useBelongsToInputController.js"; import type { AutoRelationshipInputProps } from "../../../interfaces/AutoRelationshipInputProps.js"; +/** + * A belongsTo field input component for use within components + * This component is used to configure relationships with records from a related model + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The belongsTo field API identifier + * @param props.label - The label of the belongTo field input component + * @param props.optionLabel - Controls how records on the related model are displayed as options in the relationship field input component. + * When using a string, the string will indicate the field on the related model record to be displayed as the option label. + * When using a function, the function will be called with the record to return a ReactNode to be displayed as the option label + * @returns The belongsTo field input component + */ export const MUIAutoBelongsToInput = autoInput((props: AutoRelationshipInputProps) => { const { fieldMetadata: { path, metadata }, diff --git a/packages/react/src/auto/mui/inputs/relationships/MUIAutoHasManyInput.tsx b/packages/react/src/auto/mui/inputs/relationships/MUIAutoHasManyInput.tsx index 96fed3344..2915296b1 100644 --- a/packages/react/src/auto/mui/inputs/relationships/MUIAutoHasManyInput.tsx +++ b/packages/react/src/auto/mui/inputs/relationships/MUIAutoHasManyInput.tsx @@ -4,6 +4,22 @@ import { autoInput } from "../../../AutoInput.js"; import { useHasManyInputController } from "../../../hooks/useHasManyInputController.js"; import type { AutoRelationshipInputProps } from "../../../interfaces/AutoRelationshipInputProps.js"; +/** + * A hasMany field input component for use within components + * This component is used to configure relationships with records from a related model + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The hasMany field API identifier + * @param props.label - The label of the hasMany field input component + * @param props.optionLabel - Controls how records on the related model are displayed as options in the relationship field input component. + * When using a string, the string will indicate the field on the related model record to be displayed as the option label. + * When using a function, the function will be called with the record to return a ReactNode to be displayed as the option label + * @returns The hasMany field input component + */ export const MUIAutoHasManyInput = autoInput((props: AutoRelationshipInputProps) => { const { fieldMetadata: { path, metadata }, diff --git a/packages/react/src/auto/mui/inputs/relationships/MUIAutoHasOneInput.tsx b/packages/react/src/auto/mui/inputs/relationships/MUIAutoHasOneInput.tsx index 206911ba3..f99da5635 100644 --- a/packages/react/src/auto/mui/inputs/relationships/MUIAutoHasOneInput.tsx +++ b/packages/react/src/auto/mui/inputs/relationships/MUIAutoHasOneInput.tsx @@ -9,6 +9,22 @@ import type { AutoRelationshipInputProps } from "../../../interfaces/AutoRelatio */ const showErrorBannerWhenTooManyRelatedRecords = false; +/** + * A hasOne field input component for use within components + * This component is used to configure relationships with records from a related model + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The hasOne field API identifier + * @param props.label - The label of the hasOne field input component + * @param props.optionLabel - Controls how records on the related model are displayed as options in the relationship field input component. + * When using a string, the string will indicate the field on the related model record to be displayed as the option label. + * When using a function, the function will be called with the record to return a ReactNode to be displayed as the option label + * @returns The hasOne field input component + */ export const MUIAutoHasOneInput = autoInput((props: AutoRelationshipInputProps) => { const { field } = props; const { diff --git a/packages/react/src/auto/mui/submit/MUISubmitResultBanner.tsx b/packages/react/src/auto/mui/submit/MUISubmitResultBanner.tsx index 4a60986b5..7baecd185 100644 --- a/packages/react/src/auto/mui/submit/MUISubmitResultBanner.tsx +++ b/packages/react/src/auto/mui/submit/MUISubmitResultBanner.tsx @@ -3,7 +3,15 @@ import { Alert, Button } from "@mui/material"; import React from "react"; import { useResultBannerController } from "../../hooks/useResultBannerController.js"; -export const MUISubmitResultBanner = (props: { successBannerProps?: AlertProps; errorBannerProps?: AlertProps }) => { +type MUISubmitResultBannerProps = { successBannerProps?: AlertProps; errorBannerProps?: AlertProps }; + +/** + * A banner that displays the result of an AutoForm submission. + * @param props.successBannerProps - The props for the successful banner + * @param props.errorBannerProps - The props for the error banner + * @returns The banner component + */ +export const MUISubmitResultBanner = (props: MUISubmitResultBannerProps) => { return ( <> diff --git a/packages/react/src/auto/mui/test-index.ts b/packages/react/src/auto/mui/test-index.ts index d80f3d8a6..77049dcee 100644 --- a/packages/react/src/auto/mui/test-index.ts +++ b/packages/react/src/auto/mui/test-index.ts @@ -12,10 +12,10 @@ export { MUIAutoInput as AutoInput } from "./inputs/MUIAutoInput.js"; export { MUIAutoJSONInput as AutoJSONInput } from "./inputs/MUIAutoJSONInput.js"; export { MUIAutoRolesInput as AutoRolesInput } from "./inputs/MUIAutoRolesInput.js"; export { - MUIAutoTextInput as AutoEmailInput, - MUIAutoTextInput as AutoNumberInput, - MUIAutoTextInput as AutoStringInput, - MUIAutoTextInput as AutoUrlInput, + MUIAutoEmailInput as AutoEmailInput, + MUIAutoNumberInput as AutoNumberInput, + MUIAutoStringInput as AutoStringInput, + MUIAutoUrlInput as AutoUrlInput, } from "./inputs/MUIAutoTextInput.js"; export { MUIAutoBelongsToInput as AutoBelongsToInput } from "./inputs/relationships/MUIAutoBelongsToInput.js"; export { MUIAutoHasManyInput as AutoHasManyInput } from "./inputs/relationships/MUIAutoHasManyInput.js"; diff --git a/packages/react/src/auto/polaris/PolarisAutoForm.tsx b/packages/react/src/auto/polaris/PolarisAutoForm.tsx index daeae5e73..3ce2400e4 100644 --- a/packages/react/src/auto/polaris/PolarisAutoForm.tsx +++ b/packages/react/src/auto/polaris/PolarisAutoForm.tsx @@ -12,6 +12,16 @@ import { PolarisAutoInput } from "./inputs/PolarisAutoInput.js"; import { PolarisAutoSubmit } from "./submit/PolarisAutoSubmit.js"; import { PolarisSubmitErrorBanner, PolarisSubmitSuccessfulBanner } from "./submit/PolarisSubmitResultBanner.js"; +/** + * A skeleton component for the form to display while the form is loading + * @example + * ```tsx + * + * {isLoading ? : } + * + * ``` + * @returns Skeleton component for the form + */ export const PolarisAutoFormSkeleton = () => ( <> diff --git a/packages/react/src/auto/polaris/PolarisAutoTable.tsx b/packages/react/src/auto/polaris/PolarisAutoTable.tsx index b812b0620..33717a2c5 100644 --- a/packages/react/src/auto/polaris/PolarisAutoTable.tsx +++ b/packages/react/src/auto/polaris/PolarisAutoTable.tsx @@ -54,6 +54,11 @@ const getColumnIndex = (columns: TableColumn[], apiIdentifier: string | undefine /** * Renders a table of records from the backend automatically for a given model using Polaris + * @example + * ```tsx + * + * ``` + * @param props.model - The Gadget model to show in the table */ export const PolarisAutoTable = < GivenOptions extends OptionsType, diff --git a/packages/react/src/auto/polaris/PolarisFixedOptionsCombobox.tsx b/packages/react/src/auto/polaris/PolarisFixedOptionsCombobox.tsx index ab601fd92..defd8d4d4 100644 --- a/packages/react/src/auto/polaris/PolarisFixedOptionsCombobox.tsx +++ b/packages/react/src/auto/polaris/PolarisFixedOptionsCombobox.tsx @@ -3,24 +3,54 @@ import { Autocomplete, InlineStack, Tag } from "@shopify/polaris"; import React, { useCallback, useMemo, useState } from "react"; export interface EnumOption { + /** + * The label for the option + */ label: string; + /** + * The value for the option + */ value: string; } type BaseComboboxProps = Omit & { - label?: string; + /** + * The label for the combobox + */ + label?: React.ReactNode; + /** + * The selectable options in the combobox + */ options: EnumOption[]; }; export type PolarisFixedOptionsSingleComboboxProps = BaseComboboxProps & { + /** + * The selected value + */ value?: string; + /** + * Called with the new selected value on value change + */ onChange: (value: string) => void; + /** + * Indicates that the combobox does not allow multiple selections + */ allowMultiple?: false; }; export type PolarisFixedOptionsMultiComboboxProps = BaseComboboxProps & { + /** + * The selected values + */ value?: string[]; + /** + * Called with the new selected values on value change + */ onChange: (value: string[]) => void; + /** + * Indicates that the combobox allows multiple selections + */ allowMultiple: true; }; diff --git a/packages/react/src/auto/polaris/index.ts b/packages/react/src/auto/polaris/index.ts index 0a29fce3e..08f8b6920 100644 --- a/packages/react/src/auto/polaris/index.ts +++ b/packages/react/src/auto/polaris/index.ts @@ -14,9 +14,9 @@ export { PolarisAutoNumberInput as AutoNumberInput } from "./inputs/PolarisAutoN export { PolarisAutoPasswordInput as AutoPasswordInput } from "./inputs/PolarisAutoPasswordInput.js"; export { PolarisAutoRolesInput as AutoRolesInput } from "./inputs/PolarisAutoRolesInput.js"; export { - PolarisAutoTextInput as AutoEmailInput, - PolarisAutoTextInput as AutoStringInput, - PolarisAutoTextInput as AutoUrlInput, + PolarisAutoEmailInput as AutoEmailInput, + PolarisAutoStringInput as AutoStringInput, + PolarisAutoUrlInput as AutoUrlInput, } from "./inputs/PolarisAutoTextInput.js"; export { PolarisAutoBelongsToInput as AutoBelongsToInput } from "./inputs/relationships/PolarisAutoBelongsToInput.js"; export { PolarisAutoHasManyInput as AutoHasManyInput } from "./inputs/relationships/PolarisAutoHasManyInput.js"; diff --git a/packages/react/src/auto/polaris/inputs/LazyLoadedPolarisAutoRichTextInput.tsx b/packages/react/src/auto/polaris/inputs/LazyLoadedPolarisAutoRichTextInput.tsx index 821dab05c..7882e3f34 100644 --- a/packages/react/src/auto/polaris/inputs/LazyLoadedPolarisAutoRichTextInput.tsx +++ b/packages/react/src/auto/polaris/inputs/LazyLoadedPolarisAutoRichTextInput.tsx @@ -4,6 +4,19 @@ import type { AutoRichTextInputProps } from "../../shared/AutoRichTextInputProps const LazyLoadedPolarisAutoRichTextInput = React.lazy(() => import("./PolarisAutoRichTextInput.js")); +/** + * A rich text input component for use within components. + * Requires `"@mdxeditor/editor"` as a peer dependency to be rendered + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The RichText field API identifier + * @param props.label - The label of the RichText input component + * @returns The rich text input component + */ export const PolarisAutoRichTextInput = autoInput((props: AutoRichTextInputProps) => { return ( <> diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoBooleanInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoBooleanInput.tsx index c153610db..33e768153 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoBooleanInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoBooleanInput.tsx @@ -1,12 +1,26 @@ import type { CheckboxProps } from "@shopify/polaris"; import { Checkbox } from "@shopify/polaris"; import React from "react"; -import type { Control } from "../../../useActionForm.js"; import { useController } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; import { useFieldMetadata } from "../../hooks/useFieldMetadata.js"; +import { AutoBooleanInputProps } from "../../shared/AutoInputTypes.js"; -export const PolarisAutoBooleanInput = autoInput((props: { field: string; control?: Control } & Partial) => { +export type PolarisAutoBooleanInputProps = AutoBooleanInputProps & Partial; + +/** + * A checkbox input that is controlled by react-hook-form + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The API identifier of the Boolean field + * @param props.label - The label of the checkbox + * @returns The checkbox input component + */ +export const PolarisAutoBooleanInput = autoInput((props: PolarisAutoBooleanInputProps) => { const { field: fieldApiIdentifier, control, ...rest } = props; const { path, metadata } = useFieldMetadata(fieldApiIdentifier); diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoDateTimePicker.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoDateTimePicker.tsx index e2be8eb4a..aa84cf495 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoDateTimePicker.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoDateTimePicker.tsx @@ -7,6 +7,7 @@ import type { GadgetDateTimeConfig } from "../../../internal/gql/graphql.js"; import { useController } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; import { useFieldMetadata } from "../../hooks/useFieldMetadata.js"; +import type { AutoDateTimeInputProps } from "../../shared/AutoInputTypes.js"; import type { DateTimeState } from "./PolarisAutoTimePicker.js"; import PolarisAutoTimePicker from "./PolarisAutoTimePicker.js"; @@ -39,110 +40,135 @@ export const getDateFromDateTimeObject = (dateTime: DateTimeState) => { date.setMilliseconds(0); return date; }; +export interface PolarisAutoDateTimePickerProps extends AutoDateTimeInputProps { + /** + * The HTML ID of the DateTime field + */ + id?: string; + /** + * Indicates if the Gadget DateTime field includes a time component + */ + includeTime?: boolean; + /** + * Indicates if the time popover should be hidden + */ + hideTimePopover?: boolean; + /** + * Props to pass to the Polaris DatePicker component + */ + datePickerProps?: Partial; + /** + * Props to pass to the Polaris TimePicker component + */ + timePickerProps?: Partial; +} -export const PolarisAutoDateTimePicker = autoInput( - (props: { - field: string; - id?: string; - value?: Date; - onChange?: (value: Date) => void; - error?: string; - includeTime?: boolean; - hideTimePopover?: boolean; - label?: string; - datePickerProps?: Partial; - timePickerProps?: Partial; - }) => { - const { path, metadata } = useFieldMetadata(props.field); +/** + * A date and time picker for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The API identifier of the DateTime field. + * @param props.label - The label of the date and time picker. + * @param props.includeTime - Indicates if the the time picker component should be shown. Defaults to the value of the includeTime field metadata configuration. + * @param props.hideTimePopover - Indicates if the time popover should be hidden. + * @param props.datePickerProps - Additional props passed to the Polaris DatePicker component. + * @param props.timePickerProps - Additional props passed to the Polaris TimePicker component. + * @returns The date and time picker component. + */ +export const PolarisAutoDateTimePicker = autoInput((props: PolarisAutoDateTimePickerProps) => { + const { path, metadata } = useFieldMetadata(props.field); - const { field: fieldProps, fieldState } = useController({ name: path }); + const { field: fieldProps, fieldState } = useController({ name: path }); - const { onChange, value } = props; - const localTz = Intl.DateTimeFormat().resolvedOptions().timeZone; - const localTime = useMemo(() => { - return value ? value : isValidDate(new Date(fieldProps.value)) ? new Date(fieldProps.value) : undefined; - }, [value, fieldProps.value]); + const { onChange, value } = props; + const localTz = Intl.DateTimeFormat().resolvedOptions().timeZone; + const localTime = useMemo(() => { + return value ? value : isValidDate(new Date(fieldProps.value)) ? new Date(fieldProps.value) : undefined; + }, [value, fieldProps.value]); - const [datePopoverActive, setDatePopoverActive] = useState(false); + const [datePopoverActive, setDatePopoverActive] = useState(false); - const [popoverMonth, setPopoverMonth] = useState(getDateTimeObjectFromDate(localTime ?? utcToZonedTime(new Date(), localTz)).month); - const [popoverYear, setPopoverYear] = useState(getDateTimeObjectFromDate(localTime ?? utcToZonedTime(new Date(), localTz)).year); + const [popoverMonth, setPopoverMonth] = useState(getDateTimeObjectFromDate(localTime ?? utcToZonedTime(new Date(), localTz)).month); + const [popoverYear, setPopoverYear] = useState(getDateTimeObjectFromDate(localTime ?? utcToZonedTime(new Date(), localTz)).year); - const config = metadata.configuration; + const config = metadata.configuration; - const onDateChange = useCallback>( - (range) => { - (fieldProps || value) && copyTime(range.start, zonedTimeToUtc(range.start, localTz)); - const dateOverride = value ?? new Date(fieldProps.value); - if (isValidDate(dateOverride)) { - range.start.setHours(dateOverride.getHours()); - range.start.setMinutes(dateOverride.getMinutes()); - range.start.setSeconds(dateOverride.getSeconds()); - range.start.setMilliseconds(dateOverride.getMilliseconds()); - } - onChange?.(zonedTimeToUtc(range.start, localTz)); - fieldProps.onChange(zonedTimeToUtc(range.start, localTz)); - setDatePopoverActive(false); - }, - [fieldProps, value, localTz, onChange] - ); + const onDateChange = useCallback>( + (range) => { + (fieldProps || value) && copyTime(range.start, zonedTimeToUtc(range.start, localTz)); + const dateOverride = value ?? new Date(fieldProps.value); + if (isValidDate(dateOverride)) { + range.start.setHours(dateOverride.getHours()); + range.start.setMinutes(dateOverride.getMinutes()); + range.start.setSeconds(dateOverride.getSeconds()); + range.start.setMilliseconds(dateOverride.getMilliseconds()); + } + onChange?.(zonedTimeToUtc(range.start, localTz)); + fieldProps.onChange(zonedTimeToUtc(range.start, localTz)); + setDatePopoverActive(false); + }, + [fieldProps, value, localTz, onChange] + ); - const toggleDatePopoverActive = useCallback(() => { - setPopoverMonth(getDateTimeObjectFromDate(isValidDate(localTime) && localTime ? localTime : new Date()).month); - setPopoverYear(getDateTimeObjectFromDate(isValidDate(localTime) && localTime ? localTime : new Date()).year); - setDatePopoverActive((active) => !active); - }, [localTime]); - const handleMonthChange = useCallback((month: number, year: number) => { - setPopoverMonth(month); - setPopoverYear(year); - }, []); + const toggleDatePopoverActive = useCallback(() => { + setPopoverMonth(getDateTimeObjectFromDate(isValidDate(localTime) && localTime ? localTime : new Date()).month); + setPopoverYear(getDateTimeObjectFromDate(isValidDate(localTime) && localTime ? localTime : new Date()).year); + setDatePopoverActive((active) => !active); + }, [localTime]); + const handleMonthChange = useCallback((month: number, year: number) => { + setPopoverMonth(month); + setPopoverYear(year); + }, []); - return ( - - } - autoComplete="off" - value={localTime ? formatShortDateString(localTime) : ""} - onFocus={toggleDatePopoverActive} - requiredIndicator={metadata.requiredArgumentForInput} - error={props.error ?? fieldState.error?.message} - /> - } - onClose={toggleDatePopoverActive} - > -
    - -
    -
    - {(props.includeTime ?? (config as GadgetDateTimeConfig).includeTime) && ( -
    - -
    - )} -
    - ); - } -); + return ( + + } + autoComplete="off" + value={localTime ? formatShortDateString(localTime) : ""} + onFocus={toggleDatePopoverActive} + requiredIndicator={metadata.requiredArgumentForInput} + error={props.error ?? fieldState.error?.message} + /> + } + onClose={toggleDatePopoverActive} + > +
    + +
    +
    + {(props.includeTime ?? (config as GadgetDateTimeConfig).includeTime) && ( +
    + +
    + )} +
    + ); +}); diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoEncryptedStringInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoEncryptedStringInput.tsx index d9bda63ad..b4ce79cd4 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoEncryptedStringInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoEncryptedStringInput.tsx @@ -2,31 +2,38 @@ import type { TextFieldProps } from "@shopify/polaris"; import { Button } from "@shopify/polaris"; import { HideIcon, ViewIcon } from "@shopify/polaris-icons"; import React, { useState } from "react"; -import type { Control } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; +import { AutoEncryptedStringInputProps } from "../../shared/AutoInputTypes.js"; import { PolarisAutoTextInput } from "./PolarisAutoTextInput.js"; -export const PolarisAutoEncryptedStringInput = autoInput( - ( - props: { - field: string; // The field API identifier - control?: Control; - } & Partial - ) => { - const [isShown, setIsShown] = useState(false); +export type PolarisAutoEncryptedStringInputProps = AutoEncryptedStringInputProps & Partial; - const showHideToggleButton = ( -
    -
    - ); +/** + * An encrypted string input for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The API identifier for the EncryptedString field. + * @param props.label - The label of the EncryptedString field. + * @returns The AutoEncryptedStringInput component. + */ +export const PolarisAutoEncryptedStringInput = autoInput((props: PolarisAutoEncryptedStringInputProps) => { + const [isShown, setIsShown] = useState(false); - return ; - } -); + const showHideToggleButton = ( +
    +
    + ); + + return ; +}); diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoEnumInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoEnumInput.tsx index e076a3c15..d5a519b4e 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoEnumInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoEnumInput.tsx @@ -1,134 +1,146 @@ import type { ComboboxProps } from "@shopify/polaris"; import { AutoSelection, Box, Combobox, InlineStack, Listbox, Tag, Text } from "@shopify/polaris"; import React, { useCallback } from "react"; -import type { Control } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; import { useEnumInputController } from "../../hooks/useEnumInputController.js"; +import { AutoEnumInputProps } from "../../shared/AutoInputTypes.js"; -export const PolarisAutoEnumInput = autoInput( - (props: { field: string; control?: Control; label?: string } & Partial) => { - const { field: fieldApiIdentifier, control, label: labelProp, ...comboboxProps } = props; - const { - allowMultiple, - allowOther, - onSelectionChange, - selectedOptions, - allOptions, - filteredOptions, - searchQuery, - label, - metadata, - isError, - errorMessage, - } = useEnumInputController({ field: fieldApiIdentifier, control }); - const { value: searchValue, setValue: setSearchValue } = searchQuery; +export type PolarisAutoEnumInputProps = AutoEnumInputProps & Partial; - let selectedTagsElement = null; - if (selectedOptions.length > 0) { - selectedTagsElement = ( - - {selectedOptions.map((tag) => ( - onSelectionChange(tag)}> - {tag} - - ))} - - ); - } - - const formatOptionText = useCallback( - (option: string) => { - const trimValue = searchValue.trim().toLocaleLowerCase(); - const matchIndex = option.toLocaleLowerCase().indexOf(trimValue); - - if (!searchValue || matchIndex === -1) return option; +/** + * An enum option picker for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The API identifier for the Enum field. + * @param props.label - The label of the input. + * @returns The AutoEnumInput component. + */ +export const PolarisAutoEnumInput = autoInput((props: PolarisAutoEnumInputProps) => { + const { field: fieldApiIdentifier, control, label: labelProp, ...comboboxProps } = props; + const { + allowMultiple, + allowOther, + onSelectionChange, + selectedOptions, + allOptions, + filteredOptions, + searchQuery, + label, + metadata, + isError, + errorMessage, + } = useEnumInputController({ field: fieldApiIdentifier, control }); + const { value: searchValue, setValue: setSearchValue } = searchQuery; - const start = option.slice(0, matchIndex); - const highlight = option.slice(matchIndex, matchIndex + trimValue.length); - const end = option.slice(matchIndex + trimValue.length, option.length); - - return ( -

    - {start} - - {highlight} - - {end} -

    - ); - }, - [searchValue] + let selectedTagsElement = null; + if (selectedOptions.length > 0) { + selectedTagsElement = ( + + {selectedOptions.map((tag) => ( + onSelectionChange(tag)}> + {tag} + + ))} + ); + } + + const formatOptionText = useCallback( + (option: string) => { + const trimValue = searchValue.trim().toLocaleLowerCase(); + const matchIndex = option.toLocaleLowerCase().indexOf(trimValue); - let optionItemElement = null; - if (allOptions.length > 0) { - optionItemElement = filteredOptions.map((option) => { - return ( - - {formatOptionText(option)} - - ); - }); - } + if (!searchValue || matchIndex === -1) return option; - let addExtraOptionElement = null; - if (allowOther && searchValue && !allOptions.includes(searchValue) && searchValue.trim().length > 0) { - addExtraOptionElement = {`Add "${searchValue}"`}; - } + const start = option.slice(0, matchIndex); + const highlight = option.slice(matchIndex, matchIndex + trimValue.length); + const end = option.slice(matchIndex + trimValue.length, option.length); - let emptyStateElement = null; - if (!allowOther && (!optionItemElement || optionItemElement.length === 0) && searchValue) { - emptyStateElement = ( - - {`No options found matching "${searchValue}"`} - + return ( +

    + {start} + + {highlight} + + {end} +

    ); - } + }, + [searchValue] + ); - let listBoxElement = null; - if (optionItemElement || addExtraOptionElement || emptyStateElement) { - listBoxElement = ( - { - onSelectionChange(selected); - if (allowMultiple) { - setSearchValue(""); - } - }} - > - {emptyStateElement} - {addExtraOptionElement} - {optionItemElement} - + let optionItemElement = null; + if (allOptions.length > 0) { + optionItemElement = filteredOptions.map((option) => { + return ( + + {formatOptionText(option)} + ); - } + }); + } + + let addExtraOptionElement = null; + if (allowOther && searchValue && !allOptions.includes(searchValue) && searchValue.trim().length > 0) { + addExtraOptionElement = {`Add "${searchValue}"`}; + } - const inputLabel = ( - <> - {labelProp ?? label} {metadata.requiredArgumentForInput ? * : null} - + let emptyStateElement = null; + if (!allowOther && (!optionItemElement || optionItemElement.length === 0) && searchValue) { + emptyStateElement = ( + + {`No options found matching "${searchValue}"`} + ); + } - return ( - - } - {...comboboxProps} + let listBoxElement = null; + if (optionItemElement || addExtraOptionElement || emptyStateElement) { + listBoxElement = ( + { + onSelectionChange(selected); + if (allowMultiple) { + setSearchValue(""); + } + }} > - {listBoxElement} - + {emptyStateElement} + {addExtraOptionElement} + {optionItemElement} + ); } -); + + const inputLabel = ( + <> + {labelProp ?? label} {metadata.requiredArgumentForInput ? * : null} + + ); + + return ( + + } + {...comboboxProps} + > + {listBoxElement} + + ); +}); diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoFileInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoFileInput.tsx index b297d6e62..304cc9ed6 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoFileInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoFileInput.tsx @@ -3,12 +3,27 @@ import { Box, Button, DropZone, InlineError, InlineStack, Thumbnail } from "@sho import { DeleteIcon, NoteIcon } from "@shopify/polaris-icons"; import { filesize } from "filesize"; import React, { useMemo } from "react"; -import type { Control } from "../../../useActionForm.js"; import { isAutoFileFieldValue } from "../../../validationSchema.js"; import { autoInput } from "../../AutoInput.js"; import { getFileSizeValidationMessage, imageFileTypes, useFileInputController } from "../../hooks/useFileInputController.js"; +import { AutoFileInputProps } from "../../shared/AutoInputTypes.js"; -export const PolarisAutoFileInput = autoInput((props: { field: string; control?: Control } & Omit) => { +export type PolarisAutoFileInputProps = AutoFileInputProps & Omit; + +/** + * A file input for use within components + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The API identifier of the File field + * @param props.label - The label of the File field + * @param props.onChange - called when the file input changes + * @returns The file input component + */ +export const PolarisAutoFileInput = autoInput((props: PolarisAutoFileInputProps) => { const { field: fieldApiIdentifier, control, ...rest } = props; const { fieldProps, errorMessage, imageThumbnailURL, onFileUpload, clearFileValue, canClearFileValue, validations, metadata } = useFileInputController({ diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoHiddenInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoHiddenInput.tsx index 59a86938b..608193642 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoHiddenInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoHiddenInput.tsx @@ -1,14 +1,22 @@ import React from "react"; import { autoInput } from "../../AutoInput.js"; import { useHiddenInput } from "../../hooks/useHiddenInput.js"; +import { AutoHiddenInputProps } from "../../shared/AutoInputTypes.js"; -export const PolarisAutoHiddenInput = autoInput( - (props: { - field: string; // The field API identifier - value: any; - }) => { - const fieldProps = useHiddenInput(props); +/** + * A hidden input for use within components. The value is included in form submission without rendering a visible input. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The field API identifier + * @param props.value - The value to be included in form submission + * @returns The hidden input component + */ +export const PolarisAutoHiddenInput = autoInput((props: AutoHiddenInputProps) => { + const fieldProps = useHiddenInput(props); - return ; - } -); + return ; +}); diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoIdInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoIdInput.tsx index b442c3e07..af00f6516 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoIdInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoIdInput.tsx @@ -2,20 +2,28 @@ import React from "react"; import { FieldType } from "../../../metadata.js"; import { autoInput } from "../../AutoInput.js"; import { useStringInputController } from "../../hooks/useStringInputController.js"; +import { AutoIdInputProps } from "../../shared/AutoInputTypes.js"; import { PolarisAutoTextInput } from "./PolarisAutoTextInput.js"; -export const PolarisAutoIdInput = autoInput( - (props: { - field: string; // The field API identifier - label?: string; - }) => { - const { field, label } = props; - const { name, metadata } = useStringInputController({ field }); +/** + * An id input component for use within components + * @example + * ```tsx + * + * + * + * ``` + * @param props.field The API identifier of the Id field + * @param props.label The label of the Id field + * @returns The id input component + */ +export const PolarisAutoIdInput = autoInput((props: AutoIdInputProps) => { + const { field, label } = props; + const { name, metadata } = useStringInputController({ field }); - if (metadata.fieldType !== FieldType.Id || field !== "id") { - throw new Error(`PolarisAutoIdInput: field ${field} is not of type Id`); - } - - return ; + if (metadata.fieldType !== FieldType.Id || field !== "id") { + throw new Error(`PolarisAutoIdInput: field ${field} is not of type Id`); } -); + + return ; +}); diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoInput.tsx index 7aac0e012..1a0a31fd9 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoInput.tsx @@ -2,6 +2,7 @@ import React from "react"; import { FieldType } from "../../../metadata.js"; import { autoInput } from "../../AutoInput.js"; import { useFieldMetadata } from "../../hooks/useFieldMetadata.js"; +import { AutoInputProps } from "../../shared/AutoInputTypes.js"; import { PolarisAutoRichTextInput } from "./LazyLoadedPolarisAutoRichTextInput.js"; import { PolarisAutoBooleanInput } from "./PolarisAutoBooleanInput.js"; import { PolarisAutoDateTimePicker } from "./PolarisAutoDateTimePicker.js"; @@ -17,7 +18,21 @@ import { PolarisAutoTextInput } from "./PolarisAutoTextInput.js"; import { PolarisAutoBelongsToInput } from "./relationships/PolarisAutoBelongsToInput.js"; import { PolarisAutoHasManyInput } from "./relationships/PolarisAutoHasManyInput.js"; -export const PolarisAutoInput = autoInput((props: { field: string; label?: string }) => { +/** + * An automatically generated input component based on the given field's type for use within components + * @example + * ```tsx + * + * + * + * + * + * ``` + * @param props.field The API identifier of the field + * @param props.label The label of the field + * @returns The input component + */ +export const PolarisAutoInput = autoInput((props: AutoInputProps) => { const { metadata } = useFieldMetadata(props.field); const config = metadata.configuration; diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoJSONInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoJSONInput.tsx index d75455948..249d2c61e 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoJSONInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoJSONInput.tsx @@ -1,37 +1,44 @@ import type { TextFieldProps } from "@shopify/polaris"; import { TextField } from "@shopify/polaris"; import React from "react"; -import type { Control } from "../../../useActionForm.js"; import { useFocus } from "../../../useFocus.js"; import { getPropsWithoutRef } from "../../../utils.js"; import { autoInput } from "../../AutoInput.js"; import { useJSONInputController } from "../../hooks/useJSONInputController.js"; +import { AutoJSONInputProps } from "../../shared/AutoInputTypes.js"; -export const PolarisAutoJSONInput = autoInput( - ( - props: { - field: string; // The field API identifier - control?: Control; - } & Partial> - ) => { - const [isFocused, focusProps] = useFocus(); - const { field: _field, control: _control, ...restOfProps } = props; - const { type: _type, errorMessage, ...controller } = useJSONInputController(props); +type PolarisAutoJSONInputProps = AutoJSONInputProps & Partial>; - const label = props.label ?? controller.label; - return ( - <> - - - ); - } -); +/** + * JSON field input component for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The JSON field API identifier + * @param props.label - The label of the JSON input component + * @returns The JSON input component + */ +export const PolarisAutoJSONInput = autoInput((props: PolarisAutoJSONInputProps) => { + const [isFocused, focusProps] = useFocus(); + const { field: _field, control: _control, ...restOfProps } = props; + const { type: _type, errorMessage, ...controller } = useJSONInputController(props); + + const label = props.label ?? controller.label; + return ( + <> + + + ); +}); diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoNumberInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoNumberInput.tsx index 94102df49..7ec49c8f7 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoNumberInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoNumberInput.tsx @@ -1,34 +1,41 @@ import type { TextFieldProps } from "@shopify/polaris"; import React from "react"; -import type { Control } from "../../../useActionForm.js"; import { autoInput } from "../../AutoInput.js"; import { useStringInputController } from "../../hooks/useStringInputController.js"; +import { AutoNumberInputProps } from "../../shared/AutoInputTypes.js"; import { PolarisAutoTextInput } from "./PolarisAutoTextInput.js"; -export const PolarisAutoNumberInput = autoInput( - ( - props: { - field: string; // The field API identifier - control?: Control; - } & Partial - ) => { - const { field, control } = props; - const { type, metadata, value } = useStringInputController({ field, control }); - const fieldType = type as TextFieldProps["type"]; +type PolarisAutoNumberInputProps = AutoNumberInputProps & Partial; - const step = - fieldType === "number" && - metadata.configuration.__typename === "GadgetNumberConfig" && - metadata.configuration.decimals && - metadata.configuration.decimals > 0 - ? getStepFromNumberOfDecimals(metadata.configuration.decimals) - : value - ? getStepFromNumberOfDecimals(countNumberOfDecimals(`${value}`)) - : 1; +/** + * A number input component for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The number field API identifier + * @param props.label - The label of the number input component + * @returns The number input component + */ +export const PolarisAutoNumberInput = autoInput((props: PolarisAutoNumberInputProps) => { + const { field, control } = props; + const { type, metadata, value } = useStringInputController({ field, control }); + const fieldType = type as TextFieldProps["type"]; - return ; - } -); + const step = + fieldType === "number" && + metadata.configuration.__typename === "GadgetNumberConfig" && + metadata.configuration.decimals && + metadata.configuration.decimals > 0 + ? getStepFromNumberOfDecimals(metadata.configuration.decimals) + : value + ? getStepFromNumberOfDecimals(countNumberOfDecimals(`${value}`)) + : 1; + + return ; +}); const getStepFromNumberOfDecimals = (numberOfDecimals: number) => numberOfDecimals === 0 ? 1 : Number(`0.${"0".repeat(numberOfDecimals - 1)}1`); diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoPasswordInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoPasswordInput.tsx index 16e86dcd0..2e929df4c 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoPasswordInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoPasswordInput.tsx @@ -2,52 +2,59 @@ import type { TextFieldProps } from "@shopify/polaris"; import { Button } from "@shopify/polaris"; import { EditIcon } from "@shopify/polaris-icons"; import React, { useState } from "react"; -import { useController, type Control } from "../../../useActionForm.js"; +import { useController } from "../../../useActionForm.js"; import { useAutoFormMetadata } from "../../AutoFormContext.js"; import { autoInput } from "../../AutoInput.js"; import { useFieldMetadata } from "../../hooks/useFieldMetadata.js"; +import { AutoPasswordInputProps } from "../../shared/AutoInputTypes.js"; import { PolarisAutoEncryptedStringInput } from "./PolarisAutoEncryptedStringInput.js"; /** * The salted password hash is not retrieved from the DB * Regardless of the password is defined or not, this placeholder is shown as exposing an unset password is a security risk */ const existingPasswordPlaceholder = "********"; +type PolarisAutoPasswordInputProps = AutoPasswordInputProps & Partial; -export const PolarisAutoPasswordInput = autoInput( - ( - props: { - field: string; // The field API identifier - control?: Control; - } & Partial - ) => { - const { findBy } = useAutoFormMetadata(); - const { path } = useFieldMetadata(props.field); - const { field: fieldProps } = useController({ name: path }); +/** + * A password input component for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The password field API identifier + * @param props.label - The label of the password input component + * @returns The password input component + */ +export const PolarisAutoPasswordInput = autoInput((props: PolarisAutoPasswordInputProps) => { + const { findBy } = useAutoFormMetadata(); + const { path } = useFieldMetadata(props.field); + const { field: fieldProps } = useController({ name: path }); - const [isEditing, setIsEditing] = useState(!findBy); + const [isEditing, setIsEditing] = useState(!findBy); - const startEditing = () => { - fieldProps.onChange(""); // Touch the field to mark it as dirty - setIsEditing(true); - }; + const startEditing = () => { + fieldProps.onChange(""); // Touch the field to mark it as dirty + setIsEditing(true); + }; - const startEditingButton = ( -
    -
    - ); + const startEditingButton = ( +
    +
    + ); - return ( - - ); - } -); + return ( + + ); +}); diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoRolesInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoRolesInput.tsx index 4ca5432f9..5ae2a0db6 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoRolesInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoRolesInput.tsx @@ -1,40 +1,47 @@ import React from "react"; -import type { Control } from "../../../useActionForm.js"; import { getPropsWithoutRef } from "../../../utils.js"; import { autoInput } from "../../AutoInput.js"; import { useRoleInputController } from "../../hooks/useRoleInputController.js"; +import { AutoRolesInputProps } from "../../shared/AutoInputTypes.js"; import type { PolarisFixedOptionsMultiComboboxProps } from "../PolarisFixedOptionsCombobox.js"; import { PolarisFixedOptionsCombobox } from "../PolarisFixedOptionsCombobox.js"; -export const PolarisAutoRolesInput = autoInput( - ( - props: { - field: string; // Field API identifier - control?: Control; - } & Partial - ) => { - const { options, loading, rolesError, fieldError, selectedRoleKeys, fieldProps, metadata } = useRoleInputController(props); +type PolarisAutoRolesInputProps = AutoRolesInputProps & Partial; - if (rolesError) { - throw rolesError; - } - if (fieldError) { - throw fieldError; - } +/** + * A role list input component for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The role list field API identifier + * @param props.label - The label of the role list field + * @returns The Polaris Auto Roles Input component + */ +export const PolarisAutoRolesInput = autoInput((props: PolarisAutoRolesInputProps) => { + const { options, loading, rolesError, fieldError, selectedRoleKeys, fieldProps, metadata } = useRoleInputController(props); - if (loading || !options || options.length === 0) { - // Don't render until role options exist. There must always be at least one role option `unauthenticated` - return null; - } + if (rolesError) { + throw rolesError; + } + if (fieldError) { + throw fieldError; + } - return ( - - ); + if (loading || !options || options.length === 0) { + // Don't render until role options exist. There must always be at least one role option `unauthenticated` + return null; } -); + + return ( + + ); +}); diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoTextInput.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoTextInput.tsx index 735b66421..84571040d 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoTextInput.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoTextInput.tsx @@ -1,30 +1,79 @@ import type { TextFieldProps } from "@shopify/polaris"; import { TextField } from "@shopify/polaris"; import React from "react"; -import type { Control } from "../../../useActionForm.js"; import { getPropsWithoutRef } from "../../../utils.js"; import { autoInput } from "../../AutoInput.js"; import { useStringInputController } from "../../hooks/useStringInputController.js"; +import { AutoTextInputProps } from "../../shared/AutoInputTypes.js"; -export const PolarisAutoTextInput = autoInput( - ( - props: { - field: string; // The field API identifier - control?: Control; - } & Partial - ) => { - const { field, control } = props; - const stringInputController = useStringInputController({ field, control }); +type PolarisAutoTextInputProps = AutoTextInputProps & Partial; - return ( - - ); - } -); +/** + * A text input component for string based fields for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The field API identifier + * @param props.label - The label of the field + * @returns The Input component + */ +export const PolarisAutoTextInput = autoInput((props: PolarisAutoTextInputProps) => { + const { field, control } = props; + const stringInputController = useStringInputController({ field, control }); + + return ( + + ); +}); + +/** + * An input component for email fields for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The email field API identifier + * @param props.label - The label of the field + * @returns The Input component + */ +export const PolarisAutoEmailInput = (props: AutoTextInputProps) => ; + +/** + * A input component for string fields for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The string field API identifier + * @param props.label - The label of the string field + * @returns The Input component + */ +export const PolarisAutoStringInput = (props: AutoTextInputProps) => ; + +/** + * A input component for URL fields for use within components. + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The url field API identifier + * @param props.label - The label of the url field + * @returns The Input component + */ +export const PolarisAutoUrlInput = (props: AutoTextInputProps) => ; diff --git a/packages/react/src/auto/polaris/inputs/PolarisAutoTimePicker.tsx b/packages/react/src/auto/polaris/inputs/PolarisAutoTimePicker.tsx index aa106ea9d..6c836705a 100644 --- a/packages/react/src/auto/polaris/inputs/PolarisAutoTimePicker.tsx +++ b/packages/react/src/auto/polaris/inputs/PolarisAutoTimePicker.tsx @@ -66,16 +66,42 @@ export interface DateTimeState { type DateTimeKey = keyof DateTimeState; -const PolarisAutoTimePicker = (props: { +export interface PolarisAutoTimePickerProps { + /** + * React Hook Form ControllerRenderProps object that controls the DateTime field + */ fieldProps: ControllerRenderProps; + /** + * The value of the DateTime field + */ value?: Date; + /** + * Called when the value of the DateTime field changes + */ onChange?: (value: Date) => void; - localTime?: Date; + /** + * The HTML ID of the DateTime field + */ id?: string; + /** + * Props to pass to the Polaris TimePicker component + */ timePickerProps?: Partial; + /** + * Indicates if the time popover should be hidden + */ hideTimePopover?: boolean; + /** + * The local time of the DateTime field + */ + localTime?: Date; + /** + * The local time zone of the DateTime field + */ localTz?: string; -}) => { +} + +const PolarisAutoTimePicker = (props: PolarisAutoTimePickerProps) => { const [valueProp, setValueProp] = useState(props.value); const [timeString, setTimeString] = useState(props.localTime ? getTimeString(getDateTimeObjectFromDate(props.localTime)) : ""); const [timePopoverActive, setTimePopoverActive] = useState(false); diff --git a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoBelongsToInput.tsx b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoBelongsToInput.tsx index 8edbafd00..683f8eb30 100644 --- a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoBelongsToInput.tsx +++ b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoBelongsToInput.tsx @@ -6,6 +6,22 @@ import { optionRecordsToLoadCount } from "../../../hooks/useRelatedModelOptions. import type { AutoRelationshipInputProps } from "../../../interfaces/AutoRelationshipInputProps.js"; import { RelatedModelOptions } from "./RelatedModelOptions.js"; +/** + * A belongsTo field input component for use within components + * This component is used to configure relationships with records from a related model + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The belongsTo field API identifier + * @param props.label - The label of the belongTo field input component + * @param props.optionLabel - Controls how records on the related model are displayed as options in the relationship field input component. + * When using a string, the string will indicate the field on the related model record to be displayed as the option label. + * When using a function, the function will be called with the record to return a ReactNode to be displayed as the option label + * @returns The belongsTo field input component + */ export const PolarisAutoBelongsToInput = autoInput((props: AutoRelationshipInputProps) => { const { fieldMetadata: { path, metadata }, diff --git a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasManyInput.tsx b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasManyInput.tsx index cdb6fd4af..f6f10474e 100644 --- a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasManyInput.tsx +++ b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasManyInput.tsx @@ -8,6 +8,22 @@ import type { AutoRelationshipInputProps } from "../../../interfaces/AutoRelatio import { RelatedModelOptions } from "./RelatedModelOptions.js"; import { getSelectedRelatedRecordTags } from "./SelectedRelatedRecordTags.js"; +/** + * A hasMany field input component for use within components + * This component is used to configure relationships with records from a related model + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The hasMany field API identifier + * @param props.label - The label of the hasMany field input component + * @param props.optionLabel - Controls how records on the related model are displayed as options in the relationship field input component. + * When using a string, the string will indicate the field on the related model record to be displayed as the option label. + * When using a function, the function will be called with the record to return a ReactNode to be displayed as the option label + * @returns The hasMany field input component + */ export const PolarisAutoHasManyInput = autoInput((props: AutoRelationshipInputProps) => { const { field } = props; diff --git a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasOneInput.tsx b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasOneInput.tsx index 5bbb99951..3f829a8b2 100644 --- a/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasOneInput.tsx +++ b/packages/react/src/auto/polaris/inputs/relationships/PolarisAutoHasOneInput.tsx @@ -12,6 +12,22 @@ import { getSelectedRelatedRecordTags } from "./SelectedRelatedRecordTags.js"; */ const showErrorBannerWhenTooManyRelatedRecords = false; +/** + * A hasOne field input component for use within components + * This component is used to configure relationships with records from a related model + * @example + * ```tsx + * + * + * + * ``` + * @param props.field - The hasOne field API identifier + * @param props.label - The label of the hasOne field input component + * @param props.optionLabel - Controls how records on the related model are displayed as options in the relationship field input component. + * When using a string, the string will indicate the field on the related model record to be displayed as the option label. + * When using a function, the function will be called with the record to return a ReactNode to be displayed as the option label + * @returns The hasOne field input component + */ export const PolarisAutoHasOneInput = autoInput((props: AutoRelationshipInputProps) => { const { field } = props; const { diff --git a/packages/react/src/auto/polaris/submit/PolarisSubmitResultBanner.tsx b/packages/react/src/auto/polaris/submit/PolarisSubmitResultBanner.tsx index 3676e4b7c..9aae1255c 100644 --- a/packages/react/src/auto/polaris/submit/PolarisSubmitResultBanner.tsx +++ b/packages/react/src/auto/polaris/submit/PolarisSubmitResultBanner.tsx @@ -3,7 +3,15 @@ import { Banner } from "@shopify/polaris"; import React from "react"; import { useResultBannerController } from "../../hooks/useResultBannerController.js"; -export const PolarisSubmitResultBanner = (props: { successBannerProps?: BannerProps; errorBannerProps?: BannerProps }) => { +type PolarisSubmitResultBannerProps = { successBannerProps?: BannerProps; errorBannerProps?: BannerProps }; + +/** + * A banner that displays the result of an AutoForm submission. + * @param props.successBannerProps - The props for the successful banner + * @param props.errorBannerProps - The props for the error banner + * @returns The banner component + */ +export const PolarisSubmitResultBanner = (props: PolarisSubmitResultBannerProps) => { return ( <> diff --git a/packages/react/src/auto/shared/AutoInputTypes.tsx b/packages/react/src/auto/shared/AutoInputTypes.tsx new file mode 100644 index 000000000..1f0221a14 --- /dev/null +++ b/packages/react/src/auto/shared/AutoInputTypes.tsx @@ -0,0 +1,169 @@ +import type { Control } from "../../useActionForm.js"; + +export interface ControllableWithReactHookForm { + /** + * React Hook Form control object + */ + control?: Control; +} + +export type InputLabel = string | React.ReactNode | null; + +export interface AutoBooleanInputProps extends ControllableWithReactHookForm { + /** + * The API identifier of the Boolean field + */ + field: string; + /** + * The label of the Boolean field + */ + label?: InputLabel; +} + +export interface AutoDateTimeInputProps extends ControllableWithReactHookForm { + /** + * The API identifier of the DateTime field + */ + field: string; + /** + * The value of the DateTime field + */ + value?: Date; + /** + * Called when the value of the DateTime field changes + */ + onChange?: (value: Date) => void; + /** + * Error message to display if the DateTime field is invalid + */ + error?: string; + /** + * The label of the DateTime field + */ + label?: InputLabel; +} + +export interface AutoEncryptedStringInputProps extends ControllableWithReactHookForm { + /** + * The API identifier of the EncryptedString field + */ + field: string; + /** + * The label of the EncryptedString field + */ + label?: InputLabel; +} + +export interface AutoEnumInputProps extends ControllableWithReactHookForm { + /** + * The API identifier of the Enum field + */ + field: string; + /** + * The label of the Enum field + */ + label?: InputLabel; +} + +export interface AutoFileInputProps extends ControllableWithReactHookForm { + /** + * The API identifier of the File field + */ + field: string; + /** + * The label of the File field + */ + label?: InputLabel; +} + +export interface AutoHiddenInputProps { + /** + * The API identifier of the Hidden field + */ + field: string; + /** + * The value of the Hidden field + */ + value: any; +} + +export interface AutoIdInputProps { + /** + * The API identifier of the Id field + */ + field: string; + /** + * The label of the Id field + */ + label?: InputLabel; +} + +export interface AutoInputProps { + /** + * The API identifier of the field + */ + field: string; + /** + * The label of the field + */ + label?: InputLabel; +} + +export interface AutoJSONInputProps extends ControllableWithReactHookForm { + /** + * The API identifier of the JSON field + */ + field: string; + /** + * The label of the JSON field + */ + label?: InputLabel; +} + +export interface AutoNumberInputProps extends ControllableWithReactHookForm { + /** + * The API identifier of the Number field + */ + field: string; + /** + * The label of the Number field + */ + label?: InputLabel; +} + +export interface AutoPasswordInputProps { + /** + * The API identifier of the Password field + */ + field: string; + /** + * The label of the Password field + */ + label?: InputLabel; + /** + * React Hook Form control object + */ + control?: Control; +} + +export interface AutoRolesInputProps extends ControllableWithReactHookForm { + /** + * The API identifier of the Roles field + */ + field: string; + /** + * The label of the Roles field + */ + label?: InputLabel; +} + +export interface AutoTextInputProps extends ControllableWithReactHookForm { + /** + * The API identifier of the field + */ + field: string; + /** + * The label of the field + */ + label?: InputLabel; +} diff --git a/packages/react/src/auto/shared/AutoRichTextInputProps.tsx b/packages/react/src/auto/shared/AutoRichTextInputProps.tsx index 159e0ceef..3a885a88b 100644 --- a/packages/react/src/auto/shared/AutoRichTextInputProps.tsx +++ b/packages/react/src/auto/shared/AutoRichTextInputProps.tsx @@ -2,12 +2,27 @@ import type { ForwardedRef } from "react"; import type { Control } from "../../useActionForm.js"; export interface MDXEditorMethods { + /** + * Sets the markdown content of the editor + */ setMarkdown: (markdown: string) => void; } export interface AutoRichTextInputProps { + /** + * The API identifier of the RichText field + */ field: string; + /** + * Control object from React Hook Form + */ control?: Control; + /** + * Forwards ref object to setMarkdown content of the editor + */ editorRef?: ForwardedRef | null; - label?: string; + /** + * The label for the input + */ + label?: React.ReactNode; }