@djangocfg/ui-core 2.1.415 → 2.1.417
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 +24 -0
- package/package.json +9 -4
- package/src/components/index.ts +1 -2
- package/src/components/overlay/tooltip/index.tsx +0 -2
- package/src/index.ts +3 -0
- package/src/lib/dialog-service/getDialog.ts +59 -0
- package/src/lib/dialog-service/index.ts +3 -0
- package/src/providers/UiProviders.tsx +77 -0
- package/src/providers/index.ts +2 -0
- package/src/components/overlay/tooltip/tooltip-provider-safe.tsx +0 -44
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.
|
|
3
|
+
"version": "2.1.417",
|
|
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.
|
|
103
|
+
"@djangocfg/i18n": "^2.1.417",
|
|
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.
|
|
170
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
174
|
+
"@djangocfg/i18n": "^2.1.417",
|
|
175
|
+
"@djangocfg/typescript-config": "^2.1.417",
|
|
171
176
|
"@types/node": "^25.2.3",
|
|
172
177
|
"@types/react": "^19.2.15",
|
|
173
178
|
"@types/react-dom": "^19.2.3",
|
package/src/components/index.ts
CHANGED
|
@@ -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
|
|
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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DialogAPI } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lazy accessor for the global `window.dialog` API installed by
|
|
7
|
+
* `<DialogProvider />`. Use this in any library code that needs to call
|
|
8
|
+
* `dialog.confirm` / `dialog.alert` / `dialog.prompt` from a non-React
|
|
9
|
+
* context (e.g. event handlers in `ui-tools` tools).
|
|
10
|
+
*
|
|
11
|
+
* Returns `null` (and warns once in dev) when:
|
|
12
|
+
* - we're rendering on the server (`typeof window === 'undefined'`)
|
|
13
|
+
* - the host app has not mounted `<DialogProvider />`, so `window.dialog`
|
|
14
|
+
* is missing.
|
|
15
|
+
*
|
|
16
|
+
* The caller must handle the `null` case — usually by bailing out of the
|
|
17
|
+
* action with a console warning. Library code must never throw if the
|
|
18
|
+
* provider is missing; consumers without it should still be able to
|
|
19
|
+
* import / render the component (just without CRUD flows).
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const dialog = getDialog();
|
|
23
|
+
* if (!dialog) return;
|
|
24
|
+
* const ok = await dialog.confirm({ title: 'Delete?', message: '…', variant: 'destructive' });
|
|
25
|
+
* if (ok) await onDelete();
|
|
26
|
+
*/
|
|
27
|
+
export function getDialog(): DialogAPI | null {
|
|
28
|
+
if (typeof window === 'undefined') return null;
|
|
29
|
+
const api = (window as Window & { dialog?: DialogAPI }).dialog;
|
|
30
|
+
if (!api) {
|
|
31
|
+
if (process.env.NODE_ENV !== 'production' && !warnedMissing) {
|
|
32
|
+
warnedMissing = true;
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.warn(
|
|
35
|
+
'[getDialog] window.dialog is not available — mount <DialogProvider /> at the app root.',
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return api;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let warnedMissing = false;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Like `getDialog()` but throws when unavailable. Use sparingly — only in
|
|
47
|
+
* code paths where missing `window.dialog` is a developer error (e.g. an
|
|
48
|
+
* action explicitly triggered by user interaction in a feature that
|
|
49
|
+
* unconditionally requires dialogs).
|
|
50
|
+
*/
|
|
51
|
+
export function requireDialog(): DialogAPI {
|
|
52
|
+
const api = getDialog();
|
|
53
|
+
if (!api) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
'requireDialog(): window.dialog is not available. Mount <DialogProvider /> at the app root.',
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return api;
|
|
59
|
+
}
|
|
@@ -10,6 +10,9 @@ export type {
|
|
|
10
10
|
// Store
|
|
11
11
|
export { dialogStore, useDialogStore, initDialogAPI, showDialog } from './store';
|
|
12
12
|
|
|
13
|
+
// Imperative accessors for non-React code (library helpers, event handlers).
|
|
14
|
+
export { getDialog, requireDialog } from './getDialog';
|
|
15
|
+
|
|
13
16
|
// Provider
|
|
14
17
|
export { DialogProvider } from './DialogProvider';
|
|
15
18
|
|
|
@@ -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
|
+
}
|
|
@@ -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
|
-
}
|