@djangocfg/ui-core 2.1.416 → 2.1.418

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/README.md CHANGED
@@ -22,8 +22,32 @@ pnpm add @djangocfg/ui-core
22
22
  import { Button, Card, Sidebar, SSRPagination } from '@djangocfg/ui-core/components';
23
23
  import { useIsMobile, useNavigate, useQueryParams } from '@djangocfg/ui-core/hooks';
24
24
  import { cn } from '@djangocfg/ui-core/lib';
25
+ import { UiProviders } from '@djangocfg/ui-core/providers';
25
26
  ```
26
27
 
28
+ ## Root providers
29
+
30
+ Mount `<UiProviders>` once at the top of your app — it bundles the overlay
31
+ and imperative services every ui-core / ui-tools component expects:
32
+
33
+ ```tsx
34
+ import { UiProviders } from '@djangocfg/ui-core/providers';
35
+
36
+ export function Root({ children }: { children: React.ReactNode }) {
37
+ return <UiProviders>{children}</UiProviders>;
38
+ }
39
+ ```
40
+
41
+ Includes:
42
+ - `<TooltipProvider>` (Radix tooltip root, `delayDuration={100}` by default)
43
+ - `<DialogProvider>` (installs `window.dialog.*` + renders active dialogs)
44
+ - `<Toaster>` (Sonner toast portal)
45
+
46
+ Opt out per-service: `<UiProviders noToaster noDialogService>`.
47
+
48
+ **Do not nest a second `<TooltipProvider>` inside library components** —
49
+ that creates a separate context scope and breaks the tooltip↔provider link.
50
+
27
51
  ## Components
28
52
 
29
53
  Organized in `components/` by category — everything re-exported from the root barrel.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-core",
3
- "version": "2.1.416",
3
+ "version": "2.1.418",
4
4
  "description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -63,6 +63,11 @@
63
63
  "import": "./src/lib/dialog-service/index.ts",
64
64
  "require": "./src/lib/dialog-service/index.ts"
65
65
  },
66
+ "./providers": {
67
+ "types": "./src/providers/index.ts",
68
+ "import": "./src/providers/index.ts",
69
+ "require": "./src/providers/index.ts"
70
+ },
66
71
  "./utils": {
67
72
  "types": "./src/utils/index.ts",
68
73
  "import": "./src/utils/index.ts",
@@ -95,7 +100,7 @@
95
100
  "check": "tsc --noEmit"
96
101
  },
97
102
  "peerDependencies": {
98
- "@djangocfg/i18n": "^2.1.416",
103
+ "@djangocfg/i18n": "^2.1.418",
99
104
  "consola": "^3.4.2",
100
105
  "lucide-react": "^0.545.0",
101
106
  "moment": "^2.30.1",
@@ -166,8 +171,8 @@
166
171
  "vaul": "1.1.2"
167
172
  },
168
173
  "devDependencies": {
169
- "@djangocfg/i18n": "^2.1.416",
170
- "@djangocfg/typescript-config": "^2.1.416",
174
+ "@djangocfg/i18n": "^2.1.418",
175
+ "@djangocfg/typescript-config": "^2.1.418",
171
176
  "@types/node": "^25.2.3",
172
177
  "@types/react": "^19.2.15",
173
178
  "@types/react-dom": "^19.2.3",
@@ -68,8 +68,7 @@ export { ResponsiveSheet, ResponsiveSheetContent, ResponsiveSheetHeader, Respons
68
68
  export { SidePanel, SidePanelContent, SidePanelHeader, SidePanelTitle, SidePanelDescription, SidePanelBody, SidePanelFooter, SidePanelClose } from './overlay/side-panel';
69
69
  export type { SidePanelProps, SidePanelContentProps, SidePanelCloseProps } from './overlay/side-panel';
70
70
  export { HoverCard, HoverCardContent, HoverCardTrigger } from './overlay/hover-card';
71
- export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, SafeTooltipProvider } from './overlay/tooltip';
72
- export type { SafeTooltipProviderProps } from './overlay/tooltip';
71
+ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './overlay/tooltip';
73
72
 
74
73
  // ─────────────────────────────────────────────────────────────────────────────
75
74
  // Navigation
@@ -32,5 +32,3 @@ const TooltipContent = React.forwardRef<
32
32
  TooltipContent.displayName = TooltipPrimitive.Content.displayName
33
33
 
34
34
  export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
35
- export { SafeTooltipProvider } from './tooltip-provider-safe';
36
- export type { SafeTooltipProviderProps } from './tooltip-provider-safe';
package/src/index.ts CHANGED
@@ -14,3 +14,6 @@ export * from './hooks';
14
14
 
15
15
  // Re-export lib utilities
16
16
  export * from './lib';
17
+
18
+ // Re-export composite providers (UiProviders)
19
+ export * from './providers';
@@ -0,0 +1,77 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { useEffect, useState } from 'react';
5
+
6
+ import { TooltipProvider } from '../components/overlay/tooltip';
7
+ import { Toaster } from '../components/feedback/sonner';
8
+ import { DialogProvider } from '../lib/dialog-service/DialogProvider';
9
+
10
+ export interface UiProvidersProps {
11
+ children: React.ReactNode;
12
+ /** Tooltip open delay in ms. @default 100 (snappy for desktop apps) */
13
+ tooltipDelay?: number;
14
+ /** Disable the Sonner toaster (e.g. when the host renders its own). */
15
+ noToaster?: boolean;
16
+ /** Disable the imperative `window.dialog` service + its renderer. */
17
+ noDialogService?: boolean;
18
+ /**
19
+ * SSR-safe mount strategy. Default `true` — skips providers on the
20
+ * initial server render and remounts after `useEffect` so Radix
21
+ * `<TooltipProvider>` (which calls `useId()` + opens internal state
22
+ * on mount) doesn't trigger hydration mismatches under Next.js.
23
+ *
24
+ * Set `ssr={false}` on CSR-only hosts (Wails, Vite SPA, Storybook
25
+ * iframe) to skip the deferred mount — providers render on the
26
+ * very first paint and library components see their context
27
+ * immediately.
28
+ */
29
+ ssr?: boolean;
30
+ }
31
+
32
+ /**
33
+ * One root composition for every overlay/imperative-service the
34
+ * djangocfg UI library needs:
35
+ *
36
+ * - `<TooltipProvider>` — single context root for every `<Tooltip>`
37
+ * in the tree (Radix). Without one, library components log
38
+ * "Tooltip must be used within TooltipProvider".
39
+ * - `<DialogProvider>` — installs the `window.dialog.*` imperative
40
+ * API and renders the active dialog into a portal.
41
+ * - `<Toaster>` — Sonner toast portal. Library callsites use
42
+ * `toast(...)` from the same package; no portal = silent no-op.
43
+ *
44
+ * Apple-style: app/host mounts ONE `<UiProviders>` at the very top of
45
+ * the tree, and never again. Library components must NOT include their
46
+ * own nested `TooltipProvider` — a second context root creates a fresh
47
+ * provider scope with different delays, and (worse) under Vite dev a
48
+ * dup-module load yields two `createContext()` instances, breaking the
49
+ * provider/consumer link.
50
+ *
51
+ * Usage:
52
+ * import { UiProviders } from '@djangocfg/ui-core/lib/providers';
53
+ *
54
+ * <UiProviders>
55
+ * <YourApp />
56
+ * </UiProviders>
57
+ */
58
+ export function UiProviders({
59
+ children,
60
+ tooltipDelay = 100,
61
+ noToaster,
62
+ noDialogService,
63
+ }: UiProvidersProps) {
64
+ // No SSR-skip on purpose: any nested library component that renders
65
+ // `<Tooltip>` on its first paint expects to find `<TooltipProvider>`
66
+ // already in the tree. Skipping the provider during SSR caused
67
+ // "Tooltip must be used within TooltipProvider" before hydration.
68
+ // Radix's own provider tolerates the SSR pass — no hydration
69
+ // mismatches observed; the safe wrapper was over-engineering.
70
+ const tree = noDialogService ? children : <DialogProvider>{children}</DialogProvider>;
71
+ return (
72
+ <TooltipProvider delayDuration={tooltipDelay}>
73
+ {tree}
74
+ {!noToaster && <Toaster />}
75
+ </TooltipProvider>
76
+ );
77
+ }
@@ -0,0 +1,2 @@
1
+ export { UiProviders } from './UiProviders';
2
+ export type { UiProvidersProps } from './UiProviders';
@@ -1,44 +0,0 @@
1
- "use client"
2
-
3
- import * as React from 'react';
4
-
5
- import { TooltipProvider as RadixTooltipProvider } from '@radix-ui/react-tooltip';
6
-
7
- export interface SafeTooltipProviderProps {
8
- children: React.ReactNode;
9
- delayDuration?: number;
10
- skipDelayDuration?: number;
11
- disableHoverableContent?: boolean;
12
- }
13
-
14
- /**
15
- * SafeTooltipProvider - SSR-safe wrapper for Radix TooltipProvider
16
- * Only renders on client-side to avoid hydration mismatches
17
- */
18
- export function SafeTooltipProvider({
19
- children,
20
- delayDuration = 700,
21
- skipDelayDuration = 300,
22
- disableHoverableContent,
23
- }: SafeTooltipProviderProps) {
24
- const [mounted, setMounted] = React.useState(false);
25
-
26
- React.useEffect(() => {
27
- setMounted(true);
28
- }, []);
29
-
30
- if (!mounted) {
31
- // During SSR, return children without TooltipProvider
32
- return <>{children}</>;
33
- }
34
-
35
- return (
36
- <RadixTooltipProvider
37
- delayDuration={delayDuration}
38
- skipDelayDuration={skipDelayDuration}
39
- disableHoverableContent={disableHoverableContent}
40
- >
41
- {children}
42
- </RadixTooltipProvider>
43
- );
44
- }