Nav.svelte
1 <script lang="ts"> 2 import type { ArrowOffset } from '@amp/web-app-components/src/components/Shelf/types'; 3 import ChevronCompactLeft from '@amp/web-app-components/assets/shelf/chevron-compact-left.svg'; 4 import { createEventDispatcher } from 'svelte'; 5 6 export let translateFn: ( 7 str: string, 8 values?: Record<string, string | number>, 9 ) => string; 10 export let headerHeight: number; 11 export let arrowOffset: ArrowOffset; 12 export let hasNextPage: boolean; 13 export let hasPreviousPage: boolean; 14 export let isRTL: boolean; 15 16 $: hasNavArrows = hasPreviousPage || hasNextPage; 17 18 // Adjusting arrows to center on content. 19 // This is a fallback for browsers that don't support CSS anchor positioning. 20 $: addSpaceForHeader = (() => { 21 let offsetStyle = '0px'; 22 23 // Custom adjustment provided by user 24 if (arrowOffset && arrowOffset.length) { 25 arrowOffset.forEach(({ direction, offset }) => { 26 if (direction == 'top') { 27 offsetStyle = ` 28 ${offset}px; 29 `; 30 } else { 31 offsetStyle = ` 32 calc(${offset}px * -1); 33 `; 34 } 35 }); 36 } 37 // Adjust for header 38 if (headerHeight) { 39 // adjust nav height to account for header 40 offsetStyle = ` 41 ${headerHeight}px; 42 `; 43 } 44 45 return offsetStyle; 46 })(); 47 48 const NAV = { 49 PREVIOUS: 'previous', 50 NEXT: 'next', 51 } as const; 52 53 const dispatch = createEventDispatcher(); 54 const handleNextPage = () => dispatch(NAV.NEXT); 55 const handlePreviousPage = () => dispatch(NAV.PREVIOUS); 56 57 $: NEXT_ARROW_PROPS = { 58 disabled: !hasNextPage, 59 'aria-label': translateFn('AMP.Shared.NextPage'), 60 }; 61 62 $: PREV_ARROW_PROPS = { 63 disabled: !hasPreviousPage, 64 'aria-label': translateFn('AMP.Shared.PreviousPage'), 65 }; 66 67 $: rightArrowProps = isRTL ? PREV_ARROW_PROPS : NEXT_ARROW_PROPS; 68 $: rightClick = isRTL ? handlePreviousPage : handleNextPage; 69 70 $: leftArrowProps = isRTL ? NEXT_ARROW_PROPS : PREV_ARROW_PROPS; 71 $: leftClick = isRTL ? handleNextPage : handlePreviousPage; 72 </script> 73 74 {#if hasNavArrows} 75 <button 76 {...leftArrowProps} 77 type="button" 78 class="shelf-grid-nav__arrow shelf-grid-nav__arrow--left" 79 data-testId="shelf-button-left" 80 on:click={leftClick} 81 style="--offset: {addSpaceForHeader};" 82 > 83 <ChevronCompactLeft /> 84 </button> 85 <slot name="shelf-content" /> 86 <button 87 {...rightArrowProps} 88 type="button" 89 class="shelf-grid-nav__arrow shelf-grid-nav__arrow--right" 90 data-testId="shelf-button-right" 91 on:click={rightClick} 92 style="--offset: {addSpaceForHeader};" 93 > 94 <ChevronCompactLeft /> 95 </button> 96 {:else} 97 <slot name="shelf-content" /> 98 {/if} 99 100 <style lang="scss"> 101 @use '@amp/web-shared-styles/sasskit-stylekit/ac-sasskit-config'; 102 @use './style/core.scss' as *; 103 104 .shelf-grid-nav { 105 list-style: none; 106 margin: 0; 107 108 ul { 109 list-style: none; 110 margin: 0; 111 } 112 } 113 114 .shelf-grid-nav__arrow { 115 height: $shelf-grid-arrow-height; 116 width: $shelf-grid-arrow-width; 117 align-items: center; 118 border: none; 119 cursor: pointer; 120 display: flex; 121 justify-content: center; 122 overflow: hidden; 123 position: absolute; 124 top: 50%; 125 transition: $shelf-grid-nav-transition; 126 translate: 0 -50%; 127 border-radius: 6px; 128 129 // Non GPU-accelerated layers must be below GPU-accelerated layers. 130 z-index: var(--z-default); 131 132 // Fallback for browsers that don't support CSS anchor positioning 133 @supports not (top: anchor(--a center)) { 134 transform: translateY(calc(-50% + var(--offset))); 135 translate: none; 136 } 137 138 // CSS Anchor Positioning to vertically center paddles with artwork 139 // Powerswoosh intentionally not targeted — doesn't have `shelf` class. 140 :global(.shelf:has(.shelf-grid__list--grid-rows-1)) & { 141 // Set `top` to align with center of first artwork in 1-row shelf. 142 // Targets anchor in `Shelf.svelte`. 143 top: anchor(--shelf-first-artwork center, 50%); 144 } 145 146 :global(svg) { 147 width: 8.5px; 148 height: 30.5px; 149 fill: var(--systemSecondary); 150 } 151 152 &:hover, 153 &:focus-visible { 154 text-decoration: none; 155 background: var(--systemQuinary); 156 157 @media (prefers-color-scheme: dark) { 158 background: var(--systemQuaternary); 159 } 160 } 161 162 &:active { 163 background: var(--systemQuaternary); 164 165 @media (prefers-color-scheme: dark) { 166 background: var(--systemTertiary); 167 } 168 169 :global(svg) { 170 fill: var(--systemPrimary); 171 } 172 } 173 174 &:disabled { 175 cursor: default; 176 opacity: 0; 177 } 178 179 // Paddles not used in xsmall viewport 180 @media (--range-xsmall-down) { 181 display: none; 182 } 183 } 184 185 .shelf-grid-nav__arrow--right { 186 right: $shelf-grid-arrow-position; 187 scale: -1 1; // Flip icon horizontally 188 } 189 190 .shelf-grid-nav__arrow--left { 191 left: $shelf-grid-arrow-position; 192 } 193 194 @media (--range-xsmall-down) { 195 .shelf-grid-nav { 196 display: none; 197 } 198 } 199 </style>