Skip to content

Commit 15944db

Browse files
authored
design cleanup (#1568)
1 parent d3cc0d8 commit 15944db

File tree

7 files changed

+201
-224
lines changed

7 files changed

+201
-224
lines changed

apps/svelte.dev/src/lib/packages-meta.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const FEATURED: {
88
{
99
title: 'Svelte CLI add-ons',
1010
description:
11-
'<a href="/docs/cli">sv, the Svelte CLI</a>, lets you instantly add functionality to a new or existing project.',
11+
'The <a href="/docs/cli">Svelte CLI</a> lets you instantly add functionality to a new or existing project with <code>npx sv add</code>.',
1212
packages: [
1313
{ name: 'tailwindcss', svAlias: 'tailwind' },
1414
{ name: 'drizzle-orm', svAlias: 'drizzle' },
@@ -243,12 +243,6 @@ const FEATURED: {
243243
{ name: 'felte' },
244244
{ name: '@tanstack/svelte-form' }
245245
]
246-
},
247-
{
248-
title: 'More',
249-
description:
250-
'These are just a few highlights. See a larger <a href="https://www.sveltesociety.dev/packages">directory of packages at sveltesociety.dev</a>.',
251-
packages: []
252246
}
253247
];
254248

apps/svelte.dev/src/routes/packages/+page.svelte

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
<script lang="ts">
22
import Category from './Category.svelte';
3-
import { fly } from 'svelte/transition';
43
54
const { data } = $props();
65
</script>
@@ -13,10 +12,16 @@
1312
<meta name="Description" content="Packages for your Svelte and SvelteKit apps" />
1413
</svelte:head>
1514

16-
<h1 class="visually-hidden">Packages</h1>
17-
1815
<div class="page content">
19-
<div in:fly={{ y: 20 }}>
16+
<header>
17+
<h1>Packages</h1>
18+
<p>
19+
We've collected a few of our favourite packages that work well with Svelte and SvelteKit apps.
20+
Official packages are marked with the <span class="svelte-logo" aria-label="Svelte"></span> logo.
21+
</p>
22+
</header>
23+
24+
<div>
2025
{#each data.homepage as { title, description, packages }}
2126
<Category {title} {description} {packages} />
2227
{/each}
@@ -33,7 +38,24 @@
3338
text-wrap: balance;
3439
}
3540
36-
.page :global(:where(h2, h3) code) {
37-
all: unset;
41+
header {
42+
margin: 0 0 4rem 0;
43+
}
44+
45+
h1 {
46+
margin: 0 0 2rem 0;
47+
}
48+
49+
.svelte-logo {
50+
position: relative;
51+
top: 0.15em;
52+
display: inline-block;
53+
width: 1em;
54+
aspect-ratio: 1;
55+
background: #ff3e00;
56+
mask-size: contain;
57+
mask-image: url(icons/svelte-cutout);
58+
mask-repeat: no-repeat;
59+
mask-position: 50% 50%;
3860
}
3961
</style>
Lines changed: 86 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import type { Package } from '$lib/server/content';
3-
import { prefersReducedMotion } from 'svelte/motion';
3+
import { fix_position } from '../../../../../packages/site-kit/src/lib/actions/utils';
44
import PackageCard from './PackageCard.svelte';
55
66
interface Props {
@@ -11,185 +11,120 @@
1111
1212
let { title, description, packages }: Props = $props();
1313
14-
let content: HTMLElement;
15-
let scroller: HTMLElement;
14+
let header: HTMLElement;
1615
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);
16+
const INITIAL_ITEMS = 3;
17+
let showAll = $state(false);
18+
let visiblePackages = $derived(showAll ? packages : packages.slice(0, INITIAL_ITEMS));
3519
</script>
3620

37-
<svelte:window onresize={update} />
38-
3921
<section class="category">
40-
<header>
41-
<h2>
42-
{title}
43-
</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}
54-
</header>
22+
<h2 bind:this={header}>
23+
{title}
24+
</h2>
25+
5526
{#if description}
56-
<h3>{@html description}</h3>
27+
<p>{@html description}</p>
5728
{/if}
5829

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}
30+
<div class="content">
31+
{#each visiblePackages as pkg}
32+
<div class="item">
33+
<PackageCard {pkg} />
6934
</div>
70-
</div>
35+
{/each}
36+
</div>
7137

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>
38+
{#if packages.length > INITIAL_ITEMS}
39+
<div class="show-more-container">
40+
<label>
41+
<button
42+
class="raised"
43+
aria-label="Show more"
44+
aria-pressed={showAll}
45+
onclick={(e) => {
46+
const { bottom } = header.getBoundingClientRect();
47+
48+
// if the current section is wholly visible, don't muck about with the scroll position
49+
if (!showAll || bottom > 0) {
50+
showAll = !showAll;
51+
return;
52+
}
53+
54+
// otherwise, keep the button in the same position
55+
fix_position(e.currentTarget, () => {
56+
showAll = !showAll;
57+
});
58+
}}><span class="icon"></span></button
59+
>
60+
61+
{showAll ? 'show less' : `show all (${packages.length})`}
62+
</label>
8963
</div>
90-
</div>
64+
{/if}
9165
</section>
9266

9367
<style>
9468
.category {
95-
--bleed: var(--sk-page-padding-side);
96-
margin-bottom: 4rem;
69+
margin-bottom: 3rem;
9770
}
9871
99-
header {
100-
display: flex;
101-
margin-bottom: 1rem;
102-
align-items: center;
103-
gap: 2rem;
104-
105-
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-
}
136-
}
72+
h2 {
73+
margin: 0 0 1rem 0;
13774
}
13875
13976
h3 {
14077
font: var(--sk-font-ui-medium);
14178
font-size: 1.5rem;
14279
}
14380
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-
18381
.content {
18482
display: grid;
185-
grid-auto-columns: 34rem;
186-
grid-auto-flow: column;
83+
grid-template-columns: 1fr;
18784
gap: 2rem;
188-
width: fit-content;
85+
margin-top: 1rem;
86+
87+
@media (min-width: 1024px) {
88+
grid-template-columns: repeat(3, 1fr);
89+
}
18990
}
19091
19192
.item {
19293
height: 16rem;
193-
scroll-snap-align: start;
94+
min-width: 0; /* Prevents grid items from overflowing */
95+
}
96+
97+
.show-more-container {
98+
display: flex;
99+
justify-content: flex-end;
100+
margin-top: 2rem;
101+
102+
label {
103+
font: var(--sk-font-ui-small);
104+
display: flex;
105+
align-items: center;
106+
gap: 1rem;
107+
108+
.icon {
109+
mask-size: 2rem;
110+
mask-image: url(icons/minus);
111+
}
112+
113+
button[aria-pressed='false'] .icon {
114+
mask-image: url(icons/plus);
115+
}
116+
}
117+
118+
button {
119+
order: 1;
120+
}
121+
122+
@media (min-width: 1024px) {
123+
justify-content: flex-start;
124+
125+
button {
126+
order: 0;
127+
}
128+
}
194129
}
195130
</style>

0 commit comments

Comments
 (0)