summaryrefslogtreecommitdiff
path: root/src/components/Shelf
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /src/components/Shelf
init commit
Diffstat (limited to 'src/components/Shelf')
-rw-r--r--src/components/Shelf/Title.svelte112
-rw-r--r--src/components/Shelf/Wrapper.svelte81
2 files changed, 193 insertions, 0 deletions
diff --git a/src/components/Shelf/Title.svelte b/src/components/Shelf/Title.svelte
new file mode 100644
index 0000000..e68f4b1
--- /dev/null
+++ b/src/components/Shelf/Title.svelte
@@ -0,0 +1,112 @@
+<!--
+@component
+
+Renders the "Title" and "See All action" for a `Shelf`
+
+### Supported CSS Variables
+
+- `--shelf-title-font`: overrides the font used for the "title" element
+
+-->
+<script lang="ts">
+ import { type Opt, isSome } from '@jet/environment/types/optional';
+ import { type Action, isFlowAction } from '@jet-app/app-store/api/models';
+
+ import SFSymbol from '~/components/SFSymbol.svelte';
+ import LinkWrapper from '../LinkWrapper.svelte';
+
+ export let title: string;
+ export let subtitle: Opt<string> = undefined;
+ export let seeAllAction: Opt<Action> = undefined;
+</script>
+
+<div class="title-action-wrapper" class:with-subtitle={!!subtitle}>
+ <LinkWrapper action={seeAllAction} label={title}>
+ <div class="link-contents">
+ <h2 class="shelf-title" data-test-id="shelf-title">{title}</h2>
+
+ {#if isSome(seeAllAction) && isFlowAction(seeAllAction)}
+ <span
+ class="chevron-container"
+ data-test-id="shelf-see-all-chevron"
+ aria-hidden="true"
+ >
+ <SFSymbol name="chevron.forward" />
+ </span>
+ {/if}
+ </div>
+ </LinkWrapper>
+</div>
+
+{#if subtitle}
+ <p>{subtitle}</p>
+{/if}
+
+<style lang="scss">
+ @use '@amp/web-shared-styles/sasskit-stylekit/ac-sasskit-config';
+ @use 'ac-sasskit/core/helpers' as *;
+ @use 'ac-sasskit/core/locale' as *;
+
+ .title-action-wrapper {
+ display: flex;
+ align-items: end;
+ justify-content: space-between;
+ margin: 0 var(--bodyGutter) 13px;
+ }
+
+ .title-action-wrapper.with-subtitle {
+ margin-bottom: 3px;
+ }
+
+ .title-action-wrapper :global(a:hover) {
+ text-decoration: none;
+ }
+
+ p {
+ font: var(--title-3-tall);
+ color: var(--systemSecondary);
+ margin: 0 var(--bodyGutter) 13px;
+ }
+
+ h2 {
+ color: var(--systemPrimary, #000);
+ font: var(--shelf-title-font, var(--title-2-emphasized));
+ text-wrap: pretty;
+ }
+
+ .link-contents {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .chevron-container {
+ line-height: 0;
+ padding: 6px 0 4px;
+ display: block;
+ }
+
+ .chevron-container :global(svg) {
+ height: 12px;
+ display: block;
+ translate: 0 0;
+ transition: translate 210ms ease-out;
+
+ @include rtl {
+ transform: rotate(180deg);
+ }
+ }
+
+ .chevron-container :global(svg path:not([fill='none'])) {
+ fill: var(--systemGray2);
+ }
+
+ .link-contents:hover .chevron-container :global(svg) {
+ translate: 1px 0;
+
+ @include rtl {
+ transform: rotate(180deg);
+ translate: -1px 0;
+ }
+ }
+</style>
diff --git a/src/components/Shelf/Wrapper.svelte b/src/components/Shelf/Wrapper.svelte
new file mode 100644
index 0000000..850b0d0
--- /dev/null
+++ b/src/components/Shelf/Wrapper.svelte
@@ -0,0 +1,81 @@
+<script lang="ts">
+ import type { Shelf } from '@jet-app/app-store/api/models';
+ import ShelfTitle from '~/components/Shelf/Title.svelte';
+
+ export let shelf: Shelf | undefined = undefined;
+
+ /**
+ * Whether or not to automatically display the shelf "centered" at the normal
+ * page width for the App Store
+ *
+ * When `false`, the shelf is not constrained horizontally in any way
+ *
+ * The value of this property may be ignored when the shelf's `.presentationHints`
+ * indicate that it is being rendered in a context where "centering" would not be
+ * appropriate
+ *
+ * @default true
+ */
+ export let centered: boolean = false;
+
+ export let withTopBorder: boolean = false;
+ export let withTopMargin: boolean = false;
+ export let withPaddingTop: boolean = true;
+ export let withBottomPadding: boolean = true;
+
+ $: seeAllAction =
+ shelf?.header?.titleAction ??
+ shelf?.header?.accessoryAction ??
+ shelf?.seeAllAction;
+</script>
+
+<section
+ id={shelf?.id}
+ data-test-id="shelf-wrapper"
+ class="shelf"
+ class:centered
+ class:border-top={withTopBorder}
+ class:margin-top={withTopMargin}
+ class:padding-top={withPaddingTop}
+ class:padding-bottom={withBottomPadding}
+>
+ {#if $$slots['title']}
+ <slot name="title" />
+ {:else if shelf?.header?.title}
+ <ShelfTitle
+ title={shelf.header.title}
+ subtitle={shelf.header.subtitle}
+ {seeAllAction}
+ />
+ {:else if shelf?.title}
+ <ShelfTitle
+ title={shelf.title}
+ subtitle={shelf.subtitle}
+ {seeAllAction}
+ />
+ {/if}
+
+ <slot />
+</section>
+
+<style>
+ .padding-top {
+ padding-top: 13px;
+ }
+
+ .centered {
+ margin: 0 var(--bodyGutter);
+ }
+
+ .margin-top {
+ margin-top: 13px;
+ }
+
+ .border-top {
+ border-top: 1px solid var(--systemGray4);
+ }
+
+ .shelf.padding-bottom {
+ padding-bottom: 32px;
+ }
+</style>