/ src / components / AppIcon.svelte
AppIcon.svelte
  1  <script lang="ts" context="module">
  2      import type { Artwork as JetArtworkType } from '@jet-app/app-store/api/models';
  3      import type { NamedProfile } from '~/config/components/artwork';
  4  
  5      export type AppIconProfile = Extract<
  6          NamedProfile,
  7          | 'app-icon'
  8          | 'app-icon-large'
  9          | 'app-icon-medium'
 10          | 'app-icon-small'
 11          | 'app-icon-xlarge'
 12          | 'app-icon-river'
 13          | 'brick-app-icon'
 14      >;
 15  
 16      export function doesAppIconNeedBorder(icon: JetArtworkType): boolean {
 17          const doesIconHaveTransparentBackground =
 18              icon.backgroundColor &&
 19              isNamedColor(icon.backgroundColor) &&
 20              icon.backgroundColor.name === 'clear';
 21          const isIconPrerendered =
 22              icon.style === 'roundedRectPrerendered' ||
 23              icon.style === 'roundPrerendered';
 24          const isIconUnadorned = icon.style === 'unadorned';
 25  
 26          return (
 27              !doesIconHaveTransparentBackground &&
 28              !isIconPrerendered &&
 29              !isIconUnadorned
 30          );
 31      }
 32  </script>
 33  
 34  <script lang="ts">
 35      import Artwork from '~/components/Artwork.svelte';
 36      import { ArtworkConfig } from '@amp/web-app-components/config/components/artwork';
 37      import { isNamedColor } from '~/utils/color';
 38  
 39      export let icon: JetArtworkType;
 40      export let profile: AppIconProfile = 'app-icon';
 41      export let fixedWidth: boolean = true;
 42      export let disableAutoCenter: boolean = false;
 43      export let withBorder: boolean = false;
 44  
 45      const profiles = ArtworkConfig.get().PROFILES;
 46  
 47      $: computedProfile = (
 48          icon.style === 'pill'
 49              ? `${profile}-pill`
 50              : icon.style === 'tvRect'
 51              ? `${profile}-tv-rect`
 52              : profile
 53      ) as NamedProfile;
 54      $: widthFromProfile = profiles?.get(computedProfile)?.[0] ?? 0;
 55      $: hasTransparentBackground =
 56          !!icon.backgroundColor &&
 57          isNamedColor(icon.backgroundColor) &&
 58          icon.backgroundColor.name === 'clear';
 59      $: needsBorder = withBorder || doesAppIconNeedBorder(icon);
 60  
 61      // These prerendered "Solarium" icons need to use higher than normal quality due to how their
 62      // rendering pipeline downscales/transforms sources.
 63      $: quality =
 64          icon.style &&
 65          ['roundedRectPrerendered', 'roundPrerendered'].includes(icon.style)
 66              ? 75
 67              : undefined;
 68  </script>
 69  
 70  <div
 71      class="app-icon"
 72      class:pill={icon.style === 'pill'}
 73      class:round={icon.style === 'round'}
 74      class:rounded-rect={icon.style === 'roundedRect'}
 75      class:tv-rect={icon.style === 'tvRect'}
 76      class:rounded-rect-prerendered={icon.style === 'roundedRectPrerendered'}
 77      class:round-prerendered={icon.style === 'roundPrerendered'}
 78      class:with-border={needsBorder}
 79      style={fixedWidth ? `--profileWidth: ${widthFromProfile}px` : ''}
 80  >
 81      <Artwork
 82          {disableAutoCenter}
 83          {hasTransparentBackground}
 84          {quality}
 85          artwork={icon}
 86          profile={computedProfile}
 87          noShelfChevronAnchor={true}
 88      />
 89  </div>
 90  
 91  <style>
 92      .app-icon {
 93          aspect-ratio: 1 / 1;
 94          min-width: var(--profileWidth, auto);
 95      }
 96  
 97      .app-icon.pill {
 98          aspect-ratio: 4 / 3;
 99  
100          /*
101          Creates elliptical corners with horizontal radii at 50% of the width and vertical radii
102          at 65% of the height, for a rounded, squished, pill-like effect
103          */
104          border-radius: 50% 50% 50% 50% / 65% 65% 65% 65%;
105      }
106  
107      .app-icon.round {
108          border-radius: 50%;
109      }
110  
111      .app-icon.rounded-rect {
112          border-radius: 23%;
113      }
114  
115      .app-icon.tv-rect {
116          aspect-ratio: 16/9;
117          border-radius: 9% / 16%;
118      }
119  
120      .app-icon.rounded-rect-prerendered {
121          border-radius: 25%;
122      }
123  
124      .app-icon.round-prerendered {
125          border-radius: 50%;
126      }
127  
128      .app-icon.with-border {
129          box-shadow: 0 0 0 1px var(--systemQuaternary);
130      }
131  </style>