@dynamic-framework/ui-react 2.3.1 → 2.5.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.
package/dist/index.js CHANGED
@@ -93,6 +93,12 @@ function useDisableBodyScrollEffect(disable) {
93
93
  }, [disable]);
94
94
  }
95
95
 
96
+ /**
97
+ * @internal
98
+ * Creates a DOM `<div>` portal mount point and appends it to `document.body`.
99
+ * This hook is used exclusively by `DPortalContextProvider` and is **not** part
100
+ * of the public API. Use `useDPortalContext` instead.
101
+ */
96
102
  function usePortal(portalName) {
97
103
  const [hasPortal, setHasPortal] = React.useState(false);
98
104
  React.useEffect(() => {
@@ -159,20 +165,22 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
159
165
  const { created } = usePortal(portalName);
160
166
  const [stack, { push, pop, isEmpty }] = useStackState([]);
161
167
  useDisableBodyScrollEffect(Boolean(stack.length));
162
- const openPortal = React.useCallback((name, payload) => {
168
+ const openPortal = React.useCallback(
169
+ // eslint-disable-next-line prefer-arrow-callback
170
+ function openPortalImpl(name, payload) {
163
171
  var _a;
164
172
  if (!availablePortals) {
165
- throw new Error(`there is no component for portal ${name.toString()}`);
173
+ throw new Error('openPortal was called but DContextProvider has no availablePortals configured. '
174
+ + 'Pass an availablePortals map to DContextProvider.');
166
175
  }
167
176
  const Component = availablePortals[name];
168
177
  if (!Component) {
169
- throw new Error(`there is no component for portal ${name.toString()}`);
178
+ throw new Error(`No component registered for portal "${String(name)}". `
179
+ + `Ensure "${String(name)}" has an entry in the availablePortals map on DContextProvider.`);
170
180
  }
171
- push({
172
- name,
173
- Component,
174
- payload,
175
- });
181
+ // K is a specific member of keyof T & string so the object satisfies
182
+ // InternalStackItem<T>, but TS can't verify generic-over-union assignability.
183
+ push({ name, Component, payload });
176
184
  (_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.blur();
177
185
  }, [availablePortals, push]);
178
186
  const closePortal = React.useCallback(() => {
@@ -181,11 +189,12 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
181
189
  }
182
190
  pop();
183
191
  }, [isEmpty, pop]);
192
+ const publicStack = React.useMemo(() => stack.map(({ name, payload }) => ({ name, payload })), [stack]);
184
193
  const value = React.useMemo(() => ({
185
- stack,
194
+ stack: publicStack,
186
195
  openPortal,
187
196
  closePortal,
188
- }), [stack, openPortal, closePortal]);
197
+ }), [publicStack, openPortal, closePortal]);
189
198
  const handleClose = React.useCallback((target) => {
190
199
  if (!(target instanceof HTMLDivElement)) {
191
200
  return;
@@ -241,10 +250,31 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
241
250
  jsxRuntime.jsx(Component, { name: name, payload: payload }, name),
242
251
  ]) }) }), document.getElementById(portalName))] }));
243
252
  }
253
+ /**
254
+ * Hook to open/close registered portals (modals, offcanvas, etc.).
255
+ *
256
+ * **Prerequisite**: must be called inside a `DContextProvider` configured with
257
+ * `portalName` and `availablePortals`. `DContextProvider` mounts
258
+ * `DPortalContextProvider` internally — consumers never use
259
+ * `DPortalContextProvider` directly.
260
+ *
261
+ * @template T - Map of portal name → payload shape (e.g. `ModalPayloads`).
262
+ * Typing this generic gives you autocomplete on `openPortal` arguments.
263
+ * @returns `{ openPortal, closePortal, stack }` from the nearest portal context.
264
+ * @throws If called outside of `DContextProvider` / `DPortalContextProvider`.
265
+ *
266
+ * @requires DContextProvider
267
+ *
268
+ * @example
269
+ * ```tsx
270
+ * const { openPortal, closePortal } = useDPortalContext<ModalPayloads>();
271
+ * openPortal('confirm', { message: 'Are you sure?' });
272
+ * ```
273
+ */
244
274
  function useDPortalContext() {
245
275
  const context = React.useContext(DPortalContext);
246
276
  if (context === undefined) {
247
- throw new Error('usePortalContext was used outside of PortalContextProvider');
277
+ throw new Error('useDPortalContext was used outside of DPortalContextProvider');
248
278
  }
249
279
  return context;
250
280
  }
@@ -300,10 +330,17 @@ const DEFAULT_STATE = {
300
330
  xxl: '',
301
331
  },
302
332
  setContext: () => { },
303
- portalName: 'd-portal',
304
333
  };
305
334
  const DContext = React.createContext(DEFAULT_STATE);
306
- function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAULT_STATE.currency, icon = DEFAULT_STATE.icon, iconMap = DEFAULT_STATE.iconMap, portalName = DEFAULT_STATE.portalName, availablePortals, children, }) {
335
+ /**
336
+ * Root context provider for Dynamic UI. Wrap your application with this
337
+ * component to configure icons, currency, language, and portal settings
338
+ * for all descendant Dynamic UI components.
339
+ *
340
+ * @template T - Map of portal name → payload shape (e.g. `{ modal: { title: string } }`).
341
+ * Pass it once at the top level: `<DContextProvider<MyPortals> ...>`.
342
+ */
343
+ function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAULT_STATE.currency, icon = DEFAULT_STATE.icon, iconMap = DEFAULT_STATE.iconMap, portalName = 'd-portal', availablePortals, children, }) {
307
344
  const [internalContext, setInternalContext,] = React.useState({
308
345
  language,
309
346
  currency,
@@ -327,12 +364,14 @@ function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAUL
327
364
  const value = React.useMemo(() => (Object.assign(Object.assign({}, internalContext), { setContext })), [internalContext, setContext]);
328
365
  return (jsxRuntime.jsx(DContext.Provider, { value: value, children: jsxRuntime.jsx(DPortalContextProvider, { portalName: portalName, availablePortals: availablePortals, children: children }) }));
329
366
  }
367
+ /**
368
+ * Returns the Dynamic UI context value set by `DContextProvider`.
369
+ * Falls back to the library's built-in defaults when no `DContextProvider`
370
+ * is present in the tree — wrap your application with `DContextProvider`
371
+ * to customise icons, currency, language, and portal settings.
372
+ */
330
373
  function useDContext() {
331
- const context = React.useContext(DContext);
332
- if (context === undefined) {
333
- throw new Error('useDContext was used outside of DContextProvider');
334
- }
335
- return context;
374
+ return React.useContext(DContext);
336
375
  }
337
376
 
338
377
  function subscribeToMediaQuery(query, callback) {
@@ -2399,12 +2438,34 @@ var DToast = Object.assign(DToast$1, {
2399
2438
  Body: DToastBody,
2400
2439
  });
2401
2440
 
2402
- function DToastContainer({ containerClassName, position = 'bottom-center', reverseOrder = false, containerStyle, toastOptions, gutter, }) {
2441
+ function DToastContainer({ containerClassName, position = 'bottom-center', reverseOrder = false, containerStyle, toastOptions, gutter = 8, }) {
2403
2442
  return (jsxRuntime.jsx(reactHotToast.Toaster, { containerClassName: containerClassName, position: position, reverseOrder: reverseOrder, containerStyle: containerStyle, gutter: gutter, toastOptions: toastOptions }));
2404
2443
  }
2405
2444
 
2445
+ /**
2446
+ * Hook that provides a `toast` function to dispatch DToast notifications.
2447
+ *
2448
+ * Prerequisites:
2449
+ * - The calling component must be inside `DContextProvider` (provides icon context).
2450
+ * - `DToastContainer` must be present somewhere in the component tree as a render target.
2451
+ *
2452
+ * @returns {{ toast }} Object containing a `toast` dispatcher function.
2453
+ * @requires DContextProvider
2454
+ * @requires DToastContainer
2455
+ * @example
2456
+ * const { toast } = useDToast();
2457
+ * toast({ title: 'Saved', color: 'success' });
2458
+ */
2406
2459
  function useDToast() {
2407
2460
  const { iconMap: { xLg, }, } = useDContext();
2461
+ /**
2462
+ * Dispatches a toast notification rendered as a `DToast` component.
2463
+ * When `data` is a `ToastData` object, the default DToast layout is used.
2464
+ * When `data` is a render function, full custom content is rendered instead.
2465
+ * Returns the toast ID, which can be passed to `reactHotToast.dismiss(id)`.
2466
+ * @param data - Toast content as `ToastData` or a render function for custom layouts.
2467
+ * @param toastProps - Optional per-toast overrides: id, duration, position.
2468
+ */
2408
2469
  const toast = React.useCallback((data, toastProps) => {
2409
2470
  if (typeof data === 'function') {
2410
2471
  return reactHotToast.toast.custom(data, toastProps);
@@ -2571,11 +2632,14 @@ function DCreditCard({ brand = 'visa', name, number, holderText = 'Card Holder',
2571
2632
  }
2572
2633
 
2573
2634
  const getItemClass = (action) => {
2574
- const base = `dropdown-item d-flex align-items-center
2575
- ${action.color ? `dropdown-item-${action.color}` : ''} ${action.disabled ? 'disabled' : ''}`;
2635
+ const base = classNames({
2636
+ 'dropdown-item d-flex align-items-center': true,
2637
+ [`dropdown-item-${action.color}`]: !!action.color,
2638
+ disabled: action.disabled,
2639
+ });
2576
2640
  return base;
2577
2641
  };
2578
- function DDropdown({ actions, dropdownToggle, className, }) {
2642
+ function DDropdown({ actions, dropdownToggle, className, classNameMenu, }) {
2579
2643
  const [open, setOpen] = React.useState(false);
2580
2644
  const dropdownRef = React.useRef(null);
2581
2645
  const [position, setPosition] = React.useState('down'); // 🆕
@@ -2617,14 +2681,14 @@ function DDropdown({ actions, dropdownToggle, className, }) {
2617
2681
  else {
2618
2682
  ToggleElement = (jsxRuntime.jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: () => setOpen(!open), icon: "MoreVertical" }));
2619
2683
  }
2620
- return (jsxRuntime.jsxs("div", { className: `dropdown position-relative drop-${position} ${className}`, ref: dropdownRef, children: [ToggleElement, jsxRuntime.jsx("ul", { style: {
2684
+ return (jsxRuntime.jsxs("div", { className: classNames(`dropdown drop-${position}`, className), ref: dropdownRef, children: [ToggleElement, jsxRuntime.jsx("ul", { style: {
2621
2685
  position: 'absolute',
2622
2686
  top: position === 'up' ? 'auto' : '100%',
2623
2687
  bottom: position === 'up' ? '100%' : 'auto',
2624
2688
  left: position === 'start' ? 'auto' : 0,
2625
2689
  right: position === 'start' ? '0' : 'auto',
2626
2690
  transform: 'translateY(4px)',
2627
- }, className: `dropdown-menu p-2 ${open ? 'show' : ''}`, children: actions.map((action, index) => {
2691
+ }, className: classNames('dropdown-menu p-2', { show: open }, classNameMenu), children: actions.map((action, index) => {
2628
2692
  if (action.isDivider) {
2629
2693
  return (jsxRuntime.jsx("hr", { className: "dropdown-divider" }, index));
2630
2694
  }
@@ -2975,7 +3039,6 @@ exports.useMediaBreakpointUpXl = useMediaBreakpointUpXl;
2975
3039
  exports.useMediaBreakpointUpXs = useMediaBreakpointUpXs;
2976
3040
  exports.useMediaBreakpointUpXxl = useMediaBreakpointUpXxl;
2977
3041
  exports.useMediaQuery = useMediaQuery;
2978
- exports.usePortal = usePortal;
2979
3042
  exports.useProvidedRefOrCreate = useProvidedRefOrCreate;
2980
3043
  exports.useStackState = useStackState;
2981
3044
  exports.useTabContext = useTabContext;