Skip to content

Commit dab7641

Browse files
committed
more-less
1 parent d3cc0d8 commit dab7641

File tree

1 file changed

+53
-141
lines changed

1 file changed

+53
-141
lines changed
Lines changed: 53 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<script lang="ts">
22
import type { Package } from '$lib/server/content';
3-
import { prefersReducedMotion } from 'svelte/motion';
43
import PackageCard from './PackageCard.svelte';
54
65
interface Props {
@@ -11,128 +10,48 @@
1110
1211
let { title, description, packages }: Props = $props();
1312
14-
let content: HTMLElement;
15-
let scroller: HTMLElement;
16-
17-
let behavior = $derived<ScrollBehavior>(prefersReducedMotion.current ? 'instant' : 'smooth');
18-
19-
let at_start = $state(true);
20-
let at_end = $state(true);
21-
22-
function update() {
23-
at_start = scroller.scrollLeft === 0;
24-
at_end = scroller.scrollLeft + scroller.offsetWidth >= scroller.scrollWidth;
25-
}
26-
27-
function go(d: number) {
28-
const [a, b] = scroller.querySelectorAll('.item') as NodeListOf<HTMLElement>;
29-
const left = scroller.scrollLeft + d * (b.offsetLeft - a.offsetLeft);
30-
31-
scroller.scrollTo({ left, behavior });
32-
}
33-
34-
$effect(update);
13+
const INITIAL_ITEMS = 3;
14+
let showAll = $state(false);
15+
let visiblePackages = $derived(showAll ? packages : packages.slice(0, INITIAL_ITEMS));
3516
</script>
3617

37-
<svelte:window onresize={update} />
38-
3918
<section class="category">
4019
<header>
4120
<h2>
4221
{title}
4322
</h2>
44-
45-
{#if !at_start || !at_end}
46-
<div class="controls">
47-
<button disabled={at_start} aria-label="Previous" class="raised icon" onclick={() => go(-1)}
48-
></button>
49-
50-
<button disabled={at_end} aria-label="Next" class="raised icon" onclick={() => go(1)}
51-
></button>
52-
</div>
53-
{/if}
5423
</header>
5524
{#if description}
5625
<h3>{@html description}</h3>
5726
{/if}
5827

59-
<div class="wrapper">
60-
<!-- we duplicate the DOM for the sake of the gradient effect -
61-
without this, the scrollbar extends beyond the content area -->
62-
<div inert class="viewport">
63-
<div bind:this={content} class="content">
64-
{#each packages as pkg}
65-
<div class="item">
66-
<PackageCard {pkg} />
67-
</div>
68-
{/each}
28+
<div class="content">
29+
{#each visiblePackages as pkg}
30+
<div class="item">
31+
<PackageCard {pkg} />
6932
</div>
70-
</div>
33+
{/each}
34+
</div>
7135

72-
<div
73-
bind:this={scroller}
74-
class="viewport"
75-
onscroll={(e) => {
76-
const left = e.currentTarget.scrollLeft;
77-
content.style.translate = `-${left}px`;
78-
79-
update();
80-
}}
81-
>
82-
<div class="content">
83-
{#each packages as pkg}
84-
<div class="item">
85-
<PackageCard {pkg} />
86-
</div>
87-
{/each}
88-
</div>
36+
{#if packages.length > INITIAL_ITEMS}
37+
<div class="show-more-container">
38+
<button class="show-more-btn" onclick={() => (showAll = !showAll)}>
39+
{showAll ? 'Show Less' : `Show More (${packages.length - INITIAL_ITEMS})`}
40+
</button>
8941
</div>
90-
</div>
42+
{/if}
9143
</section>
9244

9345
<style>
9446
.category {
95-
--bleed: var(--sk-page-padding-side);
96-
margin-bottom: 4rem;
47+
margin-bottom: 3rem;
9748
}
9849
9950
header {
100-
display: flex;
10151
margin-bottom: 1rem;
102-
align-items: center;
103-
gap: 2rem;
10452
10553
h2 {
106-
flex: 1;
107-
}
108-
109-
.controls {
110-
display: flex;
111-
gap: 0.5rem;
112-
}
113-
114-
button {
115-
background: var(--sk-bg-3);
116-
117-
&::after {
118-
content: '';
119-
position: absolute;
120-
width: 100%;
121-
height: 100%;
122-
top: 0;
123-
left: 0;
124-
background: currentColor;
125-
mask: url(icons/chevron) 50% 50% no-repeat;
126-
mask-size: 2rem 2rem;
127-
}
128-
129-
&[aria-label='Next']::after {
130-
rotate: 180deg;
131-
}
132-
133-
&:disabled {
134-
background: none;
135-
}
54+
margin: 0;
13655
}
13756
}
13857
@@ -141,55 +60,48 @@
14160
font-size: 1.5rem;
14261
}
14362
144-
.wrapper {
145-
position: relative;
146-
}
147-
148-
.viewport {
149-
overscroll-behavior-x: contain;
150-
overscroll-behavior-y: auto;
151-
scroll-snap-type: x mandatory;
152-
153-
&[inert] {
154-
position: relative;
155-
margin: 0 calc(-1 * var(--bleed));
156-
padding: 1rem var(--bleed);
157-
scroll-padding: 0 var(--bleed);
158-
overflow: hidden;
159-
filter: blur(0.5px);
160-
mask-image: linear-gradient(
161-
to right,
162-
rgb(0 0 0 / 0) 0%,
163-
rgb(0 0 0 / 0.5) var(--bleed),
164-
rgb(0 0 0 / 0) var(--bleed),
165-
rgb(0 0 0 / 0) calc(100% - var(--bleed)),
166-
rgb(0 0 0 / 0.5) calc(100% - var(--bleed)),
167-
rgb(0 0 0 / 0) 100%
168-
);
169-
}
170-
171-
&:not([inert]) {
172-
position: absolute;
173-
top: 0;
174-
left: 0;
175-
width: 100%;
176-
height: 100%;
177-
overflow-x: auto;
178-
overflow-y: visible;
179-
margin: 1rem 0;
180-
}
181-
}
182-
18363
.content {
18464
display: grid;
185-
grid-auto-columns: 34rem;
186-
grid-auto-flow: column;
65+
grid-template-columns: repeat(3, 1fr);
18766
gap: 2rem;
188-
width: fit-content;
67+
margin-top: 1rem;
18968
}
19069
19170
.item {
19271
height: 16rem;
193-
scroll-snap-align: start;
72+
min-width: 0; /* Prevents grid items from overflowing */
73+
}
74+
75+
.show-more-container {
76+
display: flex;
77+
justify-content: flex-end;
78+
margin-top: 1rem;
79+
}
80+
81+
.show-more-btn {
82+
background: var(--sk-bg-3);
83+
border: 1px solid var(--sk-border);
84+
border-radius: var(--sk-border-radius);
85+
padding: 0.75rem 1.5rem;
86+
font: var(--sk-font-ui-medium);
87+
font-size: 1.2rem;
88+
color: var(--sk-text-1);
89+
cursor: pointer;
90+
transition: all 0.2s ease;
91+
92+
&:hover {
93+
background: var(--sk-bg-4);
94+
border-color: var(--sk-text-3);
95+
}
96+
97+
&:active {
98+
transform: translateY(1px);
99+
}
100+
}
101+
102+
@media (max-width: 1024px) {
103+
.content {
104+
grid-template-columns: 1fr;
105+
}
194106
}
195107
</style>

0 commit comments

Comments
 (0)