@telefonica/mistica 17.2.0 → 17.4.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/blau.css +30 -16
- package/css/community/cyber.css +1491 -0
- package/css/esimflag.css +41 -18
- package/css/mistica-common.css +6 -0
- package/css/mistica.css +1 -1
- package/css/movistar.css +35 -18
- package/css/o2.css +32 -18
- package/css/telefonica.css +44 -18
- package/css/vivo-evolution.css +72 -20
- package/css/vivo.css +32 -18
- package/dist/accordion.css-mistica.js +10 -10
- package/dist/align.css-mistica.js +1 -1
- package/dist/autocomplete.css-mistica.js +4 -4
- 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/boxed.d.ts +9 -0
- package/dist/boxed.js +18 -15
- package/dist/button-group.css-mistica.js +1 -1
- package/dist/button-layout.css-mistica.js +19 -19
- package/dist/button.css-mistica.js +50 -50
- package/dist/callout.css-mistica.js +13 -13
- package/dist/card-internal.css-mistica.js +22 -22
- package/dist/carousel.css-mistica.js +30 -30
- package/dist/checkbox.css-mistica.js +15 -15
- package/dist/chip.css-mistica.js +18 -18
- package/dist/circle.css-mistica.js +2 -2
- package/dist/community/advanced-data-card.css-mistica.js +19 -19
- package/dist/community/ai-card.css-mistica.js +9 -9
- package/dist/community/blocks.css-mistica.js +3 -3
- package/dist/community/cyber/example-component.css-mistica.js +1 -1
- package/dist/community/example-component.css-mistica.js +1 -1
- package/dist/community/skins/cyber-skin.d.ts +5 -0
- package/dist/community/skins/cyber-skin.js +289 -256
- package/dist/counter.css-mistica.js +6 -6
- package/dist/cover-hero.css-mistica.js +7 -7
- 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 +10 -10
- package/dist/divider.css-mistica.js +5 -5
- package/dist/double-field.css-mistica.js +2 -2
- package/dist/drawer.css-mistica.js +4 -4
- package/dist/empty-state-card.css-mistica.js +1 -1
- package/dist/empty-state.css-mistica.js +8 -8
- package/dist/fade-in.css-mistica.js +1 -1
- package/dist/feedback.css-mistica.js +5 -5
- package/dist/file-upload.css-mistica.js +10 -10
- package/dist/fixed-footer-layout.css-mistica.js +6 -6
- package/dist/form.css-mistica.js +1 -1
- package/dist/generated/mistica-icons/icon-panel-collapse-regular.js +165 -0
- package/dist/generated/mistica-icons/icon-panel-expand-regular.js +159 -0
- 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 +4 -4
- 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 +8 -8
- package/dist/index.d.ts +5 -0
- package/dist/index.js +10 -1
- package/dist/inline.css-mistica.js +11 -11
- package/dist/list.css-mistica.js +40 -28
- package/dist/list.css.d.ts +2 -0
- package/dist/list.d.ts +3 -1
- package/dist/list.js +176 -165
- package/dist/loading-bar.css-mistica.js +1 -1
- package/dist/loading-screen.css-mistica.js +12 -12
- package/dist/logo-movistar.js +1 -1
- package/dist/logo.css-mistica.js +5 -5
- package/dist/menu.css-mistica.js +13 -13
- package/dist/mobile-navigation-menu.d.ts +26 -0
- package/dist/mobile-navigation-menu.js +145 -0
- package/dist/mosaic.css-mistica.js +1 -1
- package/dist/navigation-bar.css-mistica.js +31 -97
- package/dist/navigation-bar.css.d.ts +0 -24
- package/dist/navigation-bar.d.ts +3 -38
- package/dist/navigation-bar.js +399 -586
- package/dist/navigation-breadcrumbs.css-mistica.js +1 -1
- package/dist/navigation-shared.css-mistica.js +79 -0
- package/dist/navigation-shared.css.d.ts +26 -0
- package/dist/navigation-shared.d.ts +66 -0
- package/dist/navigation-shared.js +264 -0
- package/dist/package-version.js +1 -1
- package/dist/pagination.css-mistica.js +22 -22
- package/dist/pin-field.css-mistica.js +4 -4
- package/dist/popover.css-mistica.js +1 -1
- package/dist/portal.d.ts +1 -1
- package/dist/progress-bar.css-mistica.js +7 -7
- package/dist/radio-button.css-mistica.js +27 -27
- package/dist/rating.css-mistica.js +4 -4
- 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 +21 -21
- package/dist/sheet-action-row.css-mistica.js +1 -1
- package/dist/sheet-common.css-mistica.js +7 -7
- package/dist/sheet-info.css-mistica.js +1 -1
- package/dist/sidenav-bar-context.d.ts +25 -0
- package/dist/sidenav-bar-context.js +72 -0
- package/dist/sidenav-bar-data.d.ts +15 -0
- package/dist/sidenav-bar-data.js +38 -0
- package/dist/sidenav-bar-first-level-item.d.ts +42 -0
- package/dist/sidenav-bar-first-level-item.js +174 -0
- package/dist/sidenav-bar-keyboard.d.ts +13 -0
- package/dist/sidenav-bar-keyboard.js +314 -0
- package/dist/sidenav-bar-layout.css-mistica.js +28 -0
- package/dist/sidenav-bar-layout.css.d.ts +5 -0
- package/dist/sidenav-bar-layout.d.ts +22 -0
- package/dist/sidenav-bar-layout.js +103 -0
- package/dist/sidenav-bar-mobile.d.ts +24 -0
- package/dist/sidenav-bar-mobile.js +332 -0
- package/dist/sidenav-bar-motion.d.ts +32 -0
- package/dist/sidenav-bar-motion.js +84 -0
- package/dist/sidenav-bar-panel.d.ts +31 -0
- package/dist/sidenav-bar-panel.js +208 -0
- package/dist/sidenav-bar-row.d.ts +82 -0
- package/dist/sidenav-bar-row.js +230 -0
- package/dist/sidenav-bar-second-level-item.d.ts +43 -0
- package/dist/sidenav-bar-second-level-item.js +89 -0
- package/dist/sidenav-bar-section.d.ts +23 -0
- package/dist/sidenav-bar-section.js +167 -0
- package/dist/sidenav-bar-types.d.ts +192 -0
- package/dist/sidenav-bar-types.js +11 -0
- package/dist/sidenav-bar.css-mistica.js +325 -0
- package/dist/sidenav-bar.css.d.ts +93 -0
- package/dist/sidenav-bar.d.ts +102 -0
- package/dist/sidenav-bar.js +492 -0
- package/dist/skeletons.css-mistica.js +8 -8
- package/dist/skins/blau.d.ts +5 -0
- package/dist/skins/blau.js +12 -8
- package/dist/skins/esimflag.d.ts +5 -0
- package/dist/skins/esimflag.js +13 -9
- package/dist/skins/movistar.d.ts +5 -0
- package/dist/skins/movistar.js +13 -9
- package/dist/skins/o2.d.ts +5 -0
- package/dist/skins/o2.js +13 -9
- package/dist/skins/skin-contract.css-mistica.js +735 -731
- package/dist/skins/skin-contract.css.d.ts +16 -12
- package/dist/skins/telefonica.d.ts +5 -0
- package/dist/skins/telefonica.js +13 -9
- package/dist/skins/types/colors.d.ts +13 -6
- package/dist/skins/vivo-evolution.d.ts +5 -0
- package/dist/skins/vivo-evolution.js +14 -10
- package/dist/skins/vivo.d.ts +5 -0
- package/dist/skins/vivo.js +13 -9
- package/dist/skip-link.css-mistica.js +2 -2
- package/dist/slider.css-mistica.js +24 -24
- package/dist/snackbar.css-mistica.js +14 -14
- 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 +9 -9
- package/dist/switch-component.css-mistica.js +48 -42
- package/dist/switch-component.css.d.ts +2 -0
- package/dist/switch-component.js +18 -18
- package/dist/table.css-mistica.js +9 -9
- package/dist/tabs.css-mistica.js +22 -22
- package/dist/tag.css-mistica.js +3 -3
- package/dist/text-field-base.css-mistica.js +19 -19
- package/dist/text-field-components.css-mistica.js +10 -10
- package/dist/text-link.css-mistica.js +6 -6
- package/dist/text-tokens.d.ts +6 -0
- package/dist/text-tokens.js +157 -133
- package/dist/text.css-mistica.js +6 -6
- package/dist/timeline.css-mistica.js +11 -11
- package/dist/timer.css-mistica.js +6 -6
- package/dist/tooltip.css-mistica.js +2 -2
- package/dist/touchable.css-mistica.js +4 -4
- 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 +7 -7
- package/dist-es/align.css-mistica.js +1 -1
- package/dist-es/autocomplete.css-mistica.js +2 -2
- 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/boxed.js +25 -25
- package/dist-es/button-group.css-mistica.js +1 -1
- package/dist-es/button-layout.css-mistica.js +16 -16
- package/dist-es/button.css-mistica.js +38 -38
- package/dist-es/callout.css-mistica.js +12 -12
- package/dist-es/card-internal.css-mistica.js +17 -17
- package/dist-es/carousel.css-mistica.js +10 -10
- package/dist-es/checkbox.css-mistica.js +13 -13
- package/dist-es/chip.css-mistica.js +17 -17
- package/dist-es/circle.css-mistica.js +2 -2
- package/dist-es/community/advanced-data-card.css-mistica.js +7 -7
- package/dist-es/community/ai-card.css-mistica.js +3 -3
- package/dist-es/community/blocks.css-mistica.js +2 -2
- package/dist-es/community/cyber/example-component.css-mistica.js +1 -1
- package/dist-es/community/example-component.css-mistica.js +1 -1
- package/dist-es/community/skins/cyber-skin.js +289 -256
- package/dist-es/counter.css-mistica.js +2 -2
- package/dist-es/cover-hero.css-mistica.js +4 -4
- 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 +5 -5
- 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 +2 -2
- package/dist-es/empty-state-card.css-mistica.js +1 -1
- package/dist-es/empty-state.css-mistica.js +7 -7
- package/dist-es/fade-in.css-mistica.js +1 -1
- package/dist-es/feedback.css-mistica.js +2 -2
- package/dist-es/file-upload.css-mistica.js +8 -8
- package/dist-es/fixed-footer-layout.css-mistica.js +3 -3
- package/dist-es/form.css-mistica.js +1 -1
- package/dist-es/generated/mistica-icons/icon-panel-collapse-regular.js +115 -0
- package/dist-es/generated/mistica-icons/icon-panel-expand-regular.js +109 -0
- 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 +3 -3
- 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/index.js +2279 -2276
- package/dist-es/inline.css-mistica.js +11 -11
- package/dist-es/list.css-mistica.js +8 -2
- package/dist-es/list.js +275 -264
- package/dist-es/loading-bar.css-mistica.js +1 -1
- package/dist-es/loading-screen.css-mistica.js +5 -5
- package/dist-es/logo-movistar.js +1 -1
- package/dist-es/logo.css-mistica.js +5 -5
- package/dist-es/menu.css-mistica.js +13 -13
- package/dist-es/mobile-navigation-menu.js +90 -0
- package/dist-es/mosaic.css-mistica.js +1 -1
- package/dist-es/navigation-bar.css-mistica.js +11 -20
- package/dist-es/navigation-bar.js +469 -656
- package/dist-es/navigation-breadcrumbs.css-mistica.js +1 -1
- package/dist-es/navigation-shared.css-mistica.js +11 -0
- package/dist-es/navigation-shared.js +192 -0
- package/dist-es/package-version.js +1 -1
- package/dist-es/pagination.css-mistica.js +19 -19
- package/dist-es/pin-field.css-mistica.js +2 -2
- package/dist-es/popover.css-mistica.js +1 -1
- package/dist-es/progress-bar.css-mistica.js +7 -7
- package/dist-es/radio-button.css-mistica.js +22 -22
- package/dist-es/rating.css-mistica.js +3 -3
- 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 +18 -18
- package/dist-es/sheet-action-row.css-mistica.js +1 -1
- package/dist-es/sheet-common.css-mistica.js +2 -2
- package/dist-es/sheet-info.css-mistica.js +1 -1
- package/dist-es/sidenav-bar-context.js +14 -0
- package/dist-es/sidenav-bar-data.js +12 -0
- package/dist-es/sidenav-bar-first-level-item.js +124 -0
- package/dist-es/sidenav-bar-keyboard.js +253 -0
- package/dist-es/sidenav-bar-layout.css-mistica.js +2 -0
- package/dist-es/sidenav-bar-layout.js +86 -0
- package/dist-es/sidenav-bar-mobile.js +277 -0
- package/dist-es/sidenav-bar-motion.js +26 -0
- package/dist-es/sidenav-bar-panel.js +146 -0
- package/dist-es/sidenav-bar-row.js +208 -0
- package/dist-es/sidenav-bar-second-level-item.js +72 -0
- package/dist-es/sidenav-bar-section.js +112 -0
- package/dist-es/sidenav-bar-types.js +2 -0
- package/dist-es/sidenav-bar.css-mistica.js +74 -0
- package/dist-es/sidenav-bar.js +429 -0
- package/dist-es/skeletons.css-mistica.js +7 -7
- package/dist-es/skins/blau.js +12 -8
- package/dist-es/skins/esimflag.js +13 -9
- package/dist-es/skins/movistar.js +13 -9
- package/dist-es/skins/o2.js +13 -9
- package/dist-es/skins/skin-contract.css-mistica.js +735 -731
- package/dist-es/skins/telefonica.js +13 -9
- package/dist-es/skins/vivo-evolution.js +14 -10
- package/dist-es/skins/vivo.js +13 -9
- package/dist-es/skip-link.css-mistica.js +2 -2
- package/dist-es/slider.css-mistica.js +20 -20
- package/dist-es/snackbar.css-mistica.js +5 -5
- 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 +3 -3
- package/dist-es/style.css +1 -1
- package/dist-es/switch-component.css-mistica.js +37 -37
- package/dist-es/switch-component.js +26 -26
- package/dist-es/table.css-mistica.js +9 -9
- package/dist-es/tabs.css-mistica.js +19 -19
- package/dist-es/tag.css-mistica.js +2 -2
- package/dist-es/text-field-base.css-mistica.js +17 -17
- package/dist-es/text-field-components.css-mistica.js +4 -4
- package/dist-es/text-link.css-mistica.js +6 -6
- package/dist-es/text-tokens.js +82 -67
- package/dist-es/text.css-mistica.js +6 -6
- package/dist-es/timeline.css-mistica.js +10 -10
- package/dist-es/timer.css-mistica.js +6 -6
- package/dist-es/tooltip.css-mistica.js +2 -2
- package/dist-es/touchable.css-mistica.js +2 -2
- 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 +3 -2
- package/src/boxed.css.ts +1 -0
- package/src/boxed.tsx +18 -5
- package/src/callout.css.ts +4 -2
- package/src/community/skins/cyber-skin.tsx +127 -439
- package/src/index.tsx +14 -0
- package/src/list.css.ts +36 -1
- package/src/list.tsx +370 -344
- package/src/logo-movistar.tsx +1 -1
- package/src/mobile-navigation-menu.tsx +168 -0
- package/src/navigation-bar.css.ts +2 -267
- package/src/navigation-bar.tsx +77 -370
- package/src/navigation-shared.css.ts +289 -0
- package/src/navigation-shared.tsx +262 -0
- package/src/package-version.tsx +1 -1
- package/src/portal.tsx +1 -1
- package/src/sidenav-bar-context.tsx +41 -0
- package/src/sidenav-bar-data.ts +49 -0
- package/src/sidenav-bar-first-level-item.tsx +203 -0
- package/src/sidenav-bar-keyboard.tsx +393 -0
- package/src/sidenav-bar-layout.css.ts +89 -0
- package/src/sidenav-bar-layout.tsx +61 -0
- package/src/sidenav-bar-mobile.tsx +302 -0
- package/src/sidenav-bar-motion.tsx +70 -0
- package/src/sidenav-bar-panel.tsx +269 -0
- package/src/sidenav-bar-row.tsx +272 -0
- package/src/sidenav-bar-second-level-item.tsx +99 -0
- package/src/sidenav-bar-section.tsx +98 -0
- package/src/sidenav-bar-types.ts +224 -0
- package/src/sidenav-bar.css.ts +1018 -0
- package/src/sidenav-bar.tsx +821 -0
- package/src/skins/blau.tsx +18 -8
- package/src/skins/esimflag.tsx +19 -9
- package/src/skins/movistar.tsx +19 -9
- package/src/skins/o2.tsx +19 -9
- package/src/skins/skin-contract.css.ts +2 -0
- package/src/skins/telefonica.tsx +19 -9
- package/src/skins/types/colors.tsx +14 -6
- package/src/skins/vivo-evolution.tsx +20 -10
- package/src/skins/vivo.tsx +19 -9
- package/src/switch-component.css.ts +20 -0
- package/src/switch-component.tsx +9 -1
- package/src/text-tokens.tsx +24 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import * as styles from './sidenav-bar-layout.css';
|
|
4
|
+
import {InternalResponsiveLayout} from './responsive-layout';
|
|
5
|
+
import {getPrefixedDataAttributes} from './utils/dom';
|
|
6
|
+
import {applyCssVars} from './utils/css';
|
|
7
|
+
|
|
8
|
+
import type {DataAttributes} from './utils/types';
|
|
9
|
+
|
|
10
|
+
type SidenavLayoutProps = {
|
|
11
|
+
/** Content of the sidenav column (typically SidenavBar). */
|
|
12
|
+
sidenav: React.ReactNode;
|
|
13
|
+
/** Content of the main area, which sits beside the sidenav. */
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
/** Layout mode: 'whole-viewport' spans full width, 'centered' wraps in responsive container. @default 'whole-viewport' */
|
|
16
|
+
mode?: 'whole-viewport' | 'centered';
|
|
17
|
+
/**
|
|
18
|
+
* Height in px of a persistent band above the layout (a fixed or sticky top header). The sidenav
|
|
19
|
+
* sticks below it instead of taking the whole viewport. A header that scrolls away with the page
|
|
20
|
+
* needs no offset. @default 0
|
|
21
|
+
*/
|
|
22
|
+
topOffset?: number;
|
|
23
|
+
/** Custom data attributes for testing and tracking. */
|
|
24
|
+
dataAttributes?: DataAttributes;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const SidenavLayout = ({
|
|
28
|
+
sidenav,
|
|
29
|
+
children,
|
|
30
|
+
mode = 'whole-viewport',
|
|
31
|
+
topOffset,
|
|
32
|
+
dataAttributes,
|
|
33
|
+
}: SidenavLayoutProps): JSX.Element => {
|
|
34
|
+
const layout = (
|
|
35
|
+
<div
|
|
36
|
+
className={styles.container}
|
|
37
|
+
style={topOffset ? applyCssVars({[styles.topOffsetVar]: `${topOffset}px`}) : undefined}
|
|
38
|
+
{...getPrefixedDataAttributes({testid: 'SidenavLayout', ...dataAttributes})}
|
|
39
|
+
>
|
|
40
|
+
<div className={styles.sidenav}>{sidenav}</div>
|
|
41
|
+
<div className={styles.content}>{children}</div>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (mode === 'centered') {
|
|
46
|
+
return (
|
|
47
|
+
<InternalResponsiveLayout
|
|
48
|
+
innerDivClassName={styles.centeredResponsiveLayout}
|
|
49
|
+
shouldExpandWhenNested
|
|
50
|
+
>
|
|
51
|
+
{layout}
|
|
52
|
+
</InternalResponsiveLayout>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return layout;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default SidenavLayout;
|
|
60
|
+
export {SidenavLayout};
|
|
61
|
+
export type {SidenavLayoutProps};
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import {
|
|
4
|
+
BurgerMenuIcon,
|
|
5
|
+
Header,
|
|
6
|
+
NavigationBar,
|
|
7
|
+
NavigationBarContentContainer,
|
|
8
|
+
NavigationBarSideMargins,
|
|
9
|
+
} from './navigation-shared';
|
|
10
|
+
import {MobileNavigationMenu} from './mobile-navigation-menu';
|
|
11
|
+
import * as sharedStyles from './navigation-shared.css';
|
|
12
|
+
import ResponsiveLayout, {ResetResponsiveLayout} from './responsive-layout';
|
|
13
|
+
import {Row, RowList} from './list';
|
|
14
|
+
import {Title1, Title3} from './title';
|
|
15
|
+
import Touchable from './touchable';
|
|
16
|
+
import FocusTrap from './focus-trap';
|
|
17
|
+
import Stack from './stack';
|
|
18
|
+
import Box from './box';
|
|
19
|
+
import {Logo} from './logo';
|
|
20
|
+
import {ThemeVariant} from './theme-variant-context';
|
|
21
|
+
import {useDisableBodyScroll, useTheme} from './hooks';
|
|
22
|
+
import {useSetModalState} from './modal-context-provider';
|
|
23
|
+
import {NAVBAR_HEIGHT_MOBILE} from './theme';
|
|
24
|
+
import * as tokens from './text-tokens';
|
|
25
|
+
import {
|
|
26
|
+
isSidenavSection,
|
|
27
|
+
getFirstLevelItems,
|
|
28
|
+
getSidenavSectionTitle,
|
|
29
|
+
renderSidenavSlot,
|
|
30
|
+
renderSidenavLogo,
|
|
31
|
+
} from './sidenav-bar-data';
|
|
32
|
+
|
|
33
|
+
import type {NonDeprecatedVariant} from './theme-variant-context';
|
|
34
|
+
import type {DataAttributes} from './utils/types';
|
|
35
|
+
import type {
|
|
36
|
+
SidenavEntry,
|
|
37
|
+
SidenavFirstLevelItem,
|
|
38
|
+
SidenavSecondLevelItem,
|
|
39
|
+
SidenavLogo,
|
|
40
|
+
SidenavSlot,
|
|
41
|
+
SidenavSlotRenderProps,
|
|
42
|
+
} from './sidenav-bar-types';
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* The mobile sidenav follows the mobile Main Navigation Bar as closely as the spec asks:
|
|
46
|
+
* https://github.com/Telefonica/mistica-design/blob/aweell-generate-figma-specs/specs/sidenav.md
|
|
47
|
+
*
|
|
48
|
+
* It shares the panel of that component (see `MobileNavigationMenu`) and the chrome of its top bar, so
|
|
49
|
+
* both keep the same animation, the same margins and the same tokens. The differences that the spec lists:
|
|
50
|
+
*
|
|
51
|
+
* - An item paints its right slot before the chevron.
|
|
52
|
+
* - A section paints its title, and never its dividers.
|
|
53
|
+
* - The second level shows the label of the parent, and no action of its own, because a parent with
|
|
54
|
+
* children does not navigate.
|
|
55
|
+
* - The footer slot flows at the end of the content of the panel.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
// The top bar matches the mobile main navigation bar: the isotype of the skin at the same size.
|
|
59
|
+
const MOBILE_LOGO_SIZE = 40;
|
|
60
|
+
|
|
61
|
+
const PANEL_TOP_PADDING = 24;
|
|
62
|
+
const FOOTER_SLOT_PADDING = 16;
|
|
63
|
+
const ENTRY_SPACE = 32;
|
|
64
|
+
const SECTION_TITLE_SPACE = 8;
|
|
65
|
+
const BACK_BAR_SPACE = 16;
|
|
66
|
+
|
|
67
|
+
// The top bar and the panel share one focus trap, so the focus moves between the two of them. The group is
|
|
68
|
+
// not the group of the main navigation bar: a page that holds both components traps each one on its own.
|
|
69
|
+
const SIDENAV_FOCUS_TRAP_GROUP = 'sidenav-burger-menu-lock';
|
|
70
|
+
|
|
71
|
+
type SidenavBarMobileProps = {
|
|
72
|
+
/** First-level entries of the panel. */
|
|
73
|
+
entries?: ReadonlyArray<SidenavEntry>;
|
|
74
|
+
/** Accessible name of the navigation landmark. */
|
|
75
|
+
'aria-label': string;
|
|
76
|
+
/** Variant of the top bar. The panel always renders in the default variant. */
|
|
77
|
+
variant: NonDeprecatedVariant;
|
|
78
|
+
/** Logo of the top bar. */
|
|
79
|
+
logo?: SidenavLogo;
|
|
80
|
+
/** Content of the right side of the top bar. */
|
|
81
|
+
headerSlot?: SidenavSlot;
|
|
82
|
+
/** Content of the end of the panel. */
|
|
83
|
+
footerSlot?: SidenavSlot;
|
|
84
|
+
/** ID of the currently selected item. It marks the item for a screen reader, and paints nothing. */
|
|
85
|
+
selectedItemId: string | null;
|
|
86
|
+
/** Called when the user presses an item that navigates. */
|
|
87
|
+
onSelectedItemIdChange?: (id: string | null) => void;
|
|
88
|
+
dataAttributes?: DataAttributes;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const SidenavBarMobile = ({
|
|
92
|
+
entries,
|
|
93
|
+
'aria-label': ariaLabel,
|
|
94
|
+
variant,
|
|
95
|
+
logo,
|
|
96
|
+
headerSlot,
|
|
97
|
+
footerSlot,
|
|
98
|
+
selectedItemId,
|
|
99
|
+
onSelectedItemIdChange,
|
|
100
|
+
dataAttributes,
|
|
101
|
+
}: SidenavBarMobileProps): JSX.Element => {
|
|
102
|
+
const {texts, t} = useTheme();
|
|
103
|
+
const menuId = React.useId();
|
|
104
|
+
const setModalState = useSetModalState();
|
|
105
|
+
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
|
106
|
+
const [disableFocusTrap, setDisableFocusTrap] = React.useState(true);
|
|
107
|
+
const [openedFirstLevelItemId, setOpenedFirstLevelItemId] = React.useState<string | null>(null);
|
|
108
|
+
const [isSecondLevelOpen, setIsSecondLevelOpen] = React.useState(false);
|
|
109
|
+
|
|
110
|
+
useDisableBodyScroll(isMenuOpen);
|
|
111
|
+
|
|
112
|
+
const openMenu = () => {
|
|
113
|
+
setIsMenuOpen(true);
|
|
114
|
+
setModalState({isModalOpen: true});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const closeMenu = React.useCallback(() => {
|
|
118
|
+
setIsMenuOpen(false);
|
|
119
|
+
setModalState({isModalOpen: false});
|
|
120
|
+
}, [setModalState]);
|
|
121
|
+
|
|
122
|
+
// The top bar is not a rail: it reports the expanded state, at rest, to every slot.
|
|
123
|
+
const slotRenderProps: SidenavSlotRenderProps = {collapsed: false, state: 'expanded'};
|
|
124
|
+
const headerSlotElement = renderSidenavSlot(headerSlot, slotRenderProps);
|
|
125
|
+
const footerSlotElement = renderSidenavSlot(footerSlot, slotRenderProps);
|
|
126
|
+
const logoElement = renderSidenavLogo(logo, slotRenderProps, <Logo size={MOBILE_LOGO_SIZE} />);
|
|
127
|
+
|
|
128
|
+
const renderRow = (item: SidenavFirstLevelItem | SidenavSecondLevelItem): JSX.Element => {
|
|
129
|
+
const commonProps = {
|
|
130
|
+
title: item.label,
|
|
131
|
+
right: item.rightSlot,
|
|
132
|
+
'aria-current': selectedItemId === item.id ? ('page' as const) : undefined,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (item.children?.length) {
|
|
136
|
+
return (
|
|
137
|
+
<Row
|
|
138
|
+
key={item.id}
|
|
139
|
+
{...commonProps}
|
|
140
|
+
onPress={() => {
|
|
141
|
+
setOpenedFirstLevelItemId(item.id);
|
|
142
|
+
setIsSecondLevelOpen(true);
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const navigate = async (callback?: () => void | Promise<void>) => {
|
|
149
|
+
onSelectedItemIdChange?.(item.id);
|
|
150
|
+
try {
|
|
151
|
+
await callback?.();
|
|
152
|
+
} finally {
|
|
153
|
+
closeMenu();
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (item.href !== undefined) {
|
|
158
|
+
return (
|
|
159
|
+
<Row
|
|
160
|
+
key={item.id}
|
|
161
|
+
{...commonProps}
|
|
162
|
+
href={item.href}
|
|
163
|
+
newTab={item.newTab}
|
|
164
|
+
onNavigate={() => navigate(item.onNavigate)}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (item.to !== undefined) {
|
|
170
|
+
return (
|
|
171
|
+
<Row
|
|
172
|
+
key={item.id}
|
|
173
|
+
{...commonProps}
|
|
174
|
+
to={item.to}
|
|
175
|
+
newTab={item.newTab}
|
|
176
|
+
onNavigate={() => navigate(item.onNavigate)}
|
|
177
|
+
/>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return <Row key={item.id} {...commonProps} onPress={() => navigate(item.onPress)} />;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const renderEntry = (entry: SidenavEntry, entryIndex: number): JSX.Element => {
|
|
185
|
+
if (!isSidenavSection(entry)) {
|
|
186
|
+
return (
|
|
187
|
+
<ResetResponsiveLayout key={(entry as SidenavFirstLevelItem).id}>
|
|
188
|
+
<RowList>{renderRow(entry as SidenavFirstLevelItem)}</RowList>
|
|
189
|
+
</ResetResponsiveLayout>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// A hidden title paints no heading, and it still names the list of the section.
|
|
194
|
+
const {text: title, isHeadingVisible} = getSidenavSectionTitle(entry.title);
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<Stack space={SECTION_TITLE_SPACE} key={`${title}-${entryIndex}`}>
|
|
198
|
+
{isHeadingVisible && <Title1>{title}</Title1>}
|
|
199
|
+
<ResetResponsiveLayout>
|
|
200
|
+
<RowList aria-label={title}>{entry.items.map((item) => renderRow(item))}</RowList>
|
|
201
|
+
</ResetResponsiveLayout>
|
|
202
|
+
</Stack>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const openedFirstLevelItem = openedFirstLevelItemId
|
|
207
|
+
? getFirstLevelItems(entries ?? []).find((item) => item.id === openedFirstLevelItemId)
|
|
208
|
+
: undefined;
|
|
209
|
+
|
|
210
|
+
const topBar = (
|
|
211
|
+
<Header
|
|
212
|
+
topFixed
|
|
213
|
+
withBorder
|
|
214
|
+
isBurgerMenuOpen={isMenuOpen}
|
|
215
|
+
variant={variant}
|
|
216
|
+
dataAttributes={{testid: 'SidenavBar', ...dataAttributes}}
|
|
217
|
+
>
|
|
218
|
+
<NavigationBarSideMargins wide={false}>
|
|
219
|
+
<NavigationBarContentContainer right={headerSlotElement}>
|
|
220
|
+
<Touchable
|
|
221
|
+
className={sharedStyles.burgerMenuButton}
|
|
222
|
+
aria-live="polite"
|
|
223
|
+
aria-label={
|
|
224
|
+
isMenuOpen
|
|
225
|
+
? texts.closeNavigationMenu || t(tokens.closeNavigationMenu)
|
|
226
|
+
: texts.openNavigationMenu || t(tokens.openNavigationMenu)
|
|
227
|
+
}
|
|
228
|
+
aria-expanded={isMenuOpen}
|
|
229
|
+
aria-controls={menuId}
|
|
230
|
+
onPress={isMenuOpen ? closeMenu : openMenu}
|
|
231
|
+
>
|
|
232
|
+
<BurgerMenuIcon isOpen={isMenuOpen} />
|
|
233
|
+
</Touchable>
|
|
234
|
+
{logoElement && <div className={sharedStyles.logoContainer}>{logoElement}</div>}
|
|
235
|
+
</NavigationBarContentContainer>
|
|
236
|
+
</NavigationBarSideMargins>
|
|
237
|
+
</Header>
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// The variant paints the top bar alone. The panel always renders in the default variant, so it stays
|
|
241
|
+
// outside of this context: a portal keeps the context of the tree that renders it.
|
|
242
|
+
return (
|
|
243
|
+
<>
|
|
244
|
+
<ThemeVariant variant={variant}>
|
|
245
|
+
<FocusTrap disabled={disableFocusTrap} group={SIDENAV_FOCUS_TRAP_GROUP}>
|
|
246
|
+
{topBar}
|
|
247
|
+
</FocusTrap>
|
|
248
|
+
</ThemeVariant>
|
|
249
|
+
<div className={sharedStyles.spacer} />
|
|
250
|
+
<MobileNavigationMenu
|
|
251
|
+
open={isMenuOpen}
|
|
252
|
+
id={menuId}
|
|
253
|
+
aria-label={ariaLabel}
|
|
254
|
+
topOffset={NAVBAR_HEIGHT_MOBILE}
|
|
255
|
+
showSecondLevel={isSecondLevelOpen}
|
|
256
|
+
onExited={() => {
|
|
257
|
+
setIsSecondLevelOpen(false);
|
|
258
|
+
setOpenedFirstLevelItemId(null);
|
|
259
|
+
}}
|
|
260
|
+
focusTrapGroup={SIDENAV_FOCUS_TRAP_GROUP}
|
|
261
|
+
disableFocusTrap={disableFocusTrap}
|
|
262
|
+
setDisableFocusTrap={setDisableFocusTrap}
|
|
263
|
+
firstLevel={
|
|
264
|
+
<ResponsiveLayout>
|
|
265
|
+
<Box paddingTop={PANEL_TOP_PADDING}>
|
|
266
|
+
<Stack space={ENTRY_SPACE}>
|
|
267
|
+
{(entries ?? []).map((entry, entryIndex) => renderEntry(entry, entryIndex))}
|
|
268
|
+
</Stack>
|
|
269
|
+
</Box>
|
|
270
|
+
{footerSlotElement && <Box paddingY={FOOTER_SLOT_PADDING}>{footerSlotElement}</Box>}
|
|
271
|
+
</ResponsiveLayout>
|
|
272
|
+
}
|
|
273
|
+
secondLevel={
|
|
274
|
+
openedFirstLevelItem ? (
|
|
275
|
+
<ResponsiveLayout>
|
|
276
|
+
<Stack space={ENTRY_SPACE}>
|
|
277
|
+
<Stack space={BACK_BAR_SPACE}>
|
|
278
|
+
<NavigationBar
|
|
279
|
+
title={texts.backNavigationBar || t(tokens.backNavigationBar)}
|
|
280
|
+
onBack={() => setIsSecondLevelOpen(false)}
|
|
281
|
+
topFixed={false}
|
|
282
|
+
withBorder={false}
|
|
283
|
+
/>
|
|
284
|
+
<Title3>{openedFirstLevelItem.label}</Title3>
|
|
285
|
+
</Stack>
|
|
286
|
+
<ResetResponsiveLayout>
|
|
287
|
+
<RowList aria-label={openedFirstLevelItem.label}>
|
|
288
|
+
{(openedFirstLevelItem.children ?? []).map((child) =>
|
|
289
|
+
renderRow(child)
|
|
290
|
+
)}
|
|
291
|
+
</RowList>
|
|
292
|
+
</ResetResponsiveLayout>
|
|
293
|
+
</Stack>
|
|
294
|
+
</ResponsiveLayout>
|
|
295
|
+
) : null
|
|
296
|
+
}
|
|
297
|
+
/>
|
|
298
|
+
</>
|
|
299
|
+
);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
export {SidenavBarMobile};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import {useIsomorphicLayoutEffect, useTheme} from './hooks';
|
|
4
|
+
import {isRunningAcceptanceTest} from './utils/platform';
|
|
5
|
+
|
|
6
|
+
const REDUCED_MOTION_QUERY = '(prefers-reduced-motion)';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Whether the sidenav must move with zero duration: the user asked the operating system for less motion,
|
|
10
|
+
* or an acceptance test runs, where no half-animated node may stay in the DOM after the state that
|
|
11
|
+
* removed it.
|
|
12
|
+
*
|
|
13
|
+
* Every animated rule of the sidenav already drops its transition through the same media query, so the
|
|
14
|
+
* screen answers on its own. This hook gives the answer to the parts that run in JavaScript instead: the
|
|
15
|
+
* timeouts that keep a box in the document until it finished closing, and the delay after which the
|
|
16
|
+
* sidenav reports that its rail stopped.
|
|
17
|
+
*
|
|
18
|
+
* The media query reports `false` on the server and for the first render, where it does not exist. A
|
|
19
|
+
* toggle of the sidenav always comes later than that, so the value is settled by the time anything reads it.
|
|
20
|
+
*/
|
|
21
|
+
const useIsMotionOff = (): boolean => {
|
|
22
|
+
const {platformOverrides} = useTheme();
|
|
23
|
+
const [isReducedMotion, setIsReducedMotion] = React.useState(false);
|
|
24
|
+
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
const mediaQuery = window.matchMedia?.(REDUCED_MOTION_QUERY);
|
|
27
|
+
if (!mediaQuery) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
setIsReducedMotion(mediaQuery.matches);
|
|
31
|
+
|
|
32
|
+
const handleChange = (event: MediaQueryListEvent) => setIsReducedMotion(event.matches);
|
|
33
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
34
|
+
return () => mediaQuery.removeEventListener('change', handleChange);
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
return isRunningAcceptanceTest(platformOverrides) || isReducedMotion;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The width that a text had at rest, so that it can keep it while the rail moves.
|
|
42
|
+
*
|
|
43
|
+
* A label or a section title that takes its width from the layout re-cuts itself as the rail narrows, and
|
|
44
|
+
* a `max-content` width turns a wrapped text into one line at the first frame, which makes its row jump.
|
|
45
|
+
* Freezing the width that the text had at rest keeps its lines while its box folds, so the height of the
|
|
46
|
+
* row animates with the rail.
|
|
47
|
+
*
|
|
48
|
+
* The hook measures the element after every render at rest, and it returns that width while `isFrozen`
|
|
49
|
+
* is true. It returns `undefined` when it has no measurement yet, for example when the sidenav mounted
|
|
50
|
+
* collapsed or on the server: the caller then falls back to `max-content`.
|
|
51
|
+
*/
|
|
52
|
+
const useRestWidth = (
|
|
53
|
+
isFrozen: boolean
|
|
54
|
+
): {ref: React.RefObject<HTMLDivElement | null>; frozenWidth?: number} => {
|
|
55
|
+
const ref = React.useRef<HTMLDivElement>(null);
|
|
56
|
+
const restWidthRef = React.useRef(0);
|
|
57
|
+
|
|
58
|
+
// A layout effect reads the width before the browser paints, so the first frame after a collapse
|
|
59
|
+
// already sees the frozen width.
|
|
60
|
+
useIsomorphicLayoutEffect(() => {
|
|
61
|
+
if (!isFrozen && ref.current) {
|
|
62
|
+
restWidthRef.current = ref.current.offsetWidth;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const frozenWidth = isFrozen && restWidthRef.current > 0 ? restWidthRef.current : undefined;
|
|
67
|
+
return {ref, frozenWidth};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export {useIsMotionOff, useRestWidth};
|