Coves frontend - a photon fork
1<script lang="ts" generics="T">
2 import type { Snippet } from 'svelte'
3 import type { ClassValue } from 'svelte/elements'
4 const sizeDelay: Record<'lg' | 'md' | 'sm' | 'xs', number> = {
5 lg: 120,
6 md: 80,
7 sm: 40,
8 xs: 0,
9 }
10
11 interface Props {
12 items?: T[]
13 item?: Snippet<[T, number]>
14 children?: Snippet
15 animate?: boolean
16 size?: 'lg' | 'md' | 'sm' | 'xs'
17 class?: ClassValue
18 selected?: (item: T) => boolean
19 }
20
21 let {
22 items,
23 item: itemSnippet,
24 children,
25 animate = true,
26 size = 'sm',
27 class: clazz,
28 selected,
29 }: Props = $props()
30</script>
31
32<ul>
33 {#if items}
34 {#each items as item, index (item)}
35 <li
36 class={[
37 'group/li',
38 animate && 'animate',
39 size == 'xs' && 'xs',
40 selected?.(item) && 'selected',
41 clazz,
42 ]}
43 style="--i: {index < 10 ? index * sizeDelay[size] : 0}ms;"
44 >
45 {@render itemSnippet?.(item, index)}
46 </li>
47 {/each}
48 {/if}
49 {@render children?.()}
50</ul>
51
52<style>
53 @reference '../../../app.css';
54 ul {
55 :global {
56 :where(& > :not(:last-child)) {
57 margin-block-start: calc(calc(var(--spacing) * 1));
58 margin-block-end: calc(calc(var(--spacing) * 1));
59 }
60 }
61 }
62 ul > :global(li) {
63 transition: background-color 0.1s;
64 background-color: var(--color-white);
65 container-type: inline-size;
66 border: 1px solid
67 color-mix(in oklab, var(--color-slate-50), var(--color-slate-100));
68
69 border-radius: var(--radius-lg) var(--radius-lg);
70
71 &:first-child {
72 border-top-left-radius: var(--radius-2xl);
73 border-top-right-radius: var(--radius-2xl);
74 }
75 &:last-child {
76 border-bottom-left-radius: var(--radius-2xl);
77 border-bottom-right-radius: var(--radius-2xl);
78 }
79
80 &:hover {
81 background-color: var(--color-slate-25);
82 }
83 }
84
85 ul > :global(li:not(.xs) > *) {
86 padding-block: calc(var(--spacing) * 3);
87 padding-inline: calc(var(--spacing) * 3.5);
88
89 @media screen and (min-width: 40rem) {
90 padding-block: calc(var(--spacing) * 3);
91 padding-inline: calc(var(--spacing) * 4);
92 }
93 }
94
95 ul > :global(li.xs > *) {
96 padding-block: calc(var(--spacing) * 0.5);
97 padding-block: calc(var(--spacing) * 1);
98 }
99
100 ul > :global(li.selected) {
101 background-color: color-mix(
102 in oklab,
103 var(--color-white),
104 var(--color-slate-50)
105 );
106 }
107
108 :global(.dark) ul > :global(li.selected) {
109 background-color: color-mix(
110 in oklab,
111 var(--color-zinc-900) 90%,
112 var(--color-zinc-800)
113 );
114 }
115
116 :global(.dark) ul > :global(li) {
117 border: 1px solid var(--color-zinc-900);
118 background-color: color-mix(
119 in oklab,
120 var(--color-zinc-925),
121 var(--color-zinc-900)
122 );
123
124 &:hover {
125 background-color: var(--color-zinc-900);
126 }
127
128 &:active {
129 background-color: color-mix(
130 in oklab,
131 var(--color-zinc-900) 80%,
132 var(--color-zinc-925)
133 );
134 }
135 }
136
137 @keyframes pop-in {
138 from {
139 transform: translateY(16px);
140 opacity: 0;
141 }
142 to {
143 transform: translateY(0px);
144 opacity: 1;
145 }
146 }
147
148 .animate {
149 --delay: var(--i, 0ms);
150 }
151
152 :global(.animate > *:only-child) {
153 animation: pop-in 1s forwards cubic-bezier(0.16, 1, 0.3, 1);
154 animation-delay: var(--delay, 0ms);
155 opacity: 0;
156 }
157
158 :global(.animate:not(:has(> * + *))) {
159 animation: pop-in 1s forwards cubic-bezier(0.16, 1, 0.3, 1);
160 animation-delay: var(--delay, 0ms);
161 opacity: 0;
162 }
163
164 @media (prefers-reduced-motion: reduce) {
165 :global(.animate > *:only-child) {
166 animation: none;
167 opacity: 1;
168 }
169 :global(.animate:not(:has(> * + *))) {
170 animation: none;
171 opacity: 1;
172 }
173 }
174</style>