@ceed/cds 1.37.0 → 1.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,12 @@ export interface DialogFrameProps {
4
4
  children: React.ReactNode;
5
5
  actions: React.ReactNode;
6
6
  fullscreen?: boolean;
7
+ /**
8
+ * When provided, the dialog root renders as a `<form>` and this handler runs on submit.
9
+ * This enables native submit-on-Enter and lets a `type="submit"` button in `actions`
10
+ * submit the fields in the content area. When omitted, the root stays a `<div>`.
11
+ */
12
+ onSubmit?: React.FormEventHandler<HTMLFormElement>;
7
13
  }
8
14
  declare const DialogFrame: React.ForwardRefExoticComponent<Omit<Omit<Pick<{
9
15
  children?: React.ReactNode;
@@ -18,5 +24,5 @@ declare const DialogFrame: React.ForwardRefExoticComponent<Omit<Omit<Pick<{
18
24
  variant?: import("@mui/types").OverridableStringUnion<import("@mui/joy").VariantProp, import("@mui/joy").ModalDialogPropsVariantOverrides> | undefined;
19
25
  } & import("@mui/joy").ModalDialogSlotsAndSlotProps & Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
20
26
  ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
21
- }, "children" | "layout" | "color" | "maxWidth" | "minWidth" | "variant" | "sx" | "size" | "invertedColors" | "orientation" | keyof import("@mui/joy").ModalDialogSlotsAndSlotProps> & import("@mui/system").MUIStyledCommonProps<import("@mui/joy").Theme>, "style" | "title" | "children" | "onAnimationStart" | "onDragStart" | "onDragEnd" | "onDrag" | "layout" | "color" | "content" | "maxWidth" | "minWidth" | "translate" | "ref" | "slot" | "key" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "autoFocus" | "className" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "hidden" | "id" | "lang" | "nonce" | "spellCheck" | "tabIndex" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "prefix" | "property" | "rel" | "resource" | "rev" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-braillelabel" | "aria-brailleroledescription" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colindextext" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-description" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowindextext" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onResize" | "onResizeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDragCapture" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "variant" | "size" | "invertedColors" | "orientation" | keyof import("@mui/system").MUIStyledCommonProps<import("@mui/joy").Theme> | keyof import("@mui/joy").ModalDialogSlotsAndSlotProps> & import("@mui/system").MUIStyledCommonProps<import("@mui/joy").Theme>, "title" | "children"> & DialogFrameProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
27
+ }, "children" | "layout" | "color" | "maxWidth" | "minWidth" | "variant" | "sx" | "size" | "invertedColors" | "orientation" | keyof import("@mui/joy").ModalDialogSlotsAndSlotProps> & import("@mui/system").MUIStyledCommonProps<import("@mui/joy").Theme>, "style" | "title" | "children" | "onAnimationStart" | "onDragStart" | "onDragEnd" | "onDrag" | "layout" | "color" | "content" | "maxWidth" | "minWidth" | "translate" | "ref" | "slot" | "key" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "autoFocus" | "className" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "hidden" | "id" | "lang" | "nonce" | "spellCheck" | "tabIndex" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "prefix" | "property" | "rel" | "resource" | "rev" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-braillelabel" | "aria-brailleroledescription" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colindextext" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-description" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowindextext" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onResize" | "onResizeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDragCapture" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "variant" | "size" | "invertedColors" | "orientation" | keyof import("@mui/system").MUIStyledCommonProps<import("@mui/joy").Theme> | keyof import("@mui/joy").ModalDialogSlotsAndSlotProps> & import("@mui/system").MUIStyledCommonProps<import("@mui/joy").Theme>, "title" | "children" | "onSubmit"> & DialogFrameProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
22
28
  export { DialogFrame };
@@ -208,41 +208,45 @@ function DeleteConfirmation({ itemName, open, onClose, onDelete }) {
208
208
 
209
209
  ### Form Dialog
210
210
 
211
+ > ✅ **Best Practice** ✅
212
+ >
213
+ > For any dialog that collects input and submits, **always pass `onSubmit`** to DialogFrame — never wire the confirm button with `onClick` alone. When `onSubmit` is provided, DialogFrame renders its root as a `<form>`, so the fields in the content area and the `type="submit"` button in `actions` belong to the same form. This is what enables **submit-on-Enter** (pressing Enter inside an input submits) plus native constraint validation (`required`, `type="email"`, etc.).
214
+ >
215
+ > An `onClick` handler on the action button cannot do this: the button lives in `actions`, a sibling of the content area, so without a wrapping `<form>` there is no implicit submit and Enter does nothing.
216
+
211
217
  ```tsx
212
218
  function QuickAddDialog({ open, onClose, onSubmit }) {
213
- const [name, setName] = useState('');
214
- const [email, setEmail] = useState('');
215
-
216
- const handleSubmit = () => {
217
- onSubmit({ name, email });
218
- setName('');
219
- setEmail('');
219
+ const handleSubmit = (event) => {
220
+ event.preventDefault();
221
+ const formData = new FormData(event.currentTarget);
222
+ onSubmit({ name: formData.get('name'), email: formData.get('email') });
220
223
  onClose();
221
224
  };
222
225
 
223
226
  return (
224
227
  <Modal open={open} onClose={onClose}>
228
+ {/* onSubmit makes the root a <form>; Enter in any field submits */}
225
229
  <DialogFrame
226
230
  title="Add New User"
231
+ onSubmit={handleSubmit}
227
232
  actions={
228
233
  <>
229
234
  <Button variant="plain" color="neutral" onClick={onClose}>
230
235
  Cancel
231
236
  </Button>
232
- <Button onClick={handleSubmit} disabled={!name || !email}>
233
- Add User
234
- </Button>
237
+ {/* type="submit" ties this button to the DialogFrame form */}
238
+ <Button type="submit">Add User</Button>
235
239
  </>
236
240
  }
237
241
  >
238
242
  <Stack gap={2}>
239
243
  <FormControl>
240
244
  <FormLabel>Name</FormLabel>
241
- <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="Enter name" autoFocus />
245
+ <Input name="name" placeholder="Enter name" autoFocus required />
242
246
  </FormControl>
243
247
  <FormControl>
244
248
  <FormLabel>Email</FormLabel>
245
- <Input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Enter email" />
249
+ <Input type="email" name="email" placeholder="Enter email" required />
246
250
  </FormControl>
247
251
  </Stack>
248
252
  </DialogFrame>
@@ -402,13 +406,14 @@ function SelectOptionDialog({ open, onClose, options, onSelect }) {
402
406
 
403
407
  ### Key Props
404
408
 
405
- | Prop | Type | Default | Description |
406
- | ------------ | -------------------------------- | ------- | -------------------------------------- |
407
- | `title` | `string` | - | Dialog title displayed in the header |
408
- | `children` | `ReactNode` | - | Dialog body content |
409
- | `actions` | `ReactNode` | - | Action buttons displayed in the footer |
410
- | `fullscreen` | `boolean` | `false` | Enable fullscreen mode |
411
- | `onKeyDown` | `(event: KeyboardEvent) => void` | - | Keyboard event handler |
409
+ | Prop | Type | Default | Description |
410
+ | ------------ | --------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
411
+ | `title` | `string` | - | Dialog title displayed in the header |
412
+ | `children` | `ReactNode` | - | Dialog body content |
413
+ | `actions` | `ReactNode` | - | Action buttons displayed in the footer |
414
+ | `fullscreen` | `boolean` | `false` | Enable fullscreen mode |
415
+ | `onSubmit` | `(event: FormEvent<HTMLFormElement>) => void` | - | When provided, the dialog root renders as a `<form>`, enabling submit-on-Enter and a `type="submit"` action button. Omit it and the root stays a `<div>` |
416
+ | `onKeyDown` | `(event: KeyboardEvent) => void` | - | Keyboard event handler |
412
417
 
413
418
  ### Structure
414
419
 
@@ -576,6 +581,25 @@ DialogFrame inherits accessibility features from Modal:
576
581
  />
577
582
  ```
578
583
 
584
+ 5. **Use `onSubmit` for input dialogs**: For any dialog that collects values and submits, pass `onSubmit` and mark the confirm button `type="submit"`. This renders the root as a `<form>`, giving you submit-on-Enter and native validation for free.
585
+
586
+ ```tsx
587
+ // ✅ Good: onSubmit + type="submit" — Enter submits, `required` is enforced
588
+ <DialogFrame
589
+ onSubmit={handleSubmit}
590
+ actions={
591
+ <>
592
+ <Button variant="plain" color="neutral" onClick={onClose}>
593
+ Cancel
594
+ </Button>
595
+ <Button type="submit">Save</Button>
596
+ </>
597
+ }
598
+ >
599
+ <Input name="email" required />
600
+ </DialogFrame>
601
+ ```
602
+
579
603
  ### ❌ Don't
580
604
 
581
605
  1. **Don't use for notifications**: Use Toast instead
@@ -611,6 +635,20 @@ showToast({ message: 'Item saved!' });
611
635
 
612
636
  4. **Don't block critical workflows**: Important features should be accessible
613
637
 
638
+ 5. **Don't submit input dialogs with `onClick` only**: Pressing Enter in a field won't submit, because the button in `actions` sits outside any form.
639
+
640
+ ```tsx
641
+ // ❌ Bad: onClick-only — Enter in the input does nothing
642
+ <DialogFrame actions={<Button onClick={handleSubmit}>Save</Button>}>
643
+ <Input value={value} onChange={...} />
644
+ </DialogFrame>
645
+
646
+ // ✅ Good: onSubmit + type="submit" — Enter submits
647
+ <DialogFrame onSubmit={handleSubmit} actions={<Button type="submit">Save</Button>}>
648
+ <Input name="value" />
649
+ </DialogFrame>
650
+ ```
651
+
614
652
  ## Performance Considerations
615
653
 
616
654
  ### Lazy Load Dialog Content