@telefonica/mistica 17.6.0 → 17.7.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/css/mistica.css +1 -1
- package/dist/accordion.css-mistica.js +6 -6
- package/dist/align.css-mistica.js +1 -1
- package/dist/autocomplete.css-mistica.js +1 -1
- package/dist/avatar.css-mistica.js +1 -1
- package/dist/badge.css-mistica.js +1 -1
- package/dist/box.css-mistica.js +13 -13
- package/dist/boxed.css-mistica.js +24 -24
- package/dist/button-group.css-mistica.js +1 -1
- package/dist/button-layout.css-mistica.js +14 -14
- package/dist/button.css-mistica.js +34 -34
- package/dist/callout.css-mistica.js +11 -11
- package/dist/card-internal.css-mistica.js +21 -21
- package/dist/carousel.css-mistica.js +8 -8
- package/dist/checkbox.css-mistica.js +11 -11
- package/dist/chip.css-mistica.js +15 -15
- package/dist/circle.css-mistica.js +1 -1
- package/dist/community/advanced-data-card.css-mistica.js +8 -8
- package/dist/community/ai-card.css-mistica.js +2 -2
- package/dist/community/blocks.css-mistica.js +1 -1
- package/dist/community/cyber/example-component.css-mistica.js +1 -1
- package/dist/community/example-component.css-mistica.js +2 -2
- package/dist/counter.css-mistica.js +1 -1
- package/dist/cover-hero.css-mistica.js +2 -2
- package/dist/credit-card-number-field.css-mistica.js +3 -3
- package/dist/date-field.css-mistica.js +1 -1
- package/dist/date-time-picker.css-mistica.js +1 -1
- package/dist/dialog.css-mistica.js +4 -4
- package/dist/divider.css-mistica.js +5 -5
- package/dist/double-field.css-mistica.js +2 -2
- package/dist/drawer.css-mistica.js +1 -1
- package/dist/empty-state-card.css-mistica.js +1 -1
- package/dist/empty-state.css-mistica.js +5 -5
- package/dist/fade-in.css-mistica.js +1 -1
- package/dist/fade-out.css-mistica.js +1 -1
- package/dist/feedback.css-mistica.js +1 -1
- package/dist/file-upload.css-mistica.js +7 -7
- package/dist/fixed-footer-layout.css-mistica.js +2 -2
- package/dist/form.css-mistica.js +1 -1
- package/dist/grid-layout.css-mistica.js +3 -3
- package/dist/grid.css-mistica.js +120 -120
- package/dist/header.css-mistica.js +1 -1
- package/dist/hero.css-mistica.js +2 -2
- package/dist/horizontal-scroll.css-mistica.js +1 -1
- package/dist/icon-button.css-mistica.js +53 -53
- package/dist/icons/icon-chevron.css-mistica.js +2 -2
- package/dist/icons/icon-error.css-mistica.js +1 -1
- package/dist/image.css-mistica.js +11 -11
- package/dist/inline.css-mistica.js +11 -11
- package/dist/list.css-mistica.js +7 -7
- package/dist/loading-bar.css-mistica.js +1 -1
- package/dist/loading-screen.css-mistica.js +4 -4
- package/dist/logo.css-mistica.js +5 -5
- package/dist/menu.css-mistica.js +26 -24
- package/dist/menu.css.d.ts +2 -2
- package/dist/menu.d.ts +15 -3
- package/dist/menu.js +184 -162
- package/dist/mosaic.css-mistica.js +1 -1
- package/dist/navigation-bar.css-mistica.js +9 -9
- package/dist/navigation-breadcrumbs.css-mistica.js +1 -1
- package/dist/navigation-shared.css-mistica.js +9 -9
- package/dist/package-version.js +1 -1
- package/dist/pagination.css-mistica.js +18 -18
- package/dist/pin-field.css-mistica.js +1 -1
- package/dist/popover.css-mistica.js +1 -1
- package/dist/popover.d.ts +1 -0
- package/dist/progress-bar.css-mistica.js +6 -6
- package/dist/radio-button.css-mistica.js +19 -19
- package/dist/rating.css-mistica.js +2 -2
- package/dist/responsive-layout.css-mistica.js +6 -6
- package/dist/screen-reader-only.css-mistica.js +1 -1
- package/dist/select.css-mistica.js +15 -15
- package/dist/sheet-action-row.css-mistica.js +1 -1
- package/dist/sheet-common.css-mistica.js +1 -1
- package/dist/sheet-info.css-mistica.js +1 -1
- package/dist/sidenav-bar-layout.css-mistica.js +1 -1
- package/dist/sidenav-bar.css-mistica.js +71 -71
- package/dist/sidenav-bar.js +55 -50
- package/dist/skeletons.css-mistica.js +6 -6
- package/dist/skins/skin-contract.css-mistica.js +738 -738
- package/dist/skip-link.css-mistica.js +1 -1
- package/dist/slider.css-mistica.js +18 -18
- package/dist/snackbar.css-mistica.js +4 -4
- package/dist/spinner.css-mistica.js +1 -1
- package/dist/square.css-mistica.js +1 -1
- package/dist/stack.css-mistica.js +5 -5
- package/dist/stacking-group.css-mistica.js +1 -1
- package/dist/stepper.css-mistica.js +2 -2
- package/dist/switch-component.css-mistica.js +35 -35
- package/dist/table.css-mistica.js +9 -9
- package/dist/tabs.css-mistica.js +17 -17
- package/dist/tag.css-mistica.js +1 -1
- package/dist/text-field-base.css-mistica.js +16 -16
- package/dist/text-field-components.css-mistica.js +3 -3
- package/dist/text-link.css-mistica.js +6 -6
- package/dist/text.css-mistica.js +6 -6
- package/dist/timeline.css-mistica.js +9 -9
- package/dist/timer.css-mistica.js +6 -6
- package/dist/tooltip.css-mistica.js +1 -1
- package/dist/tooltip.d.ts +3 -1
- package/dist/tooltip.js +158 -147
- package/dist/touchable.css-mistica.js +1 -1
- package/dist/utils/aspect-ratio-support.css-mistica.js +2 -2
- package/dist/video.css-mistica.js +1 -1
- package/dist/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +1 -1
- package/dist-es/accordion.css-mistica.js +6 -6
- package/dist-es/align.css-mistica.js +1 -1
- package/dist-es/autocomplete.css-mistica.js +1 -1
- package/dist-es/avatar.css-mistica.js +1 -1
- package/dist-es/badge.css-mistica.js +1 -1
- package/dist-es/box.css-mistica.js +13 -13
- package/dist-es/boxed.css-mistica.js +23 -23
- package/dist-es/button-group.css-mistica.js +1 -1
- package/dist-es/button-layout.css-mistica.js +14 -14
- package/dist-es/button.css-mistica.js +34 -34
- package/dist-es/callout.css-mistica.js +11 -11
- package/dist-es/card-internal.css-mistica.js +21 -21
- package/dist-es/carousel.css-mistica.js +8 -8
- package/dist-es/checkbox.css-mistica.js +11 -11
- package/dist-es/chip.css-mistica.js +15 -15
- package/dist-es/circle.css-mistica.js +1 -1
- package/dist-es/community/advanced-data-card.css-mistica.js +8 -8
- package/dist-es/community/ai-card.css-mistica.js +2 -2
- package/dist-es/community/blocks.css-mistica.js +1 -1
- package/dist-es/community/cyber/example-component.css-mistica.js +1 -1
- package/dist-es/community/example-component.css-mistica.js +2 -2
- package/dist-es/counter.css-mistica.js +1 -1
- package/dist-es/cover-hero.css-mistica.js +2 -2
- package/dist-es/credit-card-number-field.css-mistica.js +3 -3
- package/dist-es/date-field.css-mistica.js +1 -1
- package/dist-es/date-time-picker.css-mistica.js +1 -1
- package/dist-es/dialog.css-mistica.js +4 -4
- package/dist-es/divider.css-mistica.js +5 -5
- package/dist-es/double-field.css-mistica.js +2 -2
- package/dist-es/drawer.css-mistica.js +1 -1
- package/dist-es/empty-state-card.css-mistica.js +1 -1
- package/dist-es/empty-state.css-mistica.js +5 -5
- package/dist-es/fade-in.css-mistica.js +1 -1
- package/dist-es/fade-out.css-mistica.js +1 -1
- package/dist-es/feedback.css-mistica.js +1 -1
- package/dist-es/file-upload.css-mistica.js +7 -7
- package/dist-es/fixed-footer-layout.css-mistica.js +2 -2
- package/dist-es/form.css-mistica.js +1 -1
- package/dist-es/grid-layout.css-mistica.js +3 -3
- package/dist-es/grid.css-mistica.js +120 -120
- package/dist-es/header.css-mistica.js +1 -1
- package/dist-es/hero.css-mistica.js +2 -2
- package/dist-es/horizontal-scroll.css-mistica.js +1 -1
- package/dist-es/icon-button.css-mistica.js +53 -53
- package/dist-es/icons/icon-chevron.css-mistica.js +2 -2
- package/dist-es/icons/icon-error.css-mistica.js +1 -1
- package/dist-es/image.css-mistica.js +4 -4
- package/dist-es/inline.css-mistica.js +11 -11
- package/dist-es/list.css-mistica.js +7 -7
- package/dist-es/loading-bar.css-mistica.js +1 -1
- package/dist-es/loading-screen.css-mistica.js +4 -4
- package/dist-es/logo.css-mistica.js +5 -5
- package/dist-es/menu.css-mistica.js +14 -15
- package/dist-es/menu.js +238 -216
- package/dist-es/mosaic.css-mistica.js +1 -1
- package/dist-es/navigation-bar.css-mistica.js +9 -9
- package/dist-es/navigation-breadcrumbs.css-mistica.js +1 -1
- package/dist-es/navigation-shared.css-mistica.js +9 -9
- package/dist-es/package-version.js +1 -1
- package/dist-es/pagination.css-mistica.js +18 -18
- package/dist-es/pin-field.css-mistica.js +1 -1
- package/dist-es/popover.css-mistica.js +1 -1
- package/dist-es/progress-bar.css-mistica.js +6 -6
- package/dist-es/radio-button.css-mistica.js +19 -19
- package/dist-es/rating.css-mistica.js +2 -2
- package/dist-es/responsive-layout.css-mistica.js +6 -6
- package/dist-es/screen-reader-only.css-mistica.js +1 -1
- package/dist-es/select.css-mistica.js +15 -15
- package/dist-es/sheet-action-row.css-mistica.js +1 -1
- package/dist-es/sheet-common.css-mistica.js +1 -1
- package/dist-es/sheet-info.css-mistica.js +1 -1
- package/dist-es/sidenav-bar-layout.css-mistica.js +1 -1
- package/dist-es/sidenav-bar.css-mistica.js +71 -71
- package/dist-es/sidenav-bar.js +70 -65
- package/dist-es/skeletons.css-mistica.js +6 -6
- package/dist-es/skins/skin-contract.css-mistica.js +738 -738
- package/dist-es/skip-link.css-mistica.js +1 -1
- package/dist-es/slider.css-mistica.js +18 -18
- package/dist-es/snackbar.css-mistica.js +4 -4
- package/dist-es/spinner.css-mistica.js +1 -1
- package/dist-es/square.css-mistica.js +1 -1
- package/dist-es/stack.css-mistica.js +5 -5
- package/dist-es/stacking-group.css-mistica.js +1 -1
- package/dist-es/stepper.css-mistica.js +2 -2
- package/dist-es/style.css +1 -1
- package/dist-es/switch-component.css-mistica.js +35 -35
- package/dist-es/table.css-mistica.js +9 -9
- package/dist-es/tabs.css-mistica.js +17 -17
- package/dist-es/tag.css-mistica.js +1 -1
- package/dist-es/text-field-base.css-mistica.js +16 -16
- package/dist-es/text-field-components.css-mistica.js +3 -3
- package/dist-es/text-link.css-mistica.js +6 -6
- package/dist-es/text.css-mistica.js +6 -6
- package/dist-es/timeline.css-mistica.js +9 -9
- package/dist-es/timer.css-mistica.js +6 -6
- package/dist-es/tooltip.css-mistica.js +1 -1
- package/dist-es/tooltip.js +198 -187
- package/dist-es/touchable.css-mistica.js +1 -1
- package/dist-es/utils/aspect-ratio-support.css-mistica.js +2 -2
- package/dist-es/video.css-mistica.js +1 -1
- package/dist-es/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +1 -1
- package/package.json +1 -1
- package/src/menu.css.ts +5 -9
- package/src/menu.tsx +82 -64
- package/src/package-version.tsx +1 -1
- package/src/popover.tsx +1 -0
- package/src/sidenav-bar.tsx +9 -2
- package/src/tooltip.css.ts +1 -0
- package/src/tooltip.tsx +94 -90
package/src/menu.tsx
CHANGED
|
@@ -270,8 +270,14 @@ export const MenuSection = ({children}: MenuSectionProps): JSX.Element => {
|
|
|
270
270
|
);
|
|
271
271
|
};
|
|
272
272
|
|
|
273
|
-
const MARGIN_THRESHOLD = 12;
|
|
274
273
|
const MENU_OFFSET_FROM_TARGET = 8;
|
|
274
|
+
const REFLECTED_PLACEMENT = {top: 'bottom', bottom: 'top', left: 'right', right: 'left'} as const;
|
|
275
|
+
const TRANSFORM_ORIGINS = {
|
|
276
|
+
top: 'center bottom',
|
|
277
|
+
bottom: 'center top',
|
|
278
|
+
left: 'right center',
|
|
279
|
+
right: 'left center',
|
|
280
|
+
} as const;
|
|
275
281
|
|
|
276
282
|
type MenuRenderProps = {
|
|
277
283
|
ref: (element: HTMLElement | null) => void;
|
|
@@ -286,19 +292,36 @@ type TargetRenderProps = {
|
|
|
286
292
|
};
|
|
287
293
|
|
|
288
294
|
export type MenuProps = {
|
|
295
|
+
/**
|
|
296
|
+
* Width in pixels. Values are clamped to the range 136–280.
|
|
297
|
+
* The final width never exceeds the viewport width, even if that
|
|
298
|
+
* means going below the 136px minimum.
|
|
299
|
+
*
|
|
300
|
+
* Examples: 100 → 136, 200 → 200, 500 → 280.
|
|
301
|
+
*/
|
|
289
302
|
width?: number;
|
|
290
303
|
renderTarget: (props: TargetRenderProps) => React.ReactNode;
|
|
291
304
|
renderMenu: (props: MenuRenderProps) => React.ReactNode;
|
|
292
305
|
children?: void;
|
|
293
|
-
position?: 'left' | 'right';
|
|
294
306
|
dataAttributes?: DataAttributes;
|
|
295
|
-
}
|
|
307
|
+
} & ExclusifyUnion<
|
|
308
|
+
| {
|
|
309
|
+
/** @deprecated Use placement and alignment instead. */
|
|
310
|
+
position?: 'left' | 'right';
|
|
311
|
+
}
|
|
312
|
+
| {
|
|
313
|
+
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
314
|
+
alignment?: 'start' | 'middle' | 'end';
|
|
315
|
+
}
|
|
316
|
+
>;
|
|
296
317
|
|
|
297
318
|
export const Menu = ({
|
|
298
319
|
renderTarget,
|
|
299
320
|
renderMenu,
|
|
300
321
|
width,
|
|
301
322
|
position = 'left',
|
|
323
|
+
placement = 'bottom',
|
|
324
|
+
alignment = position === 'left' ? 'start' : 'end',
|
|
302
325
|
dataAttributes,
|
|
303
326
|
}: MenuProps): JSX.Element => {
|
|
304
327
|
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
|
@@ -309,11 +332,9 @@ export const Menu = ({
|
|
|
309
332
|
const menuRef = React.useRef<HTMLDivElement | null>(null);
|
|
310
333
|
|
|
311
334
|
const [itemsComputedProps, setItemsComputedProps] = React.useState<{
|
|
312
|
-
left
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
bottom: string;
|
|
316
|
-
maxHeight?: number;
|
|
335
|
+
left: number;
|
|
336
|
+
top: number;
|
|
337
|
+
maxHeight: number;
|
|
317
338
|
transformOrigin: string;
|
|
318
339
|
} | null>(null);
|
|
319
340
|
|
|
@@ -326,44 +347,52 @@ export const Menu = ({
|
|
|
326
347
|
return;
|
|
327
348
|
}
|
|
328
349
|
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (isMenuOnBottom) {
|
|
348
|
-
setItemsComputedProps({
|
|
349
|
-
left: leftDirection,
|
|
350
|
-
right: rightDirection,
|
|
351
|
-
top: `${bottomTargetWithOffset}px`,
|
|
352
|
-
bottom: 'auto',
|
|
353
|
-
maxHeight: menuFitsOnBottom ? undefined : availableSpaceOnBottom,
|
|
354
|
-
transformOrigin: 'center top',
|
|
355
|
-
});
|
|
356
|
-
} else {
|
|
357
|
-
setItemsComputedProps({
|
|
358
|
-
left: leftDirection,
|
|
359
|
-
right: rightDirection,
|
|
360
|
-
top: 'auto',
|
|
361
|
-
bottom: `${windowSize.height - topTargetWithOffset}px`,
|
|
362
|
-
maxHeight: menuFitsOnTop ? undefined : availableSpaceOnTop,
|
|
363
|
-
transformOrigin: 'center bottom',
|
|
364
|
-
});
|
|
350
|
+
const availableSpace = {
|
|
351
|
+
top: targetRect.top - MENU_OFFSET_FROM_TARGET,
|
|
352
|
+
bottom: windowSize.height - targetRect.bottom - MENU_OFFSET_FROM_TARGET,
|
|
353
|
+
left: targetRect.left - MENU_OFFSET_FROM_TARGET,
|
|
354
|
+
right: windowSize.width - targetRect.right - MENU_OFFSET_FROM_TARGET,
|
|
355
|
+
};
|
|
356
|
+
const menuSize = placement === 'top' || placement === 'bottom' ? menu.scrollHeight : menu.offsetWidth;
|
|
357
|
+
const candidateReflectedPlacement = REFLECTED_PLACEMENT[placement];
|
|
358
|
+
let finalPlacement =
|
|
359
|
+
availableSpace[placement] < menuSize &&
|
|
360
|
+
availableSpace[candidateReflectedPlacement] > availableSpace[placement]
|
|
361
|
+
? candidateReflectedPlacement
|
|
362
|
+
: placement;
|
|
363
|
+
if (
|
|
364
|
+
(finalPlacement === 'left' || finalPlacement === 'right') &&
|
|
365
|
+
availableSpace[finalPlacement] < menu.offsetWidth
|
|
366
|
+
) {
|
|
367
|
+
finalPlacement = availableSpace.bottom >= availableSpace.top ? 'bottom' : 'top';
|
|
365
368
|
}
|
|
366
|
-
|
|
369
|
+
|
|
370
|
+
const isVertical = finalPlacement === 'top' || finalPlacement === 'bottom';
|
|
371
|
+
const maxHeight = Math.max(0, isVertical ? availableSpace[finalPlacement] : windowSize.height);
|
|
372
|
+
const heightMenu = Math.min(menu.scrollHeight, maxHeight);
|
|
373
|
+
const widthMenu = menu.offsetWidth;
|
|
374
|
+
const targetStart = isVertical ? targetRect.left : targetRect.top;
|
|
375
|
+
const targetSize = isVertical ? targetRect.width : targetRect.height;
|
|
376
|
+
const alignmentSize = isVertical ? widthMenu : heightMenu;
|
|
377
|
+
const alignmentOffset = alignment === 'start' ? 0 : alignment === 'end' ? 1 : 0.5;
|
|
378
|
+
const alignedPosition = targetStart + (targetSize - alignmentSize) * alignmentOffset;
|
|
379
|
+
const shiftedPosition = Math.max(
|
|
380
|
+
0,
|
|
381
|
+
Math.min(alignedPosition, (isVertical ? windowSize.width : windowSize.height) - alignmentSize)
|
|
382
|
+
);
|
|
383
|
+
const positions = {
|
|
384
|
+
top: {left: shiftedPosition, top: targetRect.top - MENU_OFFSET_FROM_TARGET - heightMenu},
|
|
385
|
+
bottom: {left: shiftedPosition, top: targetRect.bottom + MENU_OFFSET_FROM_TARGET},
|
|
386
|
+
left: {left: targetRect.left - MENU_OFFSET_FROM_TARGET - widthMenu, top: shiftedPosition},
|
|
387
|
+
right: {left: targetRect.right + MENU_OFFSET_FROM_TARGET, top: shiftedPosition},
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
setItemsComputedProps({
|
|
391
|
+
...positions[finalPlacement],
|
|
392
|
+
maxHeight,
|
|
393
|
+
transformOrigin: TRANSFORM_ORIGINS[finalPlacement],
|
|
394
|
+
});
|
|
395
|
+
}, [placement, alignment, isMenuOpen, menu, target, width, windowSize]);
|
|
367
396
|
|
|
368
397
|
const targetProps = React.useMemo(
|
|
369
398
|
() => ({
|
|
@@ -504,29 +533,18 @@ export const Menu = ({
|
|
|
504
533
|
<div
|
|
505
534
|
style={{
|
|
506
535
|
...applyCssVars({
|
|
536
|
+
[styles.vars.maxWidth]:
|
|
537
|
+
`${Math.min(styles.MENU_MAX_WIDTH, Math.max(0, windowSize.width))}px`,
|
|
538
|
+
...(width && {
|
|
539
|
+
[styles.vars.width]: `${width}px`,
|
|
540
|
+
}),
|
|
507
541
|
...(itemsComputedProps
|
|
508
542
|
? {
|
|
509
|
-
[styles.vars.top]: itemsComputedProps.top
|
|
510
|
-
[styles.vars.
|
|
543
|
+
[styles.vars.top]: `${itemsComputedProps.top}px`,
|
|
544
|
+
[styles.vars.left]: `${itemsComputedProps.left}px`,
|
|
545
|
+
[styles.vars.maxHeight]: `${itemsComputedProps.maxHeight}px`,
|
|
511
546
|
[styles.vars.transformOrigin]:
|
|
512
547
|
itemsComputedProps.transformOrigin,
|
|
513
|
-
|
|
514
|
-
...(itemsComputedProps.left !== undefined && {
|
|
515
|
-
[styles.vars.left]: `${itemsComputedProps.left}px`,
|
|
516
|
-
}),
|
|
517
|
-
|
|
518
|
-
...(itemsComputedProps.right !== undefined && {
|
|
519
|
-
[styles.vars.right]: `${itemsComputedProps.right}px`,
|
|
520
|
-
}),
|
|
521
|
-
|
|
522
|
-
...(itemsComputedProps.maxHeight !== undefined && {
|
|
523
|
-
[styles.vars.maxHeight]:
|
|
524
|
-
`${itemsComputedProps.maxHeight}px`,
|
|
525
|
-
}),
|
|
526
|
-
|
|
527
|
-
...(width !== undefined && {
|
|
528
|
-
[styles.vars.width]: width ? `${width}px` : '',
|
|
529
|
-
}),
|
|
530
548
|
}
|
|
531
549
|
: {}),
|
|
532
550
|
}),
|
package/src/package-version.tsx
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// DO NOT EDIT THIS FILE. It's autogenerated by set-version.js
|
|
2
|
-
export const PACKAGE_VERSION = '17.
|
|
2
|
+
export const PACKAGE_VERSION = '17.7.0' as string;
|
package/src/popover.tsx
CHANGED
package/src/sidenav-bar.tsx
CHANGED
|
@@ -446,8 +446,15 @@ const SidenavBar = ({
|
|
|
446
446
|
const footerDividerSentinelRef = React.useRef<HTMLDivElement>(null);
|
|
447
447
|
const bodyRef = React.useRef<HTMLDivElement>(null);
|
|
448
448
|
|
|
449
|
+
// The mobile treatment replaces the whole rail, so a trip to a narrow screen unmounts the body and
|
|
450
|
+
// its sentinels. Both dividers go back to their rest state, and the observers of the new body decide
|
|
451
|
+
// again: a divider earned by a scroll before the trip does not belong to the body that comes back.
|
|
449
452
|
React.useEffect(() => {
|
|
450
|
-
if (!bodyRef.current)
|
|
453
|
+
if (!bodyRef.current) {
|
|
454
|
+
setShowHeaderDivider(false);
|
|
455
|
+
setShowFooterDivider(false);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
451
458
|
|
|
452
459
|
// The sentinels have no height, and at rest they sit exactly on the edge of the scrollport. A
|
|
453
460
|
// fractional layout (a boxed sidenav measures 100vh minus its margins) can leave them half a
|
|
@@ -478,7 +485,7 @@ const SidenavBar = ({
|
|
|
478
485
|
headerObserver.disconnect();
|
|
479
486
|
footerObserver.disconnect();
|
|
480
487
|
};
|
|
481
|
-
}, []);
|
|
488
|
+
}, [isTabletOrSmaller]);
|
|
482
489
|
|
|
483
490
|
// The rail is travelling between its two widths. See `columnsWhileMoving`.
|
|
484
491
|
const isMoving = isSidenavMoving(collapseState);
|
package/src/tooltip.css.ts
CHANGED
package/src/tooltip.tsx
CHANGED
|
@@ -6,7 +6,6 @@ import {Transition} from 'react-transition-group';
|
|
|
6
6
|
import * as styles from './tooltip.css';
|
|
7
7
|
import Stack from './stack';
|
|
8
8
|
import {Text2} from './text';
|
|
9
|
-
import {getCssVarValue} from './utils/dom';
|
|
10
9
|
import {ESC, TAB} from './utils/keys';
|
|
11
10
|
import {isTouchableDevice} from './utils/environment';
|
|
12
11
|
import {isEqual} from './utils/helpers';
|
|
@@ -41,7 +40,9 @@ const TOOLTIP_ENTER_TRANSITION_DELAY_IN_MS = 500;
|
|
|
41
40
|
const TOOLTIP_EXIT_TRANSITION_DURATION_IN_MS = 100;
|
|
42
41
|
const ARROW_SIZE = 20;
|
|
43
42
|
const TOOLTIP_OFFSET_FROM_TARGET = 6;
|
|
44
|
-
const
|
|
43
|
+
const TOOLTIP_BORDER_SIZE = 1;
|
|
44
|
+
const TOOLTIP_PADDING_FROM_TARGET = TOOLTIP_OFFSET_FROM_TARGET + ARROW_SIZE / 2 + TOOLTIP_BORDER_SIZE;
|
|
45
|
+
const REFLECTED_POSITION = {top: 'bottom', bottom: 'top', left: 'right', right: 'left'} as const;
|
|
45
46
|
|
|
46
47
|
type Position = 'top' | 'bottom' | 'left' | 'right';
|
|
47
48
|
|
|
@@ -79,62 +80,36 @@ const getFinalPosition = (
|
|
|
79
80
|
tooltip: HTMLElement | undefined | null,
|
|
80
81
|
position: Position,
|
|
81
82
|
windowHeight: number,
|
|
82
|
-
windowWidth: number
|
|
83
|
+
windowWidth: number,
|
|
84
|
+
contentHeight?: number
|
|
83
85
|
): Position | undefined => {
|
|
84
86
|
if (!targetRect || !tooltip) {
|
|
85
87
|
return undefined;
|
|
86
88
|
}
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
89
|
+
const availableSpace = {
|
|
90
|
+
top: targetRect.top,
|
|
91
|
+
bottom: windowHeight - targetRect.bottom,
|
|
92
|
+
left: targetRect.left,
|
|
93
|
+
right: windowWidth - targetRect.right,
|
|
94
|
+
};
|
|
95
|
+
const dimensions = getElementDimensionsWithoutPadding(tooltip);
|
|
96
|
+
const width = dimensions.width;
|
|
97
|
+
const height = contentHeight === undefined ? dimensions.height : contentHeight + 2 * TOOLTIP_BORDER_SIZE;
|
|
98
|
+
const size = (position === 'top' || position === 'bottom' ? height : width) + TOOLTIP_PADDING_FROM_TARGET;
|
|
99
|
+
const reflected = REFLECTED_POSITION[position];
|
|
100
|
+
const candidateFinalPosition =
|
|
101
|
+
availableSpace[position] < size && availableSpace[reflected] > availableSpace[position]
|
|
102
|
+
? reflected
|
|
103
|
+
: position;
|
|
104
|
+
|
|
105
|
+
if (
|
|
106
|
+
(candidateFinalPosition === 'left' || candidateFinalPosition === 'right') &&
|
|
107
|
+
availableSpace[candidateFinalPosition] < size
|
|
108
|
+
) {
|
|
109
|
+
return availableSpace.bottom > availableSpace.top ? 'bottom' : 'top';
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
|
|
107
|
-
case 'left':
|
|
108
|
-
if (fitsHorizontal) {
|
|
109
|
-
return tooltipWidth <= availableSpaceOnLeft ? 'left' : 'right';
|
|
110
|
-
} else {
|
|
111
|
-
return availableSpaceOnBottom > availableSpaceOnTop ? 'bottom' : 'top';
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
case 'right':
|
|
115
|
-
if (fitsHorizontal) {
|
|
116
|
-
return tooltipWidth <= availableSpaceOnRight ? 'right' : 'left';
|
|
117
|
-
} else {
|
|
118
|
-
return availableSpaceOnBottom > availableSpaceOnTop ? 'bottom' : 'top';
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
case 'top':
|
|
122
|
-
if (fitsVertical) {
|
|
123
|
-
return tooltipHeight <= availableSpaceOnTop ? 'top' : 'bottom';
|
|
124
|
-
} else {
|
|
125
|
-
return availableSpaceOnLeft > availableSpaceOnRight ? 'left' : 'right';
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
case 'bottom':
|
|
129
|
-
if (fitsVertical) {
|
|
130
|
-
return tooltipHeight <= availableSpaceOnBottom ? 'bottom' : 'top';
|
|
131
|
-
} else {
|
|
132
|
-
return availableSpaceOnLeft > availableSpaceOnRight ? 'left' : 'right';
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
default:
|
|
136
|
-
return undefined;
|
|
137
|
-
}
|
|
112
|
+
return candidateFinalPosition;
|
|
138
113
|
};
|
|
139
114
|
|
|
140
115
|
type Props = {
|
|
@@ -145,6 +120,7 @@ type Props = {
|
|
|
145
120
|
targetStyle?: React.CSSProperties;
|
|
146
121
|
title?: string;
|
|
147
122
|
position?: Position;
|
|
123
|
+
alignment?: 'start' | 'middle' | 'end';
|
|
148
124
|
width?: number;
|
|
149
125
|
delay?: boolean;
|
|
150
126
|
dataAttributes?: DataAttributes;
|
|
@@ -157,6 +133,7 @@ type BaseTooltipProps = {
|
|
|
157
133
|
target: React.ReactNode;
|
|
158
134
|
targetStyle?: React.CSSProperties;
|
|
159
135
|
position?: Position;
|
|
136
|
+
alignment?: 'start' | 'middle' | 'end';
|
|
160
137
|
width?: number;
|
|
161
138
|
delay?: boolean;
|
|
162
139
|
dataAttributes?: DataAttributes;
|
|
@@ -174,6 +151,7 @@ export const BaseTooltip = ({
|
|
|
174
151
|
targetStyle,
|
|
175
152
|
width,
|
|
176
153
|
position = 'top',
|
|
154
|
+
alignment = 'middle',
|
|
177
155
|
dataAttributes,
|
|
178
156
|
delay = true,
|
|
179
157
|
centerContent,
|
|
@@ -183,7 +161,12 @@ export const BaseTooltip = ({
|
|
|
183
161
|
hasPointerInteractionOnly = false,
|
|
184
162
|
trackingEvent,
|
|
185
163
|
}: BaseTooltipProps): JSX.Element => {
|
|
186
|
-
const {texts, t} = useTheme();
|
|
164
|
+
const {texts, t, borderRadii} = useTheme();
|
|
165
|
+
const arrowOffsetFromBorder = parseFloat(borderRadii.popup);
|
|
166
|
+
const minSize = Math.max(
|
|
167
|
+
styles.CONTENT_MIN_WIDTH,
|
|
168
|
+
2 * (arrowOffsetFromBorder + TOOLTIP_BORDER_SIZE) + ARROW_SIZE
|
|
169
|
+
);
|
|
187
170
|
const tooltipId = React.useId();
|
|
188
171
|
const {openTooltipId} = useTooltipState();
|
|
189
172
|
const {openTooltip, closeTooltip} = useSetTooltipState();
|
|
@@ -194,6 +177,7 @@ export const BaseTooltip = ({
|
|
|
194
177
|
|
|
195
178
|
const targetRef = React.useRef<Element | null>(null);
|
|
196
179
|
const tooltipRef = React.useRef<HTMLDivElement | null>(null);
|
|
180
|
+
const contentRef = React.useRef<HTMLDivElement | null>(null);
|
|
197
181
|
const [tooltip, setTooltip] = React.useState<HTMLElement | null>(null);
|
|
198
182
|
const isTouchable = isTouchableDevice();
|
|
199
183
|
const tooltipEnterDelay = delay ? TOOLTIP_ENTER_TRANSITION_DELAY_IN_MS : 0;
|
|
@@ -208,7 +192,26 @@ export const BaseTooltip = ({
|
|
|
208
192
|
|
|
209
193
|
const targetRect = useBoundingRect(targetRef, isTooltipOpen);
|
|
210
194
|
const tooltipRect = useBoundingRect(tooltipRef, isTooltipOpen, true);
|
|
195
|
+
const contentRect = useBoundingRect(contentRef, isTooltipOpen, true);
|
|
211
196
|
const windowSize = useWindowSize();
|
|
197
|
+
const currentPosition = getFinalPosition(
|
|
198
|
+
targetRect,
|
|
199
|
+
tooltip,
|
|
200
|
+
position,
|
|
201
|
+
windowSize.height,
|
|
202
|
+
windowSize.width,
|
|
203
|
+
contentRect?.height
|
|
204
|
+
);
|
|
205
|
+
const maxHeight = Math.max(
|
|
206
|
+
0,
|
|
207
|
+
(currentPosition === 'top'
|
|
208
|
+
? targetRect?.top ?? windowSize.height
|
|
209
|
+
: currentPosition === 'bottom'
|
|
210
|
+
? windowSize.height - (targetRect?.bottom ?? 0)
|
|
211
|
+
: windowSize.height) -
|
|
212
|
+
(currentPosition === 'top' || currentPosition === 'bottom' ? TOOLTIP_PADDING_FROM_TARGET : 0) -
|
|
213
|
+
2 * TOOLTIP_BORDER_SIZE
|
|
214
|
+
);
|
|
212
215
|
|
|
213
216
|
const resetTooltipInteractions = React.useCallback(() => {
|
|
214
217
|
setIsFocused(false);
|
|
@@ -227,15 +230,7 @@ export const BaseTooltip = ({
|
|
|
227
230
|
return;
|
|
228
231
|
}
|
|
229
232
|
|
|
230
|
-
|
|
231
|
-
targetRect,
|
|
232
|
-
tooltip,
|
|
233
|
-
position,
|
|
234
|
-
windowSize.height,
|
|
235
|
-
windowSize.width
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
if (!finalPosition || !targetRect) {
|
|
233
|
+
if (!currentPosition || !targetRect) {
|
|
239
234
|
setTooltipComputedStyles(undefined);
|
|
240
235
|
setArrowComputedStyles(undefined);
|
|
241
236
|
resetTooltipInteractions();
|
|
@@ -250,22 +245,29 @@ export const BaseTooltip = ({
|
|
|
250
245
|
|
|
251
246
|
const maxLeftOffset = windowSize.width - tooltipWidth;
|
|
252
247
|
const maxTopOffset = windowSize.height - tooltipHeight;
|
|
248
|
+
const alignmentRatio = alignment === 'start' ? 0 : alignment === 'end' ? 1 : 0.5;
|
|
249
|
+
const leftPropInPx = Math.max(
|
|
250
|
+
0,
|
|
251
|
+
Math.min(maxLeftOffset, left + (right - left - tooltipWidth) * alignmentRatio)
|
|
252
|
+
);
|
|
253
|
+
const topPropInPx = Math.max(
|
|
254
|
+
0,
|
|
255
|
+
Math.min(maxTopOffset, top + (bottom - top - tooltipHeight) * alignmentRatio)
|
|
256
|
+
);
|
|
253
257
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
switch (finalPosition) {
|
|
258
|
+
switch (currentPosition) {
|
|
257
259
|
case 'top':
|
|
258
260
|
tooltipStyles = {
|
|
259
|
-
left:
|
|
261
|
+
left: leftPropInPx,
|
|
260
262
|
top: top - tooltipHeight - ARROW_SIZE / 2,
|
|
261
263
|
padding: `0px 0px ${TOOLTIP_PADDING_FROM_TARGET}px 0px`,
|
|
262
264
|
};
|
|
263
265
|
|
|
264
266
|
arrowStyles = {
|
|
265
267
|
left: Math.max(
|
|
266
|
-
|
|
268
|
+
leftPropInPx + arrowOffsetFromBorder,
|
|
267
269
|
Math.min(
|
|
268
|
-
|
|
270
|
+
leftPropInPx + tooltipWidth - arrowOffsetFromBorder - ARROW_SIZE,
|
|
269
271
|
(left + right - ARROW_SIZE) / 2
|
|
270
272
|
)
|
|
271
273
|
),
|
|
@@ -276,16 +278,16 @@ export const BaseTooltip = ({
|
|
|
276
278
|
|
|
277
279
|
case 'bottom':
|
|
278
280
|
tooltipStyles = {
|
|
279
|
-
left:
|
|
281
|
+
left: leftPropInPx,
|
|
280
282
|
top: bottom - TOOLTIP_OFFSET_FROM_TARGET,
|
|
281
283
|
padding: `${TOOLTIP_PADDING_FROM_TARGET}px 0px 0px 0px`,
|
|
282
284
|
};
|
|
283
285
|
|
|
284
286
|
arrowStyles = {
|
|
285
287
|
left: Math.max(
|
|
286
|
-
|
|
288
|
+
leftPropInPx + arrowOffsetFromBorder,
|
|
287
289
|
Math.min(
|
|
288
|
-
|
|
290
|
+
leftPropInPx + tooltipWidth - arrowOffsetFromBorder - ARROW_SIZE,
|
|
289
291
|
(left + right - ARROW_SIZE) / 2
|
|
290
292
|
)
|
|
291
293
|
),
|
|
@@ -298,15 +300,15 @@ export const BaseTooltip = ({
|
|
|
298
300
|
case 'left':
|
|
299
301
|
tooltipStyles = {
|
|
300
302
|
left: left - tooltipWidth - ARROW_SIZE / 2,
|
|
301
|
-
top:
|
|
303
|
+
top: topPropInPx,
|
|
302
304
|
padding: `0px ${TOOLTIP_PADDING_FROM_TARGET}px 0px 0px`,
|
|
303
305
|
};
|
|
304
306
|
|
|
305
307
|
arrowStyles = {
|
|
306
308
|
top: Math.max(
|
|
307
|
-
|
|
309
|
+
topPropInPx + arrowOffsetFromBorder,
|
|
308
310
|
Math.min(
|
|
309
|
-
|
|
311
|
+
topPropInPx + tooltipHeight - arrowOffsetFromBorder - ARROW_SIZE,
|
|
310
312
|
(top + bottom - ARROW_SIZE) / 2
|
|
311
313
|
)
|
|
312
314
|
),
|
|
@@ -318,18 +320,17 @@ export const BaseTooltip = ({
|
|
|
318
320
|
break;
|
|
319
321
|
|
|
320
322
|
case 'right':
|
|
321
|
-
default:
|
|
322
323
|
tooltipStyles = {
|
|
323
324
|
left: right - TOOLTIP_OFFSET_FROM_TARGET,
|
|
324
|
-
top:
|
|
325
|
+
top: topPropInPx,
|
|
325
326
|
padding: `0px 0px 0px ${TOOLTIP_PADDING_FROM_TARGET}px`,
|
|
326
327
|
};
|
|
327
328
|
|
|
328
329
|
arrowStyles = {
|
|
329
330
|
top: Math.max(
|
|
330
|
-
|
|
331
|
+
topPropInPx + arrowOffsetFromBorder,
|
|
331
332
|
Math.min(
|
|
332
|
-
|
|
333
|
+
topPropInPx + tooltipHeight - arrowOffsetFromBorder - ARROW_SIZE,
|
|
333
334
|
(top + bottom - ARROW_SIZE) / 2
|
|
334
335
|
)
|
|
335
336
|
),
|
|
@@ -339,6 +340,10 @@ export const BaseTooltip = ({
|
|
|
339
340
|
};
|
|
340
341
|
|
|
341
342
|
break;
|
|
343
|
+
default: {
|
|
344
|
+
const exhaustiveCheck: never = currentPosition;
|
|
345
|
+
throw new Error(`Invalid tooltip position: ${exhaustiveCheck}`);
|
|
346
|
+
}
|
|
342
347
|
}
|
|
343
348
|
|
|
344
349
|
/**
|
|
@@ -366,10 +371,12 @@ export const BaseTooltip = ({
|
|
|
366
371
|
targetRect,
|
|
367
372
|
tooltipRect,
|
|
368
373
|
isTooltipOpen,
|
|
369
|
-
|
|
374
|
+
currentPosition,
|
|
375
|
+
alignment,
|
|
370
376
|
windowSize,
|
|
371
377
|
tooltipComputedStyles,
|
|
372
378
|
arrowComputedStyles,
|
|
379
|
+
arrowOffsetFromBorder,
|
|
373
380
|
isTouchable,
|
|
374
381
|
tooltipId,
|
|
375
382
|
resetTooltipInteractions,
|
|
@@ -438,17 +445,8 @@ export const BaseTooltip = ({
|
|
|
438
445
|
hasControlledValue,
|
|
439
446
|
]);
|
|
440
447
|
|
|
441
|
-
const currentPosition = getFinalPosition(
|
|
442
|
-
targetRect,
|
|
443
|
-
tooltip,
|
|
444
|
-
position,
|
|
445
|
-
windowSize.height,
|
|
446
|
-
windowSize.width
|
|
447
|
-
);
|
|
448
|
-
|
|
449
448
|
// by default, center content only if tooltip has minimum possible width
|
|
450
|
-
const hasCenteredContent =
|
|
451
|
-
centerContent !== undefined ? centerContent : tooltipRect?.width === styles.CONTENT_MIN_WIDTH;
|
|
449
|
+
const hasCenteredContent = centerContent !== undefined ? centerContent : tooltipRect?.width === minSize;
|
|
452
450
|
|
|
453
451
|
return (
|
|
454
452
|
<>
|
|
@@ -568,17 +566,23 @@ export const BaseTooltip = ({
|
|
|
568
566
|
className={styles.tooltip}
|
|
569
567
|
style={{
|
|
570
568
|
width,
|
|
569
|
+
minWidth: minSize,
|
|
570
|
+
minHeight:
|
|
571
|
+
currentPosition === 'left' || currentPosition === 'right'
|
|
572
|
+
? minSize
|
|
573
|
+
: undefined,
|
|
571
574
|
...getBorderStyle(themeVariant),
|
|
572
575
|
maxWidth: Math.min(TOOLTIP_MAX_WIDTH, windowSize.width),
|
|
573
576
|
}}
|
|
574
577
|
>
|
|
575
578
|
<div
|
|
579
|
+
style={{maxHeight}}
|
|
576
580
|
className={classNames(styles.contentContainer, {
|
|
577
581
|
[styles.tooltipCenter]: hasCenteredContent,
|
|
578
582
|
})}
|
|
579
583
|
>
|
|
580
584
|
<ThemeVariant variant="default">
|
|
581
|
-
{content}
|
|
585
|
+
<div ref={contentRef}>{content}</div>
|
|
582
586
|
|
|
583
587
|
{onClose && (
|
|
584
588
|
<div className={styles.closeButtonIcon}>
|