@djangocfg/ui-core 2.1.430 → 2.1.431

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
@@ -44,7 +44,7 @@ import { UiProviders, Button, Card } from '@djangocfg/ui-core';
44
44
  </UiProviders>
45
45
  ```
46
46
 
47
- `<UiProviders>` mounts Tooltip / Dialog / Toast / Theme provider in the right order. **Mount it once at the root** — library components (and everything in `@djangocfg/ui-tools`) trust it to be there and never nest their own (a second `TooltipProvider` is the canonical "Tooltip must be used within TooltipProvider" trap).
47
+ `<UiProviders>` mounts Tooltip / Dialog / Toast in the right order **and a top-level error `Boundary`** (a crash shows a recoverable fallback, not a white screen). **Mount it once at the root** — library components (and everything in `@djangocfg/ui-tools`) trust it to be there and never nest their own (a second `TooltipProvider` is the canonical "Tooltip must be used within TooltipProvider" trap). Pass `onError` to forward crashes to your logger, `errorFallback` for a custom (e.g. i18n) crash screen, or `errorBoundary={false}` to opt out.
48
48
 
49
49
  ## Catalogue
50
50
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-core",
3
- "version": "2.1.430",
3
+ "version": "2.1.431",
4
4
  "description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -106,7 +106,7 @@
106
106
  "check": "tsc --noEmit"
107
107
  },
108
108
  "peerDependencies": {
109
- "@djangocfg/i18n": "^2.1.430",
109
+ "@djangocfg/i18n": "^2.1.431",
110
110
  "consola": "^3.4.2",
111
111
  "lucide-react": "^0.545.0",
112
112
  "moment": "^2.30.1",
@@ -180,8 +180,8 @@
180
180
  "@chenglou/pretext": "*"
181
181
  },
182
182
  "devDependencies": {
183
- "@djangocfg/i18n": "^2.1.430",
184
- "@djangocfg/typescript-config": "^2.1.430",
183
+ "@djangocfg/i18n": "^2.1.431",
184
+ "@djangocfg/typescript-config": "^2.1.431",
185
185
  "@types/node": "^25.2.3",
186
186
  "@types/react": "^19.2.15",
187
187
  "@types/react-dom": "^19.2.3",
@@ -0,0 +1,47 @@
1
+ "use client"
2
+
3
+ import * as React from 'react';
4
+
5
+ import { cn } from '../../../lib/utils';
6
+
7
+ /**
8
+ * PopoverActionButton — the full-width icon+label list/footer row, macOS style:
9
+ *
10
+ * [ icon ] label…
11
+ *
12
+ * The canonical "jump to settings" / footer-link control inside popovers and
13
+ * menus (e.g. "Connection settings…", "Manage providers…"). Replaces the
14
+ * hand-rolled `<button className="flex w-full items-center gap-2 rounded px-2
15
+ * py-1.5 …">` that was copied across popover footers.
16
+ *
17
+ * Plain `<button>` underneath — extend it with any native button prop. The
18
+ * incoming `className` is merged last, so callers can still add layout tweaks
19
+ * (`mt-1`, etc.) without re-stating the base styling.
20
+ */
21
+ export interface PopoverActionButtonProps
22
+ extends React.ComponentProps<"button"> {
23
+ /** Icon rendered before the label, in a fixed `h-3.5 w-3.5` slot. */
24
+ icon?: React.ReactNode;
25
+ }
26
+
27
+ const PopoverActionButton = React.forwardRef<HTMLButtonElement, PopoverActionButtonProps>(
28
+ ({ className, icon, type = "button", children, ...props }, ref) => {
29
+ return (
30
+ <button
31
+ ref={ref}
32
+ type={type}
33
+ className={cn(
34
+ "flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
35
+ className,
36
+ )}
37
+ {...props}
38
+ >
39
+ {icon != null && <span className="h-3.5 w-3.5 shrink-0">{icon}</span>}
40
+ {children}
41
+ </button>
42
+ );
43
+ },
44
+ );
45
+ PopoverActionButton.displayName = "PopoverActionButton";
46
+
47
+ export { PopoverActionButton };
@@ -27,6 +27,8 @@ export type { SmartOTPProps as OTPInputProps, OTPValidationMode, OTPPasteBehavio
27
27
  export { ButtonGroup as ButtonGroupComponent } from './forms/button-group';
28
28
  export { DownloadButton } from './forms/button-download';
29
29
  export type { DownloadButtonProps } from './forms/button-download';
30
+ export { PopoverActionButton } from './forms/popover-action-button';
31
+ export type { PopoverActionButtonProps } from './forms/popover-action-button';
30
32
 
31
33
  // Mask Input
32
34
  export { MaskInput } from './forms/mask-input';
@@ -79,6 +81,8 @@ export { navigationMenuTriggerStyle, NavigationMenu, NavigationMenuList, Navigat
79
81
  export { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarSeparator, MenubarLabel, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarPortal, MenubarSubContent, MenubarSubTrigger, MenubarGroup, MenubarSub, MenubarShortcut } from './navigation/menubar';
80
82
  export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup } from './navigation/dropdown-menu';
81
83
  export { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from './navigation/context-menu';
84
+ export { PopoverRowButton } from './navigation/popover-row-button';
85
+ export type { PopoverRowButtonProps } from './navigation/popover-row-button';
82
86
  export { MenuBuilder } from './navigation/menu';
83
87
  export type { MenuItem, MenuActionItem, MenuSubmenuItem, MenuCheckboxItem, MenuRadioGroup, MenuRadioOption, MenuSeparator, MenuLabel, MenuSection, MenuCustom, MenuRowBase, MenuBuilderProps } from './navigation/menu';
84
88
  export { Tabs, TabsContent, TabsList, TabsTrigger } from './navigation/tabs';
@@ -0,0 +1,54 @@
1
+ "use client"
2
+
3
+ import { Check } from 'lucide-react';
4
+ import * as React from 'react';
5
+
6
+ import { cn } from '../../../lib/utils';
7
+
8
+ /**
9
+ * PopoverRowButton — a selectable list-row button, macOS picker style:
10
+ *
11
+ * [ ✓ ] label…
12
+ *
13
+ * The canonical row for model / machine / provider pickers: full-width, left
14
+ * aligned, with a trailing-leading check that fades in on `selected`. Replaces
15
+ * the hand-rolled `<button className={cn("flex w-full items-center gap-2 px-3
16
+ * py-1.5 …", active && "bg-muted/60")}>` copied across pickers.
17
+ *
18
+ * Plain `<button>` underneath — extend it with any native button prop. The
19
+ * incoming `className` is merged last so callers can compose freely.
20
+ */
21
+ export interface PopoverRowButtonProps
22
+ extends React.ComponentProps<"button"> {
23
+ /** Marks the row active — fills the background and shows the check. */
24
+ selected?: boolean;
25
+ }
26
+
27
+ const PopoverRowButton = React.forwardRef<HTMLButtonElement, PopoverRowButtonProps>(
28
+ ({ className, selected = false, type = "button", children, ...props }, ref) => {
29
+ return (
30
+ <button
31
+ ref={ref}
32
+ type={type}
33
+ className={cn(
34
+ "flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs",
35
+ "hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40",
36
+ selected && "bg-muted/60",
37
+ className,
38
+ )}
39
+ {...props}
40
+ >
41
+ <Check
42
+ className={cn(
43
+ "h-3.5 w-3.5 shrink-0 text-primary",
44
+ selected ? "opacity-100" : "opacity-0",
45
+ )}
46
+ />
47
+ {children}
48
+ </button>
49
+ );
50
+ },
51
+ );
52
+ PopoverRowButton.displayName = "PopoverRowButton";
53
+
54
+ export { PopoverRowButton };
@@ -53,12 +53,22 @@ const SelectTrigger = React.forwardRef<
53
53
  React.ElementRef<typeof SelectPrimitive.Trigger>,
54
54
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {
55
55
  icon?: React.ComponentType<{ className?: string }>;
56
+ /**
57
+ * `ghost` strips the bordered field chrome (border, bg, shadow, fixed
58
+ * height) for use as a flat inline control — e.g. a compact dropdown in a
59
+ * status bar or toolbar where the trigger should read like a plain button.
60
+ */
61
+ variant?: "default" | "ghost";
56
62
  }
57
- >(({ className, children, icon: Icon, ...props }, ref) => (
63
+ >(({ className, children, icon: Icon, variant = "default", ...props }, ref) => (
58
64
  <SelectPrimitive.Trigger
59
65
  ref={ref}
60
66
  className={cn(
61
- "flex h-10 w-full items-center justify-between whitespace-nowrap rounded-[var(--radius)] border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
67
+ "flex items-center justify-between whitespace-nowrap text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-50",
68
+ variant === "default" &&
69
+ "h-10 w-full rounded-[var(--radius)] border border-input bg-transparent px-3 py-2 shadow-sm focus:ring-1 focus:ring-ring",
70
+ variant === "ghost" &&
71
+ "rounded-md px-2 py-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground data-[state=open]:bg-muted data-[state=open]:text-foreground",
62
72
  className
63
73
  )}
64
74
  {...props}
@@ -1,10 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import * as React from 'react';
4
- import { useEffect, useState } from 'react';
4
+ import type { ErrorInfo } from 'react';
5
5
 
6
6
  import { TooltipProvider } from '../components/overlay/tooltip';
7
7
  import { Toaster } from '../components/feedback/sonner';
8
+ import { Boundary, type BoundaryProps } from '../components/boundary';
8
9
  import { DialogProvider } from '../lib/dialog-service/DialogProvider';
9
10
 
10
11
  export interface UiProvidersProps {
@@ -15,6 +16,23 @@ export interface UiProvidersProps {
15
16
  noToaster?: boolean;
16
17
  /** Disable the imperative `window.dialog` service + its renderer. */
17
18
  noDialogService?: boolean;
19
+ /**
20
+ * Top-level crash protection. By default UiProviders wraps the whole tree in
21
+ * a fullscreen `<Boundary>` so any uncaught React error shows a recoverable
22
+ * fallback instead of a white screen. Pass `onError` to forward the crash to
23
+ * your logger / monitor (the host owns logging; the boundary just calls it).
24
+ * Set `errorBoundary={false}` if the host installs its own top-level boundary.
25
+ */
26
+ onError?: (error: Error, info: ErrorInfo) => void;
27
+ /** Disable the built-in top-level error boundary. @default false (boundary on) */
28
+ errorBoundary?: false;
29
+ /**
30
+ * Custom fallback for the built-in boundary. Receives `{ error, errorInfo,
31
+ * reset }`. Use it to render an app-specific (e.g. i18n'd) crash screen
32
+ * instead of the default fullscreen one — lets hosts keep a single boundary
33
+ * (this one) rather than wrapping their own on top.
34
+ */
35
+ errorFallback?: BoundaryProps['fallback'];
18
36
  /**
19
37
  * SSR-safe mount strategy. Default `true` — skips providers on the
20
38
  * initial server render and remounts after `useEffect` so Radix
@@ -60,6 +78,9 @@ export function UiProviders({
60
78
  tooltipDelay = 100,
61
79
  noToaster,
62
80
  noDialogService,
81
+ onError,
82
+ errorBoundary,
83
+ errorFallback,
63
84
  }: UiProvidersProps) {
64
85
  // No SSR-skip on purpose: any nested library component that renders
65
86
  // `<Tooltip>` on its first paint expects to find `<TooltipProvider>`
@@ -67,7 +88,23 @@ export function UiProviders({
67
88
  // "Tooltip must be used within TooltipProvider" before hydration.
68
89
  // Radix's own provider tolerates the SSR pass — no hydration
69
90
  // mismatches observed; the safe wrapper was over-engineering.
70
- const tree = noDialogService ? children : <DialogProvider>{children}</DialogProvider>;
91
+ const dialogTree = noDialogService ? children : <DialogProvider>{children}</DialogProvider>;
92
+ // Top-level crash protection by default. The Boundary catches uncaught React
93
+ // errors and shows a recoverable fullscreen fallback; `onError` forwards the
94
+ // crash to the host's logger/monitor. Opt out with `errorBoundary={false}`.
95
+ const tree =
96
+ errorBoundary === false ? (
97
+ dialogTree
98
+ ) : (
99
+ <Boundary
100
+ variant="fullscreen"
101
+ name="ui-providers"
102
+ onError={onError}
103
+ fallback={errorFallback}
104
+ >
105
+ {dialogTree}
106
+ </Boundary>
107
+ );
71
108
  return (
72
109
  <TooltipProvider delayDuration={tooltipDelay}>
73
110
  {tree}