aplance-ui 1.2.1 → 1.2.2
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 +1 -1
- package/registry/components/primitives/modal/modal.tsx +46 -13
- package/registry/components/primitives/popover/popover.tsx +6 -0
- package/registry/components/ui/data-table/data-table.module.css +89 -20
- package/registry/components/ui/data-table/data-table.tsx +193 -111
- package/registry/components/ui/data-table/pagination-bar.tsx +174 -71
- package/registry/components/ui/data-table/types.ts +12 -3
- package/registry/components/ui/input-fields/common/input-field-native.module.css +52 -42
- package/registry/components/ui/input-fields/custom-select/custom-select.tsx +76 -10
- package/registry/components/ui/input-fields/input/input.tsx +69 -51
- package/registry/components/ui/input-fields/media-input/media-input.module.css +29 -0
- package/registry/components/ui/input-fields/media-input/media-preview.tsx +25 -7
- package/registry/hooks/useFocusOutside.tsx +21 -6
- package/registry/registry.json +2 -1
package/package.json
CHANGED
|
@@ -86,6 +86,20 @@ const releaseScrollLock = () => {
|
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
+
// Module-level stack of open modal ids. Only the topmost modal reacts to
|
|
90
|
+
// Escape / backdrop-click - without this, dismissing a child modal (media
|
|
91
|
+
// picker, upload, etc.) also closes the parent it was opened from.
|
|
92
|
+
const _modalStack: string[] = [];
|
|
93
|
+
const pushModal = (id: string) => {
|
|
94
|
+
_modalStack.push(id);
|
|
95
|
+
};
|
|
96
|
+
const popModal = (id: string) => {
|
|
97
|
+
const index = _modalStack.lastIndexOf(id);
|
|
98
|
+
if (index >= 0) _modalStack.splice(index, 1);
|
|
99
|
+
};
|
|
100
|
+
const isTopModal = (id: string): boolean =>
|
|
101
|
+
_modalStack[_modalStack.length - 1] === id;
|
|
102
|
+
|
|
89
103
|
// ─── Shared button passthrough ────────────────────────────────────────────────
|
|
90
104
|
|
|
91
105
|
export type ButtonPassthroughProps = {
|
|
@@ -124,14 +138,14 @@ export type ModalProps = {
|
|
|
124
138
|
*/
|
|
125
139
|
placement?: ModalPlacement;
|
|
126
140
|
/**
|
|
127
|
-
* Placement override below the `--md` breakpoint (
|
|
141
|
+
* Placement override below the `--md` breakpoint (900px and below).
|
|
128
142
|
* Defaults to `bottom` so modals become bottom sheets on phones.
|
|
129
143
|
* Pass the same value as `placement` (or any placement) to opt out.
|
|
130
144
|
*/
|
|
131
145
|
mobilePlacement?: ModalPlacement;
|
|
132
146
|
/**
|
|
133
147
|
* Entrance/exit animation. Defaults based on `placement`:
|
|
134
|
-
* center
|
|
148
|
+
* center -> scale, top/bottom/left/right -> slide from that edge, full -> fade.
|
|
135
149
|
*/
|
|
136
150
|
animation?: ModalAnimation;
|
|
137
151
|
scrollBehavior?: ModalScrollBehavior;
|
|
@@ -229,8 +243,8 @@ export const Modal: FC<ModalProps> = ({
|
|
|
229
243
|
const close = useCallback(() => setIsOpen(false), [setIsOpen]);
|
|
230
244
|
const toggle = useCallback(() => setIsOpen(!isOpen), [isOpen, setIsOpen]);
|
|
231
245
|
|
|
232
|
-
// Open:
|
|
233
|
-
// Close: setIsVisible(false)
|
|
246
|
+
// Open: setShouldRender(true) -> node mounts -> useLayoutEffect below fires -> isVisible=true -> open transition.
|
|
247
|
+
// Close: setIsVisible(false) -> close transition plays -> setTimeout unmounts after duration.
|
|
234
248
|
useEffect(() => {
|
|
235
249
|
if (isOpen) {
|
|
236
250
|
setShouldRender(true);
|
|
@@ -260,22 +274,28 @@ export const Modal: FC<ModalProps> = ({
|
|
|
260
274
|
if (isOpen) onOpenRef.current?.();
|
|
261
275
|
}, [isOpen]);
|
|
262
276
|
|
|
263
|
-
// Body scroll lock - reference-counted
|
|
277
|
+
// Body scroll lock + open-modal stack registration (both reference-counted
|
|
278
|
+
// across stacked modals).
|
|
264
279
|
useEffect(() => {
|
|
265
280
|
if (!isOpen) return;
|
|
266
281
|
acquireScrollLock();
|
|
267
|
-
|
|
268
|
-
|
|
282
|
+
pushModal(modalId);
|
|
283
|
+
return () => {
|
|
284
|
+
releaseScrollLock();
|
|
285
|
+
popModal(modalId);
|
|
286
|
+
};
|
|
287
|
+
}, [isOpen, modalId]);
|
|
269
288
|
|
|
270
|
-
// ESC key
|
|
289
|
+
// ESC key - only the topmost modal closes, so Escape inside a child modal
|
|
290
|
+
// doesn't also dismiss its parent.
|
|
271
291
|
useEffect(() => {
|
|
272
292
|
if (!closeOnEsc || !isOpen) return;
|
|
273
293
|
const handler = (e: globalThis.KeyboardEvent) => {
|
|
274
|
-
if (e.key === "Escape") close();
|
|
294
|
+
if (e.key === "Escape" && isTopModal(modalId)) close();
|
|
275
295
|
};
|
|
276
296
|
window.addEventListener("keydown", handler);
|
|
277
297
|
return () => window.removeEventListener("keydown", handler);
|
|
278
|
-
}, [isOpen, closeOnEsc, close]);
|
|
298
|
+
}, [isOpen, closeOnEsc, close, modalId]);
|
|
279
299
|
|
|
280
300
|
// Focus trap
|
|
281
301
|
useLayoutEffect(() => {
|
|
@@ -312,9 +332,22 @@ export const Modal: FC<ModalProps> = ({
|
|
|
312
332
|
|
|
313
333
|
// Close when a click lands outside the dialog panel - mirrors Popover's
|
|
314
334
|
// outside-click pattern so the component family stays consistent.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
335
|
+
// `ignoreSelector` exempts portaled popover menus (CustomSelect, DatePicker,
|
|
336
|
+
// RowActionsPopover, etc.): their menus portal to document.body so they're
|
|
337
|
+
// outside `contentRef`, but a click on a dropdown option must NOT dismiss
|
|
338
|
+
// the dialog it was opened from.
|
|
339
|
+
// Backdrop / outside click - gated on being the topmost modal so a click
|
|
340
|
+
// that dismisses a child modal doesn't bubble into closing its parent.
|
|
341
|
+
useFocusOutside(
|
|
342
|
+
[contentRef],
|
|
343
|
+
() => {
|
|
344
|
+
if (isTopModal(modalId)) close();
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
enabled: closeOnOverlayClick && isOpen,
|
|
348
|
+
ignoreSelector: "[data-popover-menu]",
|
|
349
|
+
},
|
|
350
|
+
);
|
|
318
351
|
|
|
319
352
|
// ── Dialog panel ──────────────────────────────────────────────────────────
|
|
320
353
|
const dialog = shouldRender ? (
|
|
@@ -453,6 +453,12 @@ export const PopoverMenu: FC<PopoverMenuProps> = ({
|
|
|
453
453
|
<div
|
|
454
454
|
ref={menuRef}
|
|
455
455
|
className={clsx(s.menu_wrapper, className)}
|
|
456
|
+
// Stable marker for portaled-layer detection. A `Popover` menu portals
|
|
457
|
+
// to `document.body`, escaping its host's DOM subtree - hosts with
|
|
458
|
+
// outside-click dismissal (Modal/Drawer) match this selector via
|
|
459
|
+
// `useFocusOutside({ ignoreSelector })` so clicking a menu option
|
|
460
|
+
// doesn't dismiss the host.
|
|
461
|
+
data-popover-menu=""
|
|
456
462
|
onMouseEnter={handleMouseEnter}
|
|
457
463
|
onMouseLeave={handleMouseLeave}
|
|
458
464
|
style={menuStyles}
|
|
@@ -114,16 +114,18 @@
|
|
|
114
114
|
overflow-x: auto;
|
|
115
115
|
overflow-y: auto;
|
|
116
116
|
border: var(--border-1) solid var(--color-border);
|
|
117
|
-
border-radius: var(--radius-md);
|
|
118
|
-
background: var(--color-background);
|
|
119
|
-
position: relative;
|
|
120
|
-
}
|
|
117
|
+
border-radius: var(--radius-md);
|
|
118
|
+
background: var(--color-background);
|
|
119
|
+
position: relative;
|
|
120
|
+
}
|
|
121
121
|
|
|
122
122
|
/* ─────────────────────────────────────────────
|
|
123
123
|
Shared <table> - header wrap + body wrap.
|
|
124
124
|
table-layout:fixed + identical widths = perfect column alignment.
|
|
125
125
|
───────────────────────────────────────────── */
|
|
126
126
|
.table {
|
|
127
|
+
--data-table-selection-control-size: var(--spacing-9);
|
|
128
|
+
|
|
127
129
|
border-collapse: separate;
|
|
128
130
|
border-spacing: 0;
|
|
129
131
|
table-layout: fixed;
|
|
@@ -295,6 +297,8 @@
|
|
|
295
297
|
}
|
|
296
298
|
|
|
297
299
|
.selection_checkbox {
|
|
300
|
+
--native-control-size: var(--data-table-selection-control-size);
|
|
301
|
+
|
|
298
302
|
width: fit-content;
|
|
299
303
|
min-width: fit-content;
|
|
300
304
|
display: inline-flex;
|
|
@@ -534,14 +538,28 @@
|
|
|
534
538
|
min-width: 0;
|
|
535
539
|
}
|
|
536
540
|
|
|
537
|
-
.
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
gap: var(--spacing-8);
|
|
541
|
-
min-width: 0;
|
|
542
|
-
flex-shrink: 1;
|
|
541
|
+
.pagination_bar_loading {
|
|
542
|
+
flex-wrap: wrap;
|
|
543
|
+
row-gap: var(--spacing-4);
|
|
543
544
|
}
|
|
544
545
|
|
|
546
|
+
.loading_select_skeleton {
|
|
547
|
+
border-radius: var(--radius-sm);
|
|
548
|
+
flex-shrink: 0;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.pagination_left {
|
|
552
|
+
display: flex;
|
|
553
|
+
align-items: center;
|
|
554
|
+
gap: var(--spacing-8);
|
|
555
|
+
min-width: 0;
|
|
556
|
+
flex-shrink: 1;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.pagination_left_loading {
|
|
560
|
+
overflow: hidden;
|
|
561
|
+
}
|
|
562
|
+
|
|
545
563
|
.pagination_count {
|
|
546
564
|
font-size: var(--font-size-sm);
|
|
547
565
|
color: var(--color-muted-foreground);
|
|
@@ -557,15 +575,56 @@
|
|
|
557
575
|
display: flex;
|
|
558
576
|
align-items: center;
|
|
559
577
|
gap: var(--spacing-8);
|
|
560
|
-
|
|
578
|
+
min-width: 0;
|
|
579
|
+
flex-shrink: 1;
|
|
561
580
|
}
|
|
562
581
|
|
|
563
|
-
.
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
582
|
+
.pagination_controls_loading {
|
|
583
|
+
flex-direction: row;
|
|
584
|
+
flex-wrap: wrap;
|
|
585
|
+
align-items: center;
|
|
586
|
+
justify-content: flex-end;
|
|
587
|
+
max-width: 100%;
|
|
588
|
+
row-gap: var(--spacing-4);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.pagination_controls_loading > * {
|
|
592
|
+
flex: 0 1 auto;
|
|
593
|
+
min-width: 0;
|
|
594
|
+
max-width: 100%;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.pagination_text_skeleton {
|
|
598
|
+
height: 1em;
|
|
599
|
+
min-width: 0;
|
|
600
|
+
max-width: 100%;
|
|
601
|
+
flex: 0 1 auto;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.pagination_select_skeleton,
|
|
605
|
+
.pagination_button_skeleton {
|
|
606
|
+
width: var(--spacing-11);
|
|
607
|
+
height: var(--spacing-11);
|
|
608
|
+
min-width: 0;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.pagination_select_skeleton {
|
|
612
|
+
flex: 0 1 auto;
|
|
567
613
|
}
|
|
568
614
|
|
|
615
|
+
.pagination_button_skeleton {
|
|
616
|
+
flex: 0 0 auto;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.page_size_wrap {
|
|
620
|
+
display: flex;
|
|
621
|
+
align-items: center;
|
|
622
|
+
gap: var(--spacing-5);
|
|
623
|
+
min-width: 0;
|
|
624
|
+
flex-shrink: 1;
|
|
625
|
+
max-width: 100%;
|
|
626
|
+
}
|
|
627
|
+
|
|
569
628
|
.page_size_label {
|
|
570
629
|
font-size: var(--font-size-sm);
|
|
571
630
|
color: var(--color-muted-foreground);
|
|
@@ -585,8 +644,18 @@
|
|
|
585
644
|
}
|
|
586
645
|
}
|
|
587
646
|
|
|
588
|
-
.page_nav {
|
|
589
|
-
display: flex;
|
|
590
|
-
align-items: center;
|
|
591
|
-
gap: var(--spacing-2);
|
|
592
|
-
|
|
647
|
+
.page_nav {
|
|
648
|
+
display: flex;
|
|
649
|
+
align-items: center;
|
|
650
|
+
gap: var(--spacing-2);
|
|
651
|
+
flex-shrink: 0;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.page_nav_loading {
|
|
655
|
+
flex-wrap: wrap;
|
|
656
|
+
justify-content: flex-end;
|
|
657
|
+
row-gap: var(--spacing-2);
|
|
658
|
+
min-width: 0;
|
|
659
|
+
max-width: 100%;
|
|
660
|
+
flex-shrink: 1;
|
|
661
|
+
}
|