|
1 | 1 | <script lang="ts">
|
2 | 2 | import type { Package } from '$lib/server/content';
|
3 |
| - import { fix_position } from '../../../../../packages/site-kit/src/lib/actions/utils'; |
4 | 3 | import PackageCard from './PackageCard.svelte';
|
5 | 4 |
|
6 | 5 | interface Props {
|
|
11 | 10 |
|
12 | 11 | let { title, description, packages }: Props = $props();
|
13 | 12 |
|
14 |
| - let header: HTMLElement; |
15 |
| -
|
16 | 13 | const INITIAL_ITEMS = 3;
|
17 | 14 | let showAll = $state(false);
|
18 |
| - let visiblePackages = $derived(showAll ? packages : packages.slice(0, INITIAL_ITEMS)); |
19 | 15 | </script>
|
20 | 16 |
|
21 | 17 | <section class="category">
|
22 | 18 | <header>
|
23 |
| - <h2 bind:this={header}> |
24 |
| - {title} |
25 |
| - </h2> |
| 19 | + <h2>{title}</h2> |
26 | 20 |
|
27 | 21 | {#if description}
|
28 | 22 | <p>{@html description}</p>
|
29 | 23 | {/if}
|
30 | 24 | </header>
|
31 | 25 |
|
32 |
| - <div class="content"> |
33 |
| - {#each visiblePackages as pkg} |
| 26 | + <div class="grid"> |
| 27 | + {#each packages.slice(0, INITIAL_ITEMS) as pkg} |
34 | 28 | <div class="item">
|
35 | 29 | <PackageCard {pkg} />
|
36 | 30 | </div>
|
37 | 31 | {/each}
|
38 | 32 | </div>
|
39 | 33 |
|
40 | 34 | {#if packages.length > INITIAL_ITEMS}
|
41 |
| - <div class="show-more-container"> |
42 |
| - <label> |
43 |
| - <button |
44 |
| - class="raised" |
45 |
| - aria-label="Show more" |
46 |
| - aria-pressed={showAll} |
47 |
| - onclick={(e) => { |
48 |
| - const { bottom } = header.getBoundingClientRect(); |
49 |
| - |
50 |
| - // if the current section is wholly visible, don't muck about with the scroll position |
51 |
| - if (!showAll || bottom > 0) { |
52 |
| - showAll = !showAll; |
53 |
| - return; |
54 |
| - } |
55 |
| - |
56 |
| - // otherwise, keep the button in the same position |
57 |
| - fix_position(e.currentTarget, () => { |
58 |
| - showAll = !showAll; |
59 |
| - }); |
60 |
| - }}><span class="icon"></span></button |
61 |
| - > |
62 |
| - |
63 |
| - {showAll ? 'show less' : `show all (${packages.length})`} |
64 |
| - </label> |
65 |
| - </div> |
| 35 | + <details> |
| 36 | + <summary> |
| 37 | + <span class="raised button" aria-label="Toggle"> |
| 38 | + <span class="icon"></span> |
| 39 | + </span> |
| 40 | + |
| 41 | + <span> |
| 42 | + {showAll ? 'show less' : `show all (${packages.length})`} |
| 43 | + </span> |
| 44 | + </summary> |
| 45 | + |
| 46 | + <div class="grid"> |
| 47 | + {#each packages.slice(INITIAL_ITEMS) as pkg} |
| 48 | + <div class="item"> |
| 49 | + <PackageCard {pkg} /> |
| 50 | + </div> |
| 51 | + {/each} |
| 52 | + </div> |
| 53 | + </details> |
66 | 54 | {/if}
|
67 | 55 | </section>
|
68 | 56 |
|
|
83 | 71 | }
|
84 | 72 | }
|
85 | 73 |
|
86 |
| - .content { |
| 74 | + .grid { |
87 | 75 | display: grid;
|
88 | 76 | grid-template-columns: 1fr;
|
89 | 77 | gap: 2rem;
|
90 |
| - margin-top: 1rem; |
91 | 78 |
|
92 | 79 | @media (min-width: 1024px) {
|
93 | 80 | grid-template-columns: repeat(3, 1fr);
|
94 | 81 | }
|
95 | 82 | }
|
96 | 83 |
|
| 84 | + details { |
| 85 | + position: relative; |
| 86 | + margin-bottom: 9rem; |
| 87 | +
|
| 88 | + .grid { |
| 89 | + margin-top: 2rem; |
| 90 | + } |
| 91 | + } |
| 92 | +
|
| 93 | + summary { |
| 94 | + position: absolute; |
| 95 | + bottom: -6rem; |
| 96 | + font: var(--sk-font-ui-small); |
| 97 | + display: flex; |
| 98 | + align-items: center; |
| 99 | + gap: 1rem; |
| 100 | +
|
| 101 | + .icon { |
| 102 | + mask-size: 2rem; |
| 103 | + mask-image: url(icons/plus); |
| 104 | +
|
| 105 | + [open] & { |
| 106 | + mask-image: url(icons/minus); |
| 107 | + } |
| 108 | + } |
| 109 | + } |
| 110 | +
|
97 | 111 | .item {
|
98 | 112 | height: 16rem;
|
99 | 113 | min-width: 0; /* Prevents grid items from overflowing */
|
|
0 commit comments