Skip to content

Commit fce6d69

Browse files
feat(home-page): add version type filter
Resolves #45
1 parent ddf088e commit fce6d69

File tree

11 files changed

+246
-26
lines changed

11 files changed

+246
-26
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Root from "./toggle-group.svelte";
2+
import Item from "./toggle-group-item.svelte";
3+
4+
export {
5+
Root,
6+
Item,
7+
//
8+
Root as ToggleGroup,
9+
Item as ToggleGroupItem,
10+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<script lang="ts">
2+
import { ToggleGroup as ToggleGroupPrimitive } from "bits-ui";
3+
import { getToggleGroupCtx } from "./toggle-group.svelte";
4+
import { cn } from "$lib/utils.js";
5+
import { type ToggleVariants, toggleVariants } from "$lib/components/ui/toggle/index.js";
6+
7+
let {
8+
ref = $bindable(null),
9+
value = $bindable(),
10+
class: className,
11+
size,
12+
variant,
13+
...restProps
14+
}: ToggleGroupPrimitive.ItemProps & ToggleVariants = $props();
15+
16+
const ctx = getToggleGroupCtx();
17+
</script>
18+
19+
<ToggleGroupPrimitive.Item
20+
bind:ref
21+
data-slot="toggle-group-item"
22+
data-variant={ctx.variant || variant}
23+
data-size={ctx.size || size}
24+
class={cn(
25+
toggleVariants({
26+
variant: ctx.variant || variant,
27+
size: ctx.size || size,
28+
}),
29+
"min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
30+
className
31+
)}
32+
{value}
33+
{...restProps}
34+
/>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script lang="ts" module>
2+
import { getContext, setContext } from "svelte";
3+
import type { ToggleVariants } from "$lib/components/ui/toggle/index.js";
4+
export function setToggleGroupCtx(props: ToggleVariants) {
5+
setContext("toggleGroup", props);
6+
}
7+
8+
export function getToggleGroupCtx() {
9+
return getContext<ToggleVariants>("toggleGroup");
10+
}
11+
</script>
12+
13+
<script lang="ts">
14+
import { ToggleGroup as ToggleGroupPrimitive } from "bits-ui";
15+
import { cn } from "$lib/utils.js";
16+
17+
let {
18+
ref = $bindable(null),
19+
value = $bindable(),
20+
class: className,
21+
size = "default",
22+
variant = "default",
23+
...restProps
24+
}: ToggleGroupPrimitive.RootProps & ToggleVariants = $props();
25+
26+
setToggleGroupCtx({
27+
variant,
28+
size,
29+
});
30+
</script>
31+
32+
<!--
33+
Discriminated Unions + Destructing (required for bindable) do not
34+
get along, so we shut typescript up by casting `value` to `never`.
35+
-->
36+
<ToggleGroupPrimitive.Root
37+
bind:value={value as never}
38+
bind:ref
39+
data-slot="toggle-group"
40+
data-variant={variant}
41+
data-size={size}
42+
class={cn(
43+
"group/toggle-group data-[variant=outline]:shadow-xs flex w-fit items-center rounded-md",
44+
className
45+
)}
46+
{...restProps}
47+
/>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Root from "./toggle.svelte";
2+
export {
3+
toggleVariants,
4+
type ToggleSize,
5+
type ToggleVariant,
6+
type ToggleVariants,
7+
} from "./toggle.svelte";
8+
9+
export {
10+
Root,
11+
//
12+
Root as Toggle,
13+
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<script lang="ts" module>
2+
import { type VariantProps, tv } from "tailwind-variants";
3+
4+
export const toggleVariants = tv({
5+
base: "hover:bg-muted hover:text-muted-foreground data-[state=on]:bg-accent data-[state=on]:text-accent-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
6+
variants: {
7+
variant: {
8+
default: "bg-transparent",
9+
outline:
10+
"border-input shadow-xs hover:bg-accent hover:text-accent-foreground border bg-transparent",
11+
},
12+
size: {
13+
default: "h-9 min-w-9 px-2",
14+
sm: "h-8 min-w-8 px-1.5",
15+
lg: "h-10 min-w-10 px-2.5",
16+
},
17+
},
18+
defaultVariants: {
19+
variant: "default",
20+
size: "default",
21+
},
22+
});
23+
24+
export type ToggleVariant = VariantProps<typeof toggleVariants>["variant"];
25+
export type ToggleSize = VariantProps<typeof toggleVariants>["size"];
26+
export type ToggleVariants = VariantProps<typeof toggleVariants>;
27+
</script>
28+
29+
<script lang="ts">
30+
import { Toggle as TogglePrimitive } from "bits-ui";
31+
import { cn } from "$lib/utils.js";
32+
33+
let {
34+
ref = $bindable(null),
35+
pressed = $bindable(false),
36+
class: className,
37+
size = "default",
38+
variant = "default",
39+
...restProps
40+
}: TogglePrimitive.RootProps & {
41+
variant?: ToggleVariant;
42+
size?: ToggleSize;
43+
} = $props();
44+
</script>
45+
46+
<TogglePrimitive.Root
47+
bind:ref
48+
bind:pressed
49+
data-slot="toggle"
50+
class={cn(toggleVariants({ variant, size }), className)}
51+
{...restProps}
52+
/>

src/lib/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,6 @@ export const ALL_SLUG = "all";
124124
*/
125125
export type PackageSettings = {
126126
showPrereleases: boolean;
127+
releasesType: Lowercase<(typeof releasesTypes)[number]>;
127128
};
129+
export const releasesTypes = ["All", "Major", "Minor", "Patch"] as const;

src/routes/+layout.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
onNavigate(({ complete }) => {
2626
if (!document.startViewTransition) return;
2727
28-
return new Promise(resolve => {
28+
return new Promise(resolve =>
2929
document.startViewTransition(async () => {
3030
resolve();
3131
await complete;
32-
});
33-
});
32+
})
33+
);
3434
});
3535
3636
// SEO

src/routes/package/+layout.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
packageName={page.data.currentPackage.pkg.name}
5050
allPackages={data.displayablePackages}
5151
otherReleases={data.allReleases}
52-
bind:showPrereleases={packageSettings.current.showPrereleases}
52+
bind:settings={packageSettings.current}
5353
/>
5454
</Sheet.Content>
5555
</Sheet.Root>
@@ -62,6 +62,6 @@
6262
"mt-35 hidden h-fit w-100 shrink-0 lg:flex",
6363
page.data.currentPackage.pkg.description?.length && "mt-45"
6464
]}
65-
bind:showPrereleases={packageSettings.current.showPrereleases}
65+
bind:settings={packageSettings.current}
6666
/>
6767
</div>

src/routes/package/SidePanel.svelte

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
import { ChevronRight } from "@lucide/svelte";
77
import type { GitHubRelease } from "$lib/server/github-cache";
88
import type { CategorizedPackage } from "$lib/server/package-discoverer";
9-
import type { Prettify } from "$lib/types";
9+
import { type PackageSettings, type Prettify, releasesTypes } from "$lib/types";
1010
import { cn } from "$lib/utils";
1111
import { Badge } from "$lib/components/ui/badge";
1212
import * as Card from "$lib/components/ui/card";
1313
import { Checkbox } from "$lib/components/ui/checkbox";
1414
import { Label } from "$lib/components/ui/label";
1515
import { Separator } from "$lib/components/ui/separator";
16+
import * as ToggleGroup from "$lib/components/ui/toggle-group";
17+
import { DEFAULT_SETTINGS } from "./settings.svelte";
1618
1719
type CleanRelease = { cleanName: string; cleanVersion: string } & GitHubRelease;
1820
@@ -41,15 +43,15 @@
4143
| undefined
4244
>;
4345
};
44-
showPrereleases?: boolean;
46+
settings?: PackageSettings;
4547
headless?: boolean;
4648
class?: ClassValue;
4749
};
4850
let {
4951
packageName = "",
5052
allPackages = [],
51-
showPrereleases = $bindable(true),
5253
otherReleases = {},
54+
settings = $bindable(DEFAULT_SETTINGS),
5355
headless = false,
5456
class: className
5557
}: Props = $props();
@@ -222,25 +224,57 @@
222224
</Card.Content>
223225
</Card.Root>
224226
{#if headless}
225-
<Separator class="my-4" />
227+
<Separator class="my-4 rounded-full data-[orientation=horizontal]:h-1" />
228+
<h3 class="mb-6 text-xl font-semibold tracking-tight text-primary">Visibility settings</h3>
226229
{/if}
227230
<div
228231
class={[
229-
"flex items-center gap-2",
232+
"flex flex-col gap-2",
230233
!headless && "-mt-2 rounded-b-xl border-x border-b bg-card px-4 pt-5 pb-2.5"
231234
]}
232235
>
233-
<Checkbox
234-
id="beta-releases-{id}"
235-
aria-labelledby="beta-releases-label-{id}"
236-
bind:checked={showPrereleases}
237-
/>
238-
<Label
239-
id="beta-releases-label-{id}"
240-
for="beta-releases-{id}"
241-
class="text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
242-
>
243-
Show {packageName} prereleases
244-
</Label>
236+
<!-- Prereleases toggle -->
237+
<div class="flex items-center gap-2">
238+
<Checkbox
239+
id="beta-releases-{id}"
240+
aria-labelledby="beta-releases-label-{id}"
241+
bind:checked={
242+
() => settings.showPrereleases ?? DEFAULT_SETTINGS.showPrereleases,
243+
newState => (settings.showPrereleases = newState)
244+
}
245+
/>
246+
<Label
247+
id="beta-releases-label-{id}"
248+
for="beta-releases-{id}"
249+
class="text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
250+
>
251+
Show {packageName} prereleases
252+
</Label>
253+
</div>
254+
255+
<Separator class="mt-0.5" />
256+
257+
<!-- Version filtering -->
258+
<div class="flex items-center gap-2">
259+
<span class="text-sm leading-none font-medium text-nowrap">Show release types:</span>
260+
<ToggleGroup.Root
261+
type="single"
262+
bind:value={
263+
() => settings.releasesType ?? DEFAULT_SETTINGS.releasesType,
264+
newType => {
265+
// don't take in account deselections, naturally always leaving something selected
266+
if (newType) settings.releasesType = newType;
267+
}
268+
}
269+
size="sm"
270+
class="w-full"
271+
>
272+
{#each releasesTypes as releaseType (releaseType.toLowerCase())}
273+
<ToggleGroup.Item value={releaseType.toLowerCase()} class="h-auto py-0.5">
274+
{releaseType}
275+
</ToggleGroup.Item>
276+
{/each}
277+
</ToggleGroup.Root>
278+
</div>
245279
</div>
246280
</div>

src/routes/package/[...package]/+page.svelte

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
)
4949
.sort((a, b) => semver.compare(a.cleanVersion, b.cleanVersion))[0]
5050
);
51-
const sharedSettings = getPackageSettings()
51+
const sharedSettings = getPackageSettings();
5252
let packageSettings = $derived(sharedSettings.get(data.currentPackage.pkg.name));
5353
5454
let lastUpdateDate = $state<Date>();
@@ -64,7 +64,23 @@
6464
});
6565
6666
let displayableReleases = $derived(
67-
data.releases.filter(({ prerelease }) => packageSettings.current.showPrereleases || !prerelease)
67+
data.releases.filter(({ prerelease, cleanVersion }) => {
68+
const baseCondition = prerelease ? packageSettings.current.showPrereleases : true;
69+
switch (packageSettings.current.releasesType) {
70+
case "all":
71+
return baseCondition;
72+
case "major":
73+
return (
74+
baseCondition && semver.minor(cleanVersion) === 0 && semver.patch(cleanVersion) === 0
75+
);
76+
case "minor":
77+
return (
78+
baseCondition && semver.minor(cleanVersion) > 0 && semver.patch(cleanVersion) === 0
79+
);
80+
case "patch":
81+
return baseCondition && semver.patch(cleanVersion) > 0;
82+
}
83+
})
6884
);
6985
let expandableReleases = $derived.by(() => {
7086
const aWeekAgo = Date.now() - 1000 * 60 * 60 * 24 * 7;
@@ -248,6 +264,17 @@
248264
{isLatest}
249265
{isMaintenance}
250266
/>
267+
{:else}
268+
<div class="mt-8">
269+
<p class="font-display text-2xl font-semibold">Nothing to show here!</p>
270+
<p class="text-lg tracking-tight">
271+
{#if packageSettings.current.releasesType !== "all" || !packageSettings.current.showPrereleases}
272+
Try adjusting your visibility settings in the sidebar.
273+
{:else}
274+
If there was content, it would be here. Probably.
275+
{/if}
276+
</p>
277+
</div>
251278
{/each}
252279
</Accordion.Root>
253280
</div>

0 commit comments

Comments
 (0)