@jinaan/uikit 0.1.9 → 0.1.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinaan/uikit",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Reusable React and Tailwind UI components for Jinaan projects.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -4,6 +4,7 @@ import { Button } from "./button";
4
4
  import {
5
5
  Dialog,
6
6
  DialogBody,
7
+ DialogCancel,
7
8
  DialogContent,
8
9
  DialogFooter,
9
10
  DialogHeader,
@@ -93,13 +94,7 @@ export function ChoiceDialog({
93
94
  </DialogBody>
94
95
  </DialogPanel>
95
96
  <DialogFooter className="gap-3 pt-2.5">
96
- <Button
97
- variant="secondary"
98
- className="flex-1"
99
- onClick={() => onOpenChange?.(false)}
100
- >
101
- {cancelLabel}
102
- </Button>
97
+ <DialogCancel className="flex-1">{cancelLabel}</DialogCancel>
103
98
  <Button className="flex-1" onClick={() => onConfirm?.(selected)}>
104
99
  {confirmLabel}
105
100
  </Button>
package/src/dialog.tsx CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  type HTMLAttributes,
14
14
  } from "react";
15
15
 
16
+ import { Button, type ButtonProps } from "./button";
16
17
  import { computeDialogFlight, type DialogOriginRect } from "./dialog-motion";
17
18
  import { cn } from "./utils";
18
19
 
@@ -81,6 +82,7 @@ function resolveOrigin(content: HTMLElement): DialogOriginRect | null {
81
82
 
82
83
  const DIALOG_CLOSE_MS = 250;
83
84
  const DialogClosingContext = createContext(false);
85
+ const DialogRequestCloseContext = createContext<() => void>(() => {});
84
86
 
85
87
  /** Dialog root; owns the delayed close so the fly-out can finish. */
86
88
  export function Dialog({
@@ -94,6 +96,7 @@ export function Dialog({
94
96
  defaultOpen ?? false,
95
97
  );
96
98
  const [closing, setClosing] = useState(false);
99
+ const closingRef = useRef(false);
97
100
  const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
98
101
  const open = controlledOpen ?? uncontrolledOpen;
99
102
  const openRef = useRef(open);
@@ -116,12 +119,13 @@ export function Dialog({
116
119
  const handleOpenChange = (next: boolean) => {
117
120
  if (next) {
118
121
  if (closeTimer.current) clearTimeout(closeTimer.current);
122
+ closingRef.current = false;
119
123
  setClosing(false);
120
124
  if (!controlled) setUncontrolledOpen(true);
121
125
  onOpenChange?.(true);
122
126
  return;
123
127
  }
124
- if (!open || closing) return;
128
+ if (!open || closingRef.current) return;
125
129
 
126
130
  const reduced =
127
131
  typeof window !== "undefined" &&
@@ -133,27 +137,33 @@ export function Dialog({
133
137
  return;
134
138
  }
135
139
 
140
+ closingRef.current = true;
136
141
  setClosing(true);
137
142
  closeTimer.current = setTimeout(() => {
138
143
  if (!controlled) setUncontrolledOpen(false);
139
144
  onOpenChange?.(false);
140
145
  requestAnimationFrame(() => {
141
146
  // Restore the overlay only when a controlled parent vetoed the close.
142
- if (openRef.current) setClosing(false);
147
+ if (openRef.current) {
148
+ closingRef.current = false;
149
+ setClosing(false);
150
+ }
143
151
  });
144
152
  }, DIALOG_CLOSE_MS);
145
153
  };
146
154
 
147
155
  return (
148
- <DialogClosingContext.Provider value={closing}>
149
- <DialogPrimitive.Root
150
- open={open}
151
- onOpenChange={handleOpenChange}
152
- {...props}
153
- >
154
- {children}
155
- </DialogPrimitive.Root>
156
- </DialogClosingContext.Provider>
156
+ <DialogRequestCloseContext.Provider value={() => handleOpenChange(false)}>
157
+ <DialogClosingContext.Provider value={closing}>
158
+ <DialogPrimitive.Root
159
+ open={open}
160
+ onOpenChange={handleOpenChange}
161
+ {...props}
162
+ >
163
+ {children}
164
+ </DialogPrimitive.Root>
165
+ </DialogClosingContext.Provider>
166
+ </DialogRequestCloseContext.Provider>
157
167
  );
158
168
  }
159
169
 
@@ -174,6 +184,18 @@ DialogTrigger.displayName = "DialogTrigger";
174
184
 
175
185
  export const DialogClose = DialogPrimitive.Close;
176
186
 
187
+ /** Secondary action that closes through the animated dialog close path. */
188
+ export const DialogCancel = forwardRef<HTMLButtonElement, ButtonProps>(
189
+ ({ variant = "secondary", children = "Cancel", ...props }, ref) => (
190
+ <DialogPrimitive.Close asChild>
191
+ <Button ref={ref} type="button" variant={variant} {...props}>
192
+ {children}
193
+ </Button>
194
+ </DialogPrimitive.Close>
195
+ ),
196
+ );
197
+ DialogCancel.displayName = "DialogCancel";
198
+
177
199
  function startDialogMotion(content: HTMLDivElement, retry = true) {
178
200
  if (content.dataset.dialogMotionStarted === "true") return;
179
201
  if (
@@ -229,8 +251,9 @@ function startDialogMotion(content: HTMLDivElement, retry = true) {
229
251
  export const DialogContent = forwardRef<
230
252
  ElementRef<typeof DialogPrimitive.Content>,
231
253
  ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
232
- >(({ className, children, ...props }, ref) => {
254
+ >(({ className, children, onEscapeKeyDown, onPointerDownOutside, ...props }, ref) => {
233
255
  const closing = useContext(DialogClosingContext);
256
+ const requestClose = useContext(DialogRequestCloseContext);
234
257
  const contentRef = useRef<HTMLDivElement>(null);
235
258
  const setRef = useCallback(
236
259
  (node: HTMLDivElement | null) => {
@@ -251,7 +274,7 @@ export const DialogContent = forwardRef<
251
274
  <DialogPrimitive.Portal>
252
275
  <DialogPrimitive.Overlay
253
276
  className={cn(
254
- "fixed inset-0 z-50 overflow-hidden bg-overlay data-[state=open]:animate-in data-[state=open]:fade-in-0",
277
+ "fixed inset-0 z-50 overflow-hidden bg-overlay backdrop-blur-[3px] data-[state=open]:animate-in data-[state=open]:fade-in-0",
255
278
  !closing &&
256
279
  "data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
257
280
  closing && "opacity-0 transition-opacity duration-200",
@@ -272,6 +295,18 @@ export const DialogContent = forwardRef<
272
295
  "focus:outline-none",
273
296
  className,
274
297
  )}
298
+ onEscapeKeyDown={(event) => {
299
+ onEscapeKeyDown?.(event);
300
+ if (event.defaultPrevented) return;
301
+ event.preventDefault();
302
+ requestClose();
303
+ }}
304
+ onPointerDownOutside={(event) => {
305
+ onPointerDownOutside?.(event);
306
+ if (event.defaultPrevented) return;
307
+ event.preventDefault();
308
+ requestClose();
309
+ }}
275
310
  {...props}
276
311
  >
277
312
  {children}