/ src / components / pages / DefaultPage.svelte
DefaultPage.svelte
  1  <script lang="ts" context="module">
  2      import type {
  3          PagePresentationOptions,
  4          Shelf,
  5      } from '@jet-app/app-store/api/models';
  6      import type { WebRenderablePage } from '@jet-app/app-store/api/models/web-renderable-page';
  7  
  8      /**
  9       * Just the `Page` props that we actually need to render this component
 10       */
 11      export interface DefaultPageRequirements extends WebRenderablePage {
 12          shelves: Shelf[];
 13          presentationOptions?: PagePresentationOptions;
 14      }
 15  </script>
 16  
 17  <script lang="ts">
 18      import type { MarkerShelf } from '~/components/jet/shelf/MarkerShelf.svelte';
 19      import { isUberShelf } from '~/components/jet/shelf/UberShelf.svelte';
 20      import ShelfComponent from '~/components/jet/shelf/Shelf.svelte';
 21      import { partition } from '~/utils/array';
 22      import { carouselMediaStyle } from '~/stores/carousel-media-style';
 23      import mediaQueries from '~/utils/media-queries';
 24      import { isHeroCarouselShelf } from '../jet/shelf/HeroCarouselShelf.svelte';
 25      import { isRtl } from '~/utils/locale';
 26  
 27      interface $$Slots {
 28          'before-shelves': {};
 29  
 30          /**
 31           * If {@linkcode ShelfComponent}` recognizes a shelf to be a {@linkcode MarkerShelf},
 32           * this slot will be rendered so that the "page" data can be supplied by a "parent"
 33           * component
 34           */
 35          'marker-shelf': {
 36              shelf: MarkerShelf;
 37          };
 38      }
 39  
 40      export let page: DefaultPageRequirements;
 41  
 42      $: ({ title, presentationOptions = [] } = page);
 43  
 44      // Some shelves are meant to be rendered above the title, rather than below it
 45      $: [aboveTitleShelves, belowTitleShelves] = partition(
 46          page.shelves,
 47          (shelf) => {
 48              // Some "uber" shelves might be placed above the title
 49              if (isUberShelf(shelf)) {
 50                  const [uber] = shelf.items;
 51                  return uber.style === 'above';
 52              }
 53  
 54              // Everything else should be below it
 55              return false;
 56          },
 57      );
 58  
 59      $: prefersHiddenPageTitle = presentationOptions.includes(
 60          'prefersHiddenPageTitle',
 61      );
 62      $: prefersLargeTitle = presentationOptions.includes('prefersLargeTitle');
 63      $: prefersOverlayedPageHeader =
 64          $mediaQueries === 'xsmall' &&
 65          presentationOptions.includes('prefersOverlayedPageHeader');
 66      $: isOnDarkBackground =
 67          prefersOverlayedPageHeader && $carouselMediaStyle === 'dark';
 68  
 69      $: isTitleDuplicatedInHero = (() => {
 70          const firstShelf = page.shelves?.[0];
 71  
 72          if (
 73              !firstShelf ||
 74              !isHeroCarouselShelf(firstShelf) ||
 75              firstShelf.items?.length !== 1
 76          ) {
 77              return false;
 78          }
 79  
 80          const { items: ltrItems, rtlItems } = firstShelf.items?.[0] ?? {};
 81          const firstItem = isRtl() && rtlItems?.length ? rtlItems : ltrItems;
 82          const firstTitle = firstItem?.[0]?.overlay?.titleText;
 83  
 84          return title === firstTitle;
 85      })();
 86  </script>
 87  
 88  <div
 89      class="default-page-container"
 90      data-testid="default-page-container"
 91      class:with-overlaid-title={prefersOverlayedPageHeader}
 92      class:with-title-in-hero={isTitleDuplicatedInHero}
 93  >
 94      {#each aboveTitleShelves as shelf}
 95          <ShelfComponent {shelf}>
 96              <slot name="marker-shelf" slot="marker-shelf" let:shelf {shelf} />
 97          </ShelfComponent>
 98      {/each}
 99  
100      {#if title && !prefersHiddenPageTitle && !isTitleDuplicatedInHero}
101          <h1
102              data-test-id="page-title"
103              class:large-title={prefersLargeTitle}
104              class:overlaid={prefersOverlayedPageHeader}
105              class:on-dark-background={isOnDarkBackground}
106          >
107              {title}
108          </h1>
109      {/if}
110  
111      <slot name="before-shelves" />
112  
113      {#each belowTitleShelves as shelf}
114          {#if !shelf.isHidden}
115              <ShelfComponent {shelf}>
116                  <slot
117                      name="marker-shelf"
118                      slot="marker-shelf"
119                      let:shelf
120                      {shelf}
121                  />
122              </ShelfComponent>
123          {/if}
124      {/each}
125  </div>
126  
127  <style lang="scss">
128      @use 'ac-sasskit/modules/viewportcontent/core' as *;
129      @use 'amp/stylekit/core/viewports' as *;
130  
131      .default-page-container {
132          flex-grow: 1;
133          width: 100%;
134          max-width: viewport-content-for(xlarge);
135          margin: 0 auto;
136      }
137  
138      .default-page-container.with-overlaid-title {
139          margin-top: -13px;
140      }
141  
142      .default-page-container.with-title-in-hero {
143          @media (--range-small-up) {
144              margin-top: 10px;
145          }
146      }
147  
148      h1 {
149          padding: 11px var(--bodyGutter);
150          font: var(--large-title-emphasized);
151          letter-spacing: -0.5px;
152          word-wrap: break-word;
153          color: var(--systemPrimary, #000);
154          position: relative;
155          z-index: 1;
156          transition: color 210ms ease-in;
157      }
158  
159      h1.large-title {
160          font: var(--large-title-emphasized-tall);
161      }
162  
163      h1.overlaid {
164          position: absolute;
165          z-index: 3;
166          padding: var(--bodyGutter) var(--bodyGutter) 0;
167          color: var(--systemPrimary-onLight, #000);
168      }
169  
170      h1.on-dark-background {
171          color: var(--systemPrimary-onDark);
172      }
173  </style>