aplance-ui 1.2.2 → 1.3.0
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 +16 -10
- package/registry/components/primitives/popover/popover-context.ts +1 -0
- package/registry/components/primitives/popover/popover.tsx +7 -2
- package/registry/components/primitives/slider/slider.module.css +62 -5
- package/registry/components/primitives/slider/slider.tsx +192 -34
- package/registry/components/primitives/slider/types.ts +56 -3
- package/registry/components/primitives/slider/utils.ts +9 -0
- package/registry/components/ui/data-table/data-table.module.css +99 -50
- package/registry/components/ui/data-table/data-table.tsx +110 -104
- package/registry/components/ui/input-fields/custom-select/custom-select.module.css +21 -1
- package/registry/components/ui/input-fields/custom-select/custom-select.tsx +29 -14
- package/registry/components/ui/input-fields/input/input.tsx +80 -69
- package/registry/components/ui/tabs/tabs.module.css +22 -8
- package/registry/components/ui/tabs/tabs.tsx +5 -1
- package/registry/hooks/useHasMounted.ts +22 -0
- package/registry/registry.json +20 -3
package/package.json
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type ButtonLayout,
|
|
23
23
|
} from "../../ui/button/button";
|
|
24
24
|
import { useFocusOutside } from "../../../hooks/useFocusOutside";
|
|
25
|
+
import { useHasMounted } from "../../../hooks/useHasMounted";
|
|
25
26
|
import { ModalContext, useModal } from "./modal-context";
|
|
26
27
|
import s from "./modal.module.css";
|
|
27
28
|
import { Icon } from "../icon/icon";
|
|
@@ -138,14 +139,14 @@ export type ModalProps = {
|
|
|
138
139
|
*/
|
|
139
140
|
placement?: ModalPlacement;
|
|
140
141
|
/**
|
|
141
|
-
* Placement override below the `--md` breakpoint (900px
|
|
142
|
+
* Placement override below the `--md` breakpoint (≤ 900px).
|
|
142
143
|
* Defaults to `bottom` so modals become bottom sheets on phones.
|
|
143
144
|
* Pass the same value as `placement` (or any placement) to opt out.
|
|
144
145
|
*/
|
|
145
146
|
mobilePlacement?: ModalPlacement;
|
|
146
147
|
/**
|
|
147
148
|
* Entrance/exit animation. Defaults based on `placement`:
|
|
148
|
-
* center
|
|
149
|
+
* center→scale, top/bottom/left/right→slide from that edge, full→fade.
|
|
149
150
|
*/
|
|
150
151
|
animation?: ModalAnimation;
|
|
151
152
|
scrollBehavior?: ModalScrollBehavior;
|
|
@@ -201,6 +202,8 @@ export const Modal: FC<ModalProps> = ({
|
|
|
201
202
|
// Always starts false so the element is in the DOM before the transition fires
|
|
202
203
|
const [isVisible, setIsVisible] = useState(false);
|
|
203
204
|
|
|
205
|
+
const mounted = useHasMounted();
|
|
206
|
+
|
|
204
207
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
205
208
|
const modalId = useId();
|
|
206
209
|
|
|
@@ -243,8 +246,8 @@ export const Modal: FC<ModalProps> = ({
|
|
|
243
246
|
const close = useCallback(() => setIsOpen(false), [setIsOpen]);
|
|
244
247
|
const toggle = useCallback(() => setIsOpen(!isOpen), [isOpen, setIsOpen]);
|
|
245
248
|
|
|
246
|
-
// Open:
|
|
247
|
-
// Close: setIsVisible(false)
|
|
249
|
+
// Open: setShouldRender(true) → node mounts → useLayoutEffect below fires → isVisible=true → open transition
|
|
250
|
+
// Close: setIsVisible(false) immediately → close transition plays → setTimeout unmounts after duration
|
|
248
251
|
useEffect(() => {
|
|
249
252
|
if (isOpen) {
|
|
250
253
|
setShouldRender(true);
|
|
@@ -263,12 +266,12 @@ export const Modal: FC<ModalProps> = ({
|
|
|
263
266
|
// registers the closed CSS state before we flip to open - same trick
|
|
264
267
|
// Popover uses with isMeasured + ResizeObserver.
|
|
265
268
|
useLayoutEffect(() => {
|
|
266
|
-
if (shouldRender && contentRef.current) {
|
|
269
|
+
if (shouldRender && mounted && contentRef.current) {
|
|
267
270
|
void contentRef.current.offsetHeight; // flush reflow
|
|
268
271
|
setIsVisible(isOpen);
|
|
269
272
|
}
|
|
270
273
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
271
|
-
}, [shouldRender]); // only run when node mounts
|
|
274
|
+
}, [shouldRender, mounted]); // only run when node mounts or mounted state changes
|
|
272
275
|
|
|
273
276
|
useEffect(() => {
|
|
274
277
|
if (isOpen) onOpenRef.current?.();
|
|
@@ -328,15 +331,14 @@ export const Modal: FC<ModalProps> = ({
|
|
|
328
331
|
};
|
|
329
332
|
el.addEventListener("keydown", trap);
|
|
330
333
|
return () => el.removeEventListener("keydown", trap);
|
|
331
|
-
}, [isOpen]);
|
|
334
|
+
}, [isOpen, mounted]);
|
|
332
335
|
|
|
333
336
|
// Close when a click lands outside the dialog panel - mirrors Popover's
|
|
334
337
|
// outside-click pattern so the component family stays consistent.
|
|
335
338
|
// `ignoreSelector` exempts portaled popover menus (CustomSelect, DatePicker,
|
|
336
339
|
// RowActionsPopover, etc.): their menus portal to document.body so they're
|
|
337
340
|
// 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
|
|
341
|
+
// the dialog it was opened from. Gated on being the topmost modal so a click
|
|
340
342
|
// that dismisses a child modal doesn't bubble into closing its parent.
|
|
341
343
|
useFocusOutside(
|
|
342
344
|
[contentRef],
|
|
@@ -392,7 +394,11 @@ export const Modal: FC<ModalProps> = ({
|
|
|
392
394
|
) : null;
|
|
393
395
|
|
|
394
396
|
const portal =
|
|
395
|
-
renderOnPortal && dialog
|
|
397
|
+
renderOnPortal && dialog
|
|
398
|
+
? mounted
|
|
399
|
+
? createPortal(dialog, document.body)
|
|
400
|
+
: null
|
|
401
|
+
: dialog;
|
|
396
402
|
|
|
397
403
|
// Hoisted triggers sit outside the portal so they stay visible while closed;
|
|
398
404
|
// dialog content lives inside the portal.
|
|
@@ -15,6 +15,7 @@ export type PopoverContextType = {
|
|
|
15
15
|
position: Position;
|
|
16
16
|
hoverTimeoutRef: RefObject<ReturnType<typeof setTimeout> | null>;
|
|
17
17
|
renderOnPortal: boolean;
|
|
18
|
+
mounted: boolean;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
export const PopoverContext = createContext<PopoverContextType | undefined>(
|
|
@@ -14,6 +14,7 @@ import { createPortal } from "react-dom";
|
|
|
14
14
|
import s from "./popover.module.css";
|
|
15
15
|
import { PopoverContext, usePopover } from "./popover-context";
|
|
16
16
|
import { useFocusOutside } from "../../../hooks/useFocusOutside";
|
|
17
|
+
import { useHasMounted } from "../../../hooks/useHasMounted";
|
|
17
18
|
|
|
18
19
|
// Types
|
|
19
20
|
export type Position = "top" | "bottom" | "left" | "right";
|
|
@@ -82,6 +83,8 @@ export const Popover: FC<PopoverProps> = ({
|
|
|
82
83
|
// For hover trigger
|
|
83
84
|
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
84
85
|
|
|
86
|
+
const mounted = useHasMounted();
|
|
87
|
+
|
|
85
88
|
const [coords, setCoords] = useState<Coords>({ top: 0, left: 0 });
|
|
86
89
|
const [shouldRender, setShouldRender] = useState(false);
|
|
87
90
|
const [isMeasured, setIsMeasured] = useState(false);
|
|
@@ -178,7 +181,7 @@ export const Popover: FC<PopoverProps> = ({
|
|
|
178
181
|
setIsMeasured(false);
|
|
179
182
|
};
|
|
180
183
|
}
|
|
181
|
-
}, [shouldRender, calculatePosition]);
|
|
184
|
+
}, [shouldRender, mounted, calculatePosition]);
|
|
182
185
|
|
|
183
186
|
// Clear any pending hover-close timer when the Popover unmounts so the
|
|
184
187
|
// delayed setIsOpen never fires on a stale instance.
|
|
@@ -265,6 +268,7 @@ export const Popover: FC<PopoverProps> = ({
|
|
|
265
268
|
hoverTimeoutRef,
|
|
266
269
|
renderOnPortal,
|
|
267
270
|
position,
|
|
271
|
+
mounted,
|
|
268
272
|
}}
|
|
269
273
|
>
|
|
270
274
|
<div
|
|
@@ -387,6 +391,7 @@ export const PopoverMenu: FC<PopoverMenuProps> = ({
|
|
|
387
391
|
position,
|
|
388
392
|
hoverTimeoutRef,
|
|
389
393
|
renderOnPortal,
|
|
394
|
+
mounted,
|
|
390
395
|
} = usePopover();
|
|
391
396
|
|
|
392
397
|
const handleMouseEnter = () => {
|
|
@@ -467,5 +472,5 @@ export const PopoverMenu: FC<PopoverMenuProps> = ({
|
|
|
467
472
|
</div>
|
|
468
473
|
);
|
|
469
474
|
|
|
470
|
-
return renderOnPortal ? createPortal(menu, document.body) : menu;
|
|
475
|
+
return renderOnPortal ? (mounted ? createPortal(menu, document.body) : null) : menu;
|
|
471
476
|
};
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
─────────────────────────────────────────────────────────────────────────── */
|
|
5
5
|
|
|
6
6
|
.container {
|
|
7
|
-
--slider-nav-size: var(--spacing-
|
|
7
|
+
--slider-nav-size: var(--spacing-11);
|
|
8
8
|
--slider-nav-bg: color-mix(in srgb, var(--color-foreground) 56%, transparent);
|
|
9
9
|
--slider-nav-bg-hover: color-mix(in srgb, var(--color-foreground) 78%, transparent);
|
|
10
10
|
--slider-nav-fg: var(--color-background);
|
|
11
|
-
--slider-dot-size: var(--spacing-
|
|
11
|
+
--slider-dot-size: var(--spacing-5);
|
|
12
12
|
--slider-dot-bg: color-mix(in srgb, var(--color-foreground) 28%, transparent);
|
|
13
13
|
--slider-dot-bg-active: var(--color-primary);
|
|
14
14
|
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
transition: height var(--transition-base);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
.viewport_coverflow {
|
|
43
|
+
perspective: 72rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
.track {
|
|
43
47
|
display: flex;
|
|
44
48
|
min-width: 0;
|
|
@@ -48,11 +52,21 @@
|
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
.track_fade,
|
|
51
|
-
.track_scale
|
|
55
|
+
.track_scale,
|
|
56
|
+
.track_flip {
|
|
52
57
|
display: grid;
|
|
53
58
|
transform: none;
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
.track_flip {
|
|
62
|
+
perspective: 72rem;
|
|
63
|
+
transform-style: preserve-3d;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.track_coverflow {
|
|
67
|
+
transform-style: preserve-3d;
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
.slide {
|
|
57
71
|
min-width: 0;
|
|
58
72
|
flex-grow: 0;
|
|
@@ -75,14 +89,45 @@
|
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
|
|
92
|
+
.slide_cards,
|
|
93
|
+
.slide_coverflow {
|
|
94
|
+
will-change: transform, opacity;
|
|
95
|
+
transition:
|
|
96
|
+
opacity var(--slider-transition-duration) var(--slider-transition-easing),
|
|
97
|
+
transform var(--slider-transition-duration) var(--slider-transition-easing);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.slide_cards {
|
|
101
|
+
transform:
|
|
102
|
+
translateY(var(--slider-slide-offset))
|
|
103
|
+
scale(var(--slider-slide-scale));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.slide_coverflow {
|
|
107
|
+
transform:
|
|
108
|
+
translateY(var(--slider-slide-offset))
|
|
109
|
+
translateZ(var(--slider-slide-depth))
|
|
110
|
+
rotateY(var(--slider-slide-rotate))
|
|
111
|
+
scale(var(--slider-slide-scale));
|
|
112
|
+
transform-style: preserve-3d;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.container_dragging,
|
|
116
|
+
.container_no_transition {
|
|
117
|
+
.slide_cards,
|
|
118
|
+
.slide_coverflow {
|
|
119
|
+
transition: none;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
78
123
|
.slide_overlay {
|
|
79
124
|
grid-area: 1 / 1;
|
|
80
125
|
width: 100%;
|
|
81
126
|
opacity: 0;
|
|
82
127
|
pointer-events: none;
|
|
83
128
|
transition:
|
|
84
|
-
opacity var(--transition-
|
|
85
|
-
transform var(--transition-
|
|
129
|
+
opacity var(--slider-transition-duration) var(--slider-transition-easing),
|
|
130
|
+
transform var(--slider-transition-duration) var(--slider-transition-easing);
|
|
86
131
|
}
|
|
87
132
|
|
|
88
133
|
.track_fade .slide_overlay {
|
|
@@ -93,12 +138,22 @@
|
|
|
93
138
|
transform: scale(0.96);
|
|
94
139
|
}
|
|
95
140
|
|
|
141
|
+
.track_flip .slide_overlay {
|
|
142
|
+
backface-visibility: hidden;
|
|
143
|
+
transform: rotateY(90deg) scale(0.96);
|
|
144
|
+
transform-style: preserve-3d;
|
|
145
|
+
}
|
|
146
|
+
|
|
96
147
|
.slide_active {
|
|
97
148
|
opacity: 1;
|
|
98
149
|
pointer-events: auto;
|
|
99
150
|
transform: scale(1);
|
|
100
151
|
}
|
|
101
152
|
|
|
153
|
+
.slide_overlay.slide_active {
|
|
154
|
+
transform: scale(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
102
157
|
/* ─── Built-in nav arrows ───────────────────────────────────────────────────
|
|
103
158
|
`data-slider-no-drag` is set in TSX so `dragIgnoreSelector` does not start
|
|
104
159
|
a track drag when the user clicks the chevron.
|
|
@@ -294,6 +349,8 @@
|
|
|
294
349
|
@media (--motion-reduce) {
|
|
295
350
|
.track,
|
|
296
351
|
.slide_overlay,
|
|
352
|
+
.slide_cards,
|
|
353
|
+
.slide_coverflow,
|
|
297
354
|
.progress_bar {
|
|
298
355
|
transition: none;
|
|
299
356
|
}
|