= defaultTheme;
+
+export default sistentTheme;
+export { generateTheme };
diff --git a/src/custom/RJSFFormWrapper/theme/util.ts b/src/custom/RJSFFormWrapper/theme/util.ts
new file mode 100644
index 000000000..4588c26ad
--- /dev/null
+++ b/src/custom/RJSFFormWrapper/theme/util.ts
@@ -0,0 +1,189 @@
+import type {
+ AlertProps,
+ BoxProps,
+ ButtonProps,
+ CardProps,
+ CheckboxProps,
+ DividerProps,
+ FormControlLabelProps,
+ FormControlProps,
+ FormGroupProps,
+ FormHelperTextProps,
+ FormLabelProps,
+ GridProps,
+ InputBaseComponentProps,
+ InputLabelProps,
+ InputProps,
+ ListItemProps,
+ ListProps,
+ MenuItemProps,
+ PaperProps,
+ RadioGroupProps,
+ RadioProps,
+ SelectProps,
+ SliderProps,
+ SvgIconProps,
+ SwitchProps,
+ TextFieldProps,
+ TypographyProps
+} from '@mui/material';
+import type { SxProps, Theme } from '@mui/material/styles';
+import type { FormContextType, RJSFSchema, StrictRJSFSchema, UIOptionsType } from '@rjsf/utils';
+
+export type SistentSlotProps = Omit
;
+
+/**
+ * Slot props for individual Sistent/MUI sub-components across RJSF widgets and templates.
+ * Explicitly declares known sub-component slot props with their corresponding MUI prop types.
+ */
+export interface SistentMuiSlotProps {
+ // Radio & Checkbox slots
+ radioGroup?: Partial>;
+ formGroup?: Partial>;
+ formControlLabel?: Partial>;
+ formLabel?: Partial>;
+ radio?: Partial>;
+ checkbox?: Partial>;
+ switch?: Partial>;
+ toggle?: Partial>;
+
+ // Form field & Typography slots
+ fieldFormControl?: Partial>;
+ fieldTypography?: Partial>;
+ descTypography?: Partial>;
+ helpFormHelperText?: Partial>;
+ fieldErrorList?: Partial>;
+ fieldErrorListItem?: Partial>;
+ fieldErrorFormHelperText?: Partial>;
+
+ // Title slots
+ titleBox?: Partial>;
+ titleTypography?: Partial>;
+ titleDivider?: Partial>;
+ titleGridContainer?: Partial>;
+ titleGridItem?: Partial>;
+ titleOptionalDataGridItem?: Partial>;
+
+ // Error list slots
+ errorAlert?: Partial>;
+ errorListRoot?: Partial>;
+ errorListCard?: Partial>;
+ errorListHeading?: Partial>;
+ errorList?: Partial>;
+ errorListItem?: Partial>;
+ errorListItemText?: Partial>;
+
+ // Button slots
+ submitButton?: Partial>;
+ submitBox?: Partial>;
+ addButton?: Partial>;
+ removeButton?: Partial>;
+ moveUpButton?: Partial>;
+ moveDownButton?: Partial>;
+
+ // Input & Select slots
+ textField?: Partial>;
+ input?: Partial>;
+ htmlInput?: InputBaseComponentProps;
+ inputLabel?: Partial>;
+ select?: Partial>;
+ menuItem?: Partial>;
+
+ // Range widget slots
+ rangeBox?: Partial>;
+ slider?: Partial>;
+ rangeSlider?: Partial>;
+ rangeTypography?: Partial>;
+
+ // Array slots
+ arrayBox?: Partial>;
+ arrayPaper?: Partial>;
+ arrayToolbar?: Partial>;
+ arrayAddButton?: Partial>;
+ arrayAddButtonBox?: Partial>;
+ arrayAddButtonGridContainer?: Partial>;
+ arrayAddButtonGridItem?: Partial>;
+ arrayItemGridContainer?: Partial>;
+ arrayItemGridItem?: Partial>;
+ arrayItemInnerBox?: Partial>;
+ arrayItemOuterBox?: Partial>;
+ arrayItemPaper?: Partial>;
+ arrayItemToolbarGrid?: Partial>;
+
+ // Object field & Wrapper slots
+ objectBox?: Partial>;
+ objectGrid?: Partial>;
+ objectGridContainer?: Partial>;
+ objectGridItem?: Partial>;
+ objectAddButtonGridContainer?: Partial>;
+ objectAddButtonGridItem?: Partial>;
+ wrapBox?: Partial>;
+ wrapGridContainer?: Partial>;
+ wrapKeyGridItem?: Partial>;
+ wrapChildrenGridItem?: Partial>;
+ wrapRemoveButtonGridItem?: Partial>;
+ wrapHelpIcon?: Partial>;
+}
+
+/**
+ * Top-level MUI customization options read from `uiSchema.ui:options.mui`.
+ * Known fields are explicitly typed; additional MUI props can be passed as unknown.
+ */
+export interface SistentMuiOptions {
+ sx?: SxProps;
+ className?: string;
+ rjsfSlotProps?: SistentMuiSlotProps;
+ [key: string]: unknown;
+}
+
+/**
+ * Extract props meant for MUI/Sistent components from the `options` field of the `uiSchema`.
+ */
+export function getMuiProps<
+ T = unknown,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = unknown
+>(
+ options?: UIOptionsType,
+ propsToFilter?: string[]
+): SistentMuiOptions {
+ const muiProps = (options?.mui as SistentMuiOptions) || {};
+ if (propsToFilter) {
+ return Object.keys(muiProps)
+ .filter((key) => propsToFilter.includes(key))
+ .reduce((obj: Record, key) => {
+ obj[key] = muiProps[key];
+ return obj;
+ }, {}) as SistentMuiOptions;
+ }
+ return muiProps;
+}
+
+/**
+ * Merges base sx props with any custom sx specified in uiOptions.mui.
+ */
+export function computeSxProps(
+ sxProps: SxProps,
+ muiProps?: SistentMuiOptions
+): SxProps {
+ if (!muiProps) {
+ return sxProps;
+ }
+ const sxIsObject = sxProps !== null && typeof sxProps === 'object' && !Array.isArray(sxProps);
+ if (Array.isArray(muiProps?.sx)) {
+ return sxIsObject
+ ? [sxProps, ...muiProps.sx] as unknown as SxProps
+ : [...(Array.isArray(sxProps) ? sxProps : [sxProps]), ...muiProps.sx] as unknown as SxProps;
+ }
+ if (typeof muiProps?.sx === 'function') {
+ return sxIsObject
+ ? [sxProps, muiProps.sx] as unknown as SxProps
+ : [...(Array.isArray(sxProps) ? sxProps : [sxProps]), muiProps.sx] as unknown as SxProps;
+ }
+ if (muiProps?.sx) {
+ return sxIsObject
+ ? { ...(sxProps as object), ...(muiProps.sx as object) } as unknown as SxProps
+ : [...(Array.isArray(sxProps) ? sxProps : [sxProps]), muiProps.sx] as unknown as SxProps;
+ }
+ return sxProps;
+}
diff --git a/src/custom/RJSFFormWrapper/theme/widgets/CheckboxWidget.tsx b/src/custom/RJSFFormWrapper/theme/widgets/CheckboxWidget.tsx
new file mode 100644
index 000000000..aae44ad0c
--- /dev/null
+++ b/src/custom/RJSFFormWrapper/theme/widgets/CheckboxWidget.tsx
@@ -0,0 +1,94 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {
+ type FormContextType,
+ type RJSFSchema,
+ type StrictRJSFSchema,
+ type WidgetProps,
+ ariaDescribedByIds,
+ descriptionId,
+ getTemplate,
+ labelValue,
+ schemaRequiresTrueValue
+} from '@rjsf/utils';
+import React, { type ChangeEvent } from 'react';
+import { Checkbox } from '../../../../base/Checkbox';
+import { FormControlLabel } from '../../../../base/FormControlLabel';
+import { getMuiProps } from '../util';
+
+/**
+ * Sistent's `CheckboxWidget` renders boolean properties using Sistent Checkbox.
+ */
+export default function CheckboxWidget<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>(props: WidgetProps): React.JSX.Element {
+ const {
+ schema,
+ id,
+ htmlName,
+ value,
+ disabled,
+ readonly,
+ label = '',
+ hideLabel,
+ autofocus,
+ onChange,
+ onBlur,
+ onFocus,
+ registry,
+ options,
+ uiSchema
+ } = props;
+
+ const DescriptionFieldTemplate = getTemplate<'DescriptionFieldTemplate', T, S, F>(
+ 'DescriptionFieldTemplate',
+ registry,
+ options
+ );
+
+ const required = schemaRequiresTrueValue(schema);
+ const _onChange = (_: ChangeEvent, checked: boolean): void => {
+ onChange(checked);
+ };
+ const _onBlur = (): void => onBlur(id, value);
+ const _onFocus = (): void => onFocus(id, value);
+ const description = options.description ?? schema.description;
+ const { rjsfSlotProps: muiSlotProps, ...otherMuiProps } = getMuiProps(options);
+
+ return (
+ <>
+ {description && (
+
+ )}
+
+ }
+ label={labelValue(label, hideLabel, false)}
+ />
+ >
+ );
+}
diff --git a/src/custom/RJSFFormWrapper/theme/widgets/CheckboxesWidget.tsx b/src/custom/RJSFFormWrapper/theme/widgets/CheckboxesWidget.tsx
new file mode 100644
index 000000000..ef9868bd6
--- /dev/null
+++ b/src/custom/RJSFFormWrapper/theme/widgets/CheckboxesWidget.tsx
@@ -0,0 +1,116 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {
+ type FormContextType,
+ type RJSFSchema,
+ type StrictRJSFSchema,
+ type WidgetProps,
+ ariaDescribedByIds,
+ enumOptionsDeselectValue,
+ enumOptionsIsSelected,
+ enumOptionsSelectValue,
+ labelValue,
+ optionId
+} from '@rjsf/utils';
+import React, { type ChangeEvent } from 'react';
+import { Checkbox } from '../../../../base/Checkbox';
+import { FormControlLabel } from '../../../../base/FormControlLabel';
+import { FormGroup } from '../../../../base/FormGroup';
+import { FormLabel } from '../../../../base/FormLabel';
+import { getMuiProps } from '../util';
+
+/**
+ * Sistent's `CheckboxesWidget` renders checkbox groups for enum arrays.
+ */
+export default function CheckboxesWidget<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>(props: WidgetProps): React.JSX.Element {
+ const {
+ label,
+ hideLabel,
+ id,
+ htmlName,
+ disabled,
+ options,
+ value,
+ autofocus,
+ readonly,
+ required,
+ onChange,
+ onBlur,
+ onFocus
+ } = props;
+
+ const { enumOptions, enumDisabled, inline } = options;
+ const checkboxesValues = Array.isArray(value) ? value : value !== undefined ? [value] : [];
+
+ const _onChange =
+ (index: number) =>
+ ({ target: { checked } }: ChangeEvent): void => {
+ if (checked) {
+ onChange(enumOptionsSelectValue(index, checkboxesValues, enumOptions));
+ } else {
+ onChange(enumOptionsDeselectValue(index, checkboxesValues, enumOptions));
+ }
+ };
+
+ const _onBlur = (): void => {
+ onBlur(id, checkboxesValues);
+ };
+
+ const _onFocus = (): void => {
+ onFocus(id, checkboxesValues);
+ };
+
+ const { rjsfSlotProps: muiSlotProps, ...otherMuiProps } = getMuiProps(options);
+
+ return (
+ <>
+ {labelValue(
+
+ {label || undefined}
+ ,
+ hideLabel
+ )}
+
+ {Array.isArray(enumOptions) &&
+ enumOptions.map((option, index) => {
+ const checked = enumOptionsIsSelected(option.value, checkboxesValues);
+ const itemDisabled =
+ Array.isArray(enumDisabled) && enumDisabled.indexOf(option.value) !== -1;
+ const checkbox = (
+
+ );
+ return (
+
+ );
+ })}
+
+ >
+ );
+}
diff --git a/src/custom/RJSFFormWrapper/theme/widgets/FileWidget.tsx b/src/custom/RJSFFormWrapper/theme/widgets/FileWidget.tsx
new file mode 100644
index 000000000..db5a83827
--- /dev/null
+++ b/src/custom/RJSFFormWrapper/theme/widgets/FileWidget.tsx
@@ -0,0 +1,165 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {
+ type FileInfoType,
+ type FormContextType,
+ type RJSFSchema,
+ type StrictRJSFSchema,
+ TranslatableString,
+ type WidgetProps,
+ getTemplate,
+ useFileWidgetProps
+} from '@rjsf/utils';
+import React, { type ChangeEvent } from 'react';
+import { Box } from '../../../../base/Box';
+import { Link } from '../../../../base/Link';
+import { List } from '../../../../base/List';
+import { ListItem } from '../../../../base/ListItem';
+import { Typography } from '../../../../base/Typography';
+import { useTheme } from '../../../../theme';
+
+function FileInfoPreview<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>({
+ fileInfo,
+ registry
+}: {
+ fileInfo: FileInfoType;
+ registry: WidgetProps['registry'];
+}): React.JSX.Element | null {
+ const { translateString } = registry;
+ const { dataURL, type, name } = fileInfo;
+ if (!dataURL) {
+ return null;
+ }
+ if (type && (type.startsWith('image/') || ['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml', 'image/gif', 'image/avif'].includes(type))) {
+ return (
+
+ );
+ }
+ return (
+
+ {translateString(TranslatableString.PreviewLabel)}
+
+ );
+}
+
+function FilesInfo<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>({
+ filesInfo,
+ registry,
+ preview,
+ onRemove,
+ options,
+ disabled
+}: {
+ filesInfo: FileInfoType[];
+ registry: WidgetProps['registry'];
+ preview?: boolean;
+ onRemove: (index: number) => void;
+ options: WidgetProps['options'];
+ disabled?: boolean;
+}): React.JSX.Element | null {
+ const theme = useTheme();
+ if (filesInfo.length === 0) {
+ return null;
+ }
+ const { RemoveButton } = getTemplate<'ButtonTemplates', T, S, F>(
+ 'ButtonTemplates',
+ registry,
+ options
+ );
+
+ return (
+
+ {filesInfo.map((fileInfo, key) => {
+ const { name, size, type } = fileInfo;
+ const handleRemove = (): void => {
+ if (!disabled) {
+ onRemove(key);
+ }
+ };
+ return (
+
+
+
+ {name}
+
+
+ ({type || 'unknown'}, {size ? `${(size / 1024).toFixed(1)} KB` : '0 KB'})
+
+ {preview && }
+
+
+
+ );
+ })}
+
+ );
+}
+
+/**
+ * Sistent's `FileWidget` renders file upload inputs with Sistent-styled file list info and previews.
+ */
+export default function FileWidget<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>(props: WidgetProps): React.JSX.Element {
+ const { disabled, readonly, required, multiple, onChange, value, options, registry } = props;
+ const { filesInfo, handleChange, handleRemove } = useFileWidgetProps(value, onChange, multiple);
+ const BaseInputTemplate = getTemplate<'BaseInputTemplate', T, S, F>(
+ 'BaseInputTemplate',
+ registry,
+ options
+ );
+
+ const handleOnChangeEvent = (event: ChangeEvent): void => {
+ if (event.target.files) {
+ void handleChange(event.target.files);
+ }
+ };
+
+ return (
+
+ 0 ? false : required}
+ onChangeOverride={handleOnChangeEvent}
+ value=""
+ accept={options.accept ? String(options.accept) : undefined}
+ />
+
+
+ );
+}
diff --git a/src/custom/RJSFFormWrapper/theme/widgets/RadioWidget.tsx b/src/custom/RJSFFormWrapper/theme/widgets/RadioWidget.tsx
new file mode 100644
index 000000000..35c6a5ac6
--- /dev/null
+++ b/src/custom/RJSFFormWrapper/theme/widgets/RadioWidget.tsx
@@ -0,0 +1,119 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {
+ type FormContextType,
+ type RJSFSchema,
+ type StrictRJSFSchema,
+ type WidgetProps,
+ ariaDescribedByIds,
+ enumOptionSelectedValue,
+ enumOptionValueDecoder,
+ enumOptionValueEncoder,
+ getOptionValueFormat,
+ labelValue,
+ optionId
+} from '@rjsf/utils';
+import React, { type ChangeEvent, type FocusEvent } from 'react';
+import { FormControlLabel } from '../../../../base/FormControlLabel';
+import { FormLabel } from '../../../../base/FormLabel';
+import { Radio } from '../../../../base/Radio';
+import { RadioGroup } from '../../../../base/RadioGroup';
+import { getMuiProps } from '../util';
+
+/**
+ * Sistent's `RadioWidget` renders single-choice radio option groups.
+ */
+export default function RadioWidget<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>(props: WidgetProps): React.JSX.Element {
+ const {
+ id,
+ htmlName,
+ options,
+ value,
+ required,
+ disabled,
+ readonly,
+ label,
+ hideLabel,
+ autofocus,
+ onChange,
+ onBlur,
+ onFocus
+ } = props;
+
+ const { enumOptions, enumDisabled, emptyValue } = options;
+ const optionValueFormat = getOptionValueFormat(options);
+
+ const _onChange = (_: ChangeEvent, val: string): void => {
+ onChange(enumOptionValueDecoder(val, enumOptions, optionValueFormat, emptyValue));
+ };
+
+ const _onBlur = ({ target }: FocusEvent): void => {
+ onBlur(
+ id,
+ enumOptionValueDecoder(target && target.value, enumOptions, optionValueFormat, emptyValue)
+ );
+ };
+
+ const _onFocus = ({ target }: FocusEvent): void => {
+ onFocus(
+ id,
+ enumOptionValueDecoder(target && target.value, enumOptions, optionValueFormat, emptyValue)
+ );
+ };
+
+ const row = options ? Boolean(options.inline) : false;
+ const selectValue = enumOptionSelectedValue(value, enumOptions, false, optionValueFormat, '');
+ const { rjsfSlotProps: muiSlotProps, ...otherMuiProps } = getMuiProps(options);
+
+ return (
+ <>
+ {labelValue(
+
+ {label || undefined}
+ ,
+ hideLabel
+ )}
+
+ {Array.isArray(enumOptions) &&
+ enumOptions.map((option, index) => {
+ const itemDisabled =
+ Array.isArray(enumDisabled) && enumDisabled.indexOf(option.value) !== -1;
+ return (
+
+ }
+ label={option.label}
+ value={enumOptionValueEncoder(option.value, index, optionValueFormat)}
+ key={index}
+ disabled={disabled || itemDisabled || readonly}
+ />
+ );
+ })}
+
+ >
+ );
+}
diff --git a/src/custom/RJSFFormWrapper/theme/widgets/RangeWidget.tsx b/src/custom/RJSFFormWrapper/theme/widgets/RangeWidget.tsx
new file mode 100644
index 000000000..0bc08fe36
--- /dev/null
+++ b/src/custom/RJSFFormWrapper/theme/widgets/RangeWidget.tsx
@@ -0,0 +1,101 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {
+ type FormContextType,
+ type RJSFSchema,
+ type StrictRJSFSchema,
+ type WidgetProps,
+ ariaDescribedByIds,
+ labelValue,
+ rangeSpec
+} from '@rjsf/utils';
+import React, { type FocusEvent } from 'react';
+import { Box } from '../../../../base/Box';
+import { FormLabel } from '../../../../base/FormLabel';
+import { Slider } from '../../../../base/Slider';
+import { Typography } from '../../../../base/Typography';
+import { useTheme } from '../../../../theme';
+import { computeSxProps, getMuiProps } from '../util';
+
+/**
+ * Sistent's `RangeWidget` renders numeric range sliders.
+ */
+export default function RangeWidget<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>(props: WidgetProps): React.JSX.Element {
+ const theme = useTheme();
+ const {
+ value,
+ readonly,
+ disabled,
+ onBlur,
+ onFocus,
+ options,
+ schema,
+ onChange,
+ required,
+ label,
+ hideLabel,
+ id
+ } = props;
+
+ const sliderProps = { value, id, ...rangeSpec(schema) };
+
+ const _onChange = (_: Event, val: number | number[]): void => {
+ onChange(val);
+ };
+
+ const _onBlur = ({ target }: FocusEvent): void => {
+ onBlur(id, target && target.value);
+ };
+
+ const _onFocus = ({ target }: FocusEvent): void => {
+ onFocus(id, target && target.value);
+ };
+
+ const { rjsfSlotProps: muiSlotProps, ...otherMuiProps } = getMuiProps(options);
+
+ return (
+ <>
+ {labelValue(
+
+ {label || undefined}
+ ,
+ hideLabel
+ )}
+
+
+
+ {value ?? sliderProps.min ?? 0}
+
+
+ >
+ );
+}
diff --git a/src/custom/RJSFFormWrapper/theme/widgets/SelectWidget.tsx b/src/custom/RJSFFormWrapper/theme/widgets/SelectWidget.tsx
new file mode 100644
index 000000000..0d2cfae49
--- /dev/null
+++ b/src/custom/RJSFFormWrapper/theme/widgets/SelectWidget.tsx
@@ -0,0 +1,171 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {
+ type FormContextType,
+ type RJSFSchema,
+ type StrictRJSFSchema,
+ type WidgetProps,
+ ariaDescribedByIds,
+ enumOptionSelectedValue,
+ enumOptionValueDecoder,
+ enumOptionValueEncoder,
+ getOptionValueFormat,
+ labelValue
+} from '@rjsf/utils';
+import React, { type ChangeEvent, type FocusEvent } from 'react';
+import { MenuItem } from '../../../../base/MenuItem';
+import { TextField, type TextFieldProps } from '../../../../base/TextField';
+import { getMuiProps } from '../util';
+
+/**
+ * Sistent's `SelectWidget` renders dropdown menus using Sistent Select / MenuItem.
+ */
+export default function SelectWidget<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>(props: WidgetProps): React.JSX.Element {
+ const {
+ schema,
+ id,
+ htmlName,
+ options,
+ label,
+ hideLabel,
+ required,
+ disabled,
+ placeholder,
+ readonly,
+ value,
+ multiple,
+ autofocus,
+ onChange,
+ onBlur,
+ onFocus,
+ rawErrors = []
+ } = props;
+
+ const { enumOptions, enumDisabled, emptyValue: optEmptyVal } = options;
+ const optionValueFormat = getOptionValueFormat(options);
+ const isMultiple = typeof multiple === 'undefined' ? false : Boolean(multiple);
+ const emptyValue = isMultiple ? [] : '';
+ const isEmpty =
+ typeof value === 'undefined' ||
+ (isMultiple && Array.isArray(value) && value.length < 1) ||
+ (!isMultiple && value === emptyValue);
+
+ const _onChange = ({ target: { value: nextVal } }: ChangeEvent): void => {
+ onChange(enumOptionValueDecoder(nextVal, enumOptions, optionValueFormat, optEmptyVal));
+ };
+
+ const _onBlur = ({ target }: FocusEvent): void => {
+ onBlur(
+ id,
+ enumOptionValueDecoder(target && target.value, enumOptions, optionValueFormat, optEmptyVal)
+ );
+ };
+
+ const _onFocus = ({ target }: FocusEvent): void => {
+ onFocus(
+ id,
+ enumOptionValueDecoder(target && target.value, enumOptions, optionValueFormat, optEmptyVal)
+ );
+ };
+
+ const { rjsfSlotProps: muiSlotProps, ...otherMuiProps } = getMuiProps(options);
+ const showPlaceholderOption = !isMultiple && schema.default === undefined;
+
+ /* eslint-disable @typescript-eslint/no-unused-vars */
+ const {
+ schema: _schema,
+ id: _id,
+ htmlName: _htmlName,
+ options: _options,
+ label: _label,
+ hideLabel: _hideLabel,
+ required: _required,
+ disabled: _disabled,
+ placeholder: _placeholder,
+ readonly: _readonly,
+ value: _value,
+ multiple: _multiple,
+ autofocus: _autofocus,
+ onChange: _onChange2,
+ onBlur: _onBlur2,
+ onFocus: _onFocus2,
+ rawErrors: _rawErrors,
+ // exclude RJSF-only props that must not reach TextField
+ name: _name,
+ hideError: _hideError,
+ errorSchema: _errorSchema,
+ uiSchema: _uiSchema,
+ registry: _registry,
+ InputLabelProps: _InputLabelProps,
+ SelectProps: _SelectProps,
+ formContext: _formContext,
+ color: _color,
+ ...textFieldProps
+ } = props;
+ /* eslint-enable @typescript-eslint/no-unused-vars */
+
+ const forwardedTextFieldProps: Partial> = textFieldProps;
+
+ return (
+ 0}
+ onChange={_onChange}
+ onBlur={_onBlur}
+ onFocus={_onFocus}
+ select
+ slotProps={{
+ ...muiSlotProps,
+ input: {
+ ...muiSlotProps?.input,
+ ...(readonly ? { readOnly: true } : undefined)
+ },
+ inputLabel: {
+ ...muiSlotProps?.inputLabel,
+ shrink: !isEmpty
+ },
+ select: {
+ ...muiSlotProps?.select,
+ multiple: isMultiple,
+ ...(readonly ? { readOnly: true } : undefined),
+ ...(hideLabel && label ? { 'aria-label': label } : undefined)
+ }
+ }}
+ aria-describedby={ariaDescribedByIds(id)}
+ >
+ {showPlaceholderOption && (
+
+ )}
+ {Array.isArray(enumOptions) &&
+ enumOptions.map(({ value: optVal, label: optLabel }, i) => {
+ const itemDisabled = Array.isArray(enumDisabled) && enumDisabled.indexOf(optVal) !== -1;
+ return (
+
+ );
+ })}
+
+ );
+}
diff --git a/src/custom/RJSFFormWrapper/theme/widgets/TextWidget.tsx b/src/custom/RJSFFormWrapper/theme/widgets/TextWidget.tsx
new file mode 100644
index 000000000..6ffce9855
--- /dev/null
+++ b/src/custom/RJSFFormWrapper/theme/widgets/TextWidget.tsx
@@ -0,0 +1,26 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {
+ type FormContextType,
+ type RJSFSchema,
+ type StrictRJSFSchema,
+ type WidgetProps,
+ getTemplate
+} from '@rjsf/utils';
+import React from 'react';
+
+/**
+ * Sistent's `TextWidget` delegates text input rendering to `BaseInputTemplate`.
+ */
+export default function TextWidget<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>(props: WidgetProps): React.JSX.Element {
+ const { options, registry } = props;
+ const BaseInputTemplate = getTemplate<'BaseInputTemplate', T, S, F>(
+ 'BaseInputTemplate',
+ registry,
+ options
+ );
+ return ;
+}
diff --git a/src/custom/RJSFFormWrapper/theme/widgets/TextareaWidget.tsx b/src/custom/RJSFFormWrapper/theme/widgets/TextareaWidget.tsx
new file mode 100644
index 000000000..ad2169c04
--- /dev/null
+++ b/src/custom/RJSFFormWrapper/theme/widgets/TextareaWidget.tsx
@@ -0,0 +1,32 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {
+ type FormContextType,
+ type RJSFSchema,
+ type StrictRJSFSchema,
+ type WidgetProps,
+ getTemplate
+} from '@rjsf/utils';
+import React from 'react';
+
+/**
+ * Sistent's `TextareaWidget` renders multiline text fields.
+ */
+export default function TextareaWidget<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>(props: WidgetProps): React.JSX.Element {
+ const { options, registry } = props;
+ const BaseInputTemplate = getTemplate<'BaseInputTemplate', T, S, F>(
+ 'BaseInputTemplate',
+ registry,
+ options
+ );
+
+ let rows: string | number = 4;
+ if (typeof options.rows === 'string' || typeof options.rows === 'number') {
+ rows = options.rows;
+ }
+
+ return ;
+}
diff --git a/src/custom/RJSFFormWrapper/theme/widgets/ToggleWidget.tsx b/src/custom/RJSFFormWrapper/theme/widgets/ToggleWidget.tsx
new file mode 100644
index 000000000..701d018a4
--- /dev/null
+++ b/src/custom/RJSFFormWrapper/theme/widgets/ToggleWidget.tsx
@@ -0,0 +1,95 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import {
+ type FormContextType,
+ type RJSFSchema,
+ type StrictRJSFSchema,
+ type WidgetProps,
+ ariaDescribedByIds,
+ descriptionId,
+ getTemplate,
+ labelValue,
+ schemaRequiresTrueValue
+} from '@rjsf/utils';
+import React, { type ChangeEvent } from 'react';
+import { FormControlLabel } from '../../../../base/FormControlLabel';
+import { Switch } from '../../../../base/Switch';
+import { getMuiProps } from '../util';
+
+/**
+ * Sistent's `ToggleWidget` (or `SwitchWidget`) renders boolean toggles using Sistent Switch.
+ */
+export default function ToggleWidget<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>(props: WidgetProps): React.JSX.Element {
+ const {
+ schema,
+ id,
+ htmlName,
+ value,
+ disabled,
+ readonly,
+ label = '',
+ hideLabel,
+ autofocus,
+ onChange,
+ onBlur,
+ onFocus,
+ registry,
+ options,
+ uiSchema
+ } = props;
+
+ const DescriptionFieldTemplate = getTemplate<'DescriptionFieldTemplate', T, S, F>(
+ 'DescriptionFieldTemplate',
+ registry,
+ options
+ );
+
+ const required = schemaRequiresTrueValue(schema);
+ const _onChange = (_: ChangeEvent, checked: boolean): void => {
+ onChange(checked);
+ };
+ const _onBlur = (): void => onBlur(id, value);
+ const _onFocus = (): void => onFocus(id, value);
+ const description = options.description ?? schema.description;
+ const { rjsfSlotProps: muiSlotProps, ...otherMuiProps } = getMuiProps(options);
+
+ return (
+ <>
+ {description && (
+
+ )}
+
+ }
+ label={labelValue(label, hideLabel, false)}
+ />
+ >
+ );
+}
+
+export const SwitchWidget = ToggleWidget;
diff --git a/src/custom/RJSFFormWrapper/theme/widgets/index.ts b/src/custom/RJSFFormWrapper/theme/widgets/index.ts
new file mode 100644
index 000000000..bc02caac0
--- /dev/null
+++ b/src/custom/RJSFFormWrapper/theme/widgets/index.ts
@@ -0,0 +1,52 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import type {
+ FormContextType,
+ RJSFSchema,
+ RegistryWidgetsType,
+ StrictRJSFSchema
+} from '@rjsf/utils';
+import CheckboxWidget from './CheckboxWidget';
+import CheckboxesWidget from './CheckboxesWidget';
+import FileWidget from './FileWidget';
+import RadioWidget from './RadioWidget';
+import RangeWidget from './RangeWidget';
+import SelectWidget from './SelectWidget';
+import TextWidget from './TextWidget';
+import TextareaWidget from './TextareaWidget';
+import ToggleWidget, { SwitchWidget } from './ToggleWidget';
+
+export function generateWidgets<
+ T = any,
+ S extends StrictRJSFSchema = RJSFSchema,
+ F extends FormContextType = any
+>(): RegistryWidgetsType {
+ return {
+ CheckboxWidget,
+ CheckboxesWidget,
+ FileWidget,
+ RadioWidget,
+ RangeWidget,
+ SelectWidget,
+ TextWidget,
+ TextareaWidget,
+ ToggleWidget,
+ SwitchWidget,
+ switch: SwitchWidget,
+ toggle: ToggleWidget
+ };
+}
+
+export {
+ CheckboxWidget,
+ CheckboxesWidget,
+ FileWidget,
+ RadioWidget,
+ RangeWidget,
+ SelectWidget,
+ SwitchWidget,
+ TextWidget,
+ TextareaWidget,
+ ToggleWidget
+};
+
+export default generateWidgets();
diff --git a/src/index.tsx b/src/index.tsx
index 24a87e1a1..9f990c1ae 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -164,3 +164,21 @@ export {
type Team as TeamPickerRecord,
type TeamSearchFieldProps
} from './custom/DashboardWidgets/GettingStartedWidget/TeamSearchField';
+
+// Explicit root re-exports for RJSFFormWrapper, RJSFFormModal, and Sistent RJSF theme
+// to ensure rollup-plugin-dts includes their types in dist/index.d.ts.
+export {
+ RJSFFormModal,
+ RJSFFormWrapper,
+ hideRootObjectTitle,
+ sistentTheme,
+ sistentTemplates,
+ sistentWidgets,
+ generateTheme,
+ generateTemplates,
+ generateWidgets,
+ type RJSFFormModalProps,
+ type RJSFFormWrapperProps,
+ type RJSFValidationError
+} from './custom/RJSFFormWrapper';
+