From 4db8de357faf93449122d9feed1652f627c7938d Mon Sep 17 00:00:00 2001
From: ghadabezine <114864513+ghadabezine@users.noreply.github.com>
Date: Mon, 12 May 2025 23:54:53 +0100
Subject: [PATCH 1/2] feat: add emptyText prop to SelectArrayInput component
---
.../src/input/SelectArrayInput.tsx | 37 ++++++++++++-------
1 file changed, 23 insertions(+), 14 deletions(-)
diff --git a/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx b/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx
index cdcf06a7f0b..a3bc3203fb7 100644
--- a/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx
+++ b/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx
@@ -125,6 +125,7 @@ export const SelectArrayInput = (inProps: SelectArrayInputProps) => {
variant,
disabled,
readOnly,
+ emptyText,
...rest
} = props;
@@ -332,21 +333,28 @@ export const SelectArrayInput = (inProps: SelectArrayInputProps) => {
renderValue={(selected: any[]) => (
{(Array.isArray(selected) ? selected : [])
- .map(item =>
- (allChoices || []).find(
- // eslint-disable-next-line eqeqeq
- choice => getChoiceValue(choice) == item
+ .length === 0 && emptyText ? (
+ {emptyText}
+ ) : (
+ (Array.isArray(selected) ? selected : [])
+ .map(item =>
+ (allChoices || []).find(
+ choice =>
+ getChoiceValue(choice) == item
+ )
)
- )
- .filter(item => !!item)
- .map(item => (
-
- ))}
+ .filter(item => !!item)
+ .map(item => (
+
+ ))
+ )}
)}
disabled={disabled || readOnly}
@@ -382,6 +390,7 @@ export type SelectArrayInputProps = ChoicesProps &
options?: SelectProps;
disableValue?: string;
source?: string;
+ emptyText?: string | React.ReactElement;
onChange?: (event: ChangeEvent | RaRecord) => void;
};
From 950d37f2c0478267ee2be1eee7e16becc329b76a Mon Sep 17 00:00:00 2001
From: ghadabezine <114864513+ghadabezine@users.noreply.github.com>
Date: Mon, 12 May 2025 23:57:55 +0100
Subject: [PATCH 2/2] docs: add documentation for custom field components with
defaultProps
---
docs/CustomFields.md | 182 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 182 insertions(+)
create mode 100644 docs/CustomFields.md
diff --git a/docs/CustomFields.md b/docs/CustomFields.md
new file mode 100644
index 00000000000..5fdb562edce
--- /dev/null
+++ b/docs/CustomFields.md
@@ -0,0 +1,182 @@
+# Custom Fields
+
+This document explains how to create custom field components in react-admin that integrate seamlessly with the rest of the framework.
+
+## Basic Structure
+
+A custom field component should:
+
+1. Extend the `FieldProps` interface
+2. Use the `useFieldValue` hook to get the field value
+3. Handle empty values appropriately
+4. Support translation
+5. Support the common field props (label, source, etc.)
+
+Here's a basic example:
+
+```tsx
+import * as React from 'react';
+import { Typography } from '@mui/material';
+import { useFieldValue, useTranslate, FieldProps } from 'react-admin';
+import { sanitizeFieldRestProps } from './sanitizeFieldRestProps';
+
+export const CustomField = = Record>(
+ props: CustomFieldProps
+) => {
+ const { className, emptyText, ...rest } = props;
+ const value = useFieldValue(props);
+ const translate = useTranslate();
+
+ if (value == null) {
+ return emptyText ? (
+
+ {translate(emptyText, { _: emptyText })}
+
+ ) : null;
+ }
+
+ return (
+
+ {value}
+
+ );
+};
+
+export interface CustomFieldProps<
+ RecordType extends Record = Record
+> extends FieldProps {
+ // Add any custom props here
+}
+```
+
+## Using defaultProps for Labels
+
+When creating a custom field component, it's recommended to use `defaultProps` to set default values for the `label` prop. This ensures consistent behavior across your application and makes the component more reusable.
+
+Here's how to implement it:
+
+```tsx
+import * as React from 'react';
+import { Typography } from '@mui/material';
+import { useFieldValue, useTranslate, FieldProps } from 'react-admin';
+import { sanitizeFieldRestProps } from './sanitizeFieldRestProps';
+
+export const CustomField = = Record>(
+ props: CustomFieldProps
+) => {
+ // ... component implementation
+};
+
+CustomField.defaultProps = {
+ label: 'Custom Field', // Default label
+ addLabel: true, // Show label by default
+};
+
+export interface CustomFieldProps<
+ RecordType extends Record = Record
+> extends FieldProps {
+ // Add any custom props here
+}
+```
+
+The benefits of using `defaultProps` for labels include:
+
+1. **Consistency**: All instances of your field will have a default label if none is provided
+2. **Type Safety**: TypeScript will know about the default values
+3. **Documentation**: It makes it clear what the default behavior is
+4. **Maintainability**: You can change the default value in one place
+
+## Best Practices
+
+1. **Always extend FieldProps**: This ensures your component supports all standard field props like `source`, `label`, `emptyText`, etc.
+
+2. **Use defaultProps for common values**: Set sensible defaults for `label` and other commonly used props.
+
+3. **Handle empty values**: Always check for null/undefined values and use the `emptyText` prop appropriately.
+
+4. **Support translation**: Use the `useTranslate` hook for all user-facing strings, including the default label.
+
+5. **Use sanitizeFieldRestProps**: This utility function removes field-specific props before passing them to the underlying component.
+
+6. **Type your props properly**: Use generics to ensure type safety with your record type.
+
+## Example with All Best Practices
+
+```tsx
+import * as React from 'react';
+import { Typography } from '@mui/material';
+import { useFieldValue, useTranslate, FieldProps } from 'react-admin';
+import { sanitizeFieldRestProps } from './sanitizeFieldRestProps';
+
+export const CustomField = = Record>(
+ props: CustomFieldProps
+) => {
+ const { className, emptyText, ...rest } = props;
+ const value = useFieldValue(props);
+ const translate = useTranslate();
+
+ if (value == null) {
+ return emptyText ? (
+
+ {translate(emptyText, { _: emptyText })}
+
+ ) : null;
+ }
+
+ return (
+
+ {translate(value.toString())}
+
+ );
+};
+
+CustomField.defaultProps = {
+ label: 'custom.field', // Translation key for default label
+ addLabel: true,
+ emptyText: 'custom.field.empty', // Translation key for empty state
+};
+
+export interface CustomFieldProps<
+ RecordType extends Record = Record
+> extends FieldProps {
+ // Add any custom props here
+}
+```
+
+## Usage
+
+```tsx
+// Basic usage with default label
+
+
+// Override default label
+
+
+// Disable label
+
+
+// Custom empty text
+
+```
+
+Remember to add translations for your default labels and empty text messages in your translation files.
\ No newline at end of file