@grove-dev/starlight 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/components/custom/ContainerSection.astro +58 -0
  4. package/components/custom/LinkButton.astro +141 -0
  5. package/components/custom/dropdown/Dropdown.astro +616 -0
  6. package/components/custom/dropdown/DropdownContent.astro +179 -0
  7. package/components/custom/dropdown/DropdownItem.astro +72 -0
  8. package/components/custom/dropdown/DropdownLabel.astro +31 -0
  9. package/components/custom/dropdown/DropdownSeparator.astro +18 -0
  10. package/components/custom/dropdown/DropdownShortcut.astro +24 -0
  11. package/components/custom/dropdown/DropdownTrigger.astro +54 -0
  12. package/components/custom/dropdown/index.ts +29 -0
  13. package/components/overrides/ContentPanel.astro +1 -0
  14. package/components/overrides/Footer.astro +54 -0
  15. package/components/overrides/Header.astro +93 -0
  16. package/components/overrides/Hero.astro +260 -0
  17. package/components/overrides/MarkdownContent.astro +17 -0
  18. package/components/overrides/PageFrame.astro +121 -0
  19. package/components/overrides/PageSidebar.astro +20 -0
  20. package/components/overrides/PageTitle.astro +130 -0
  21. package/components/overrides/Pagination.astro +101 -0
  22. package/components/overrides/Search.astro +549 -0
  23. package/components/overrides/Sidebar.astro +111 -0
  24. package/components/overrides/SiteTitle.astro +65 -0
  25. package/components/overrides/SocialIcons.astro +47 -0
  26. package/components/overrides/TableOfContents.astro +36 -0
  27. package/components/overrides/ThemeSelect.astro +156 -0
  28. package/components/overrides/TwoColumnContent.astro +75 -0
  29. package/components/overrides/parts/Drawer.astro +232 -0
  30. package/components/overrides/parts/MobileMenuToggle.astro +42 -0
  31. package/components/overrides/parts/NavBar.astro +110 -0
  32. package/components/overrides/parts/SidebarSublist.astro +115 -0
  33. package/components/overrides/parts/toc/TableOfContentsList.astro +71 -0
  34. package/components/overrides/parts/toc/starlight-toc.ts +119 -0
  35. package/core/config/constants.ts +1 -0
  36. package/core/config/expresive-code.ts +63 -0
  37. package/core/config/override.ts +48 -0
  38. package/core/config/schemas.ts +57 -0
  39. package/core/config/vite.ts +20 -0
  40. package/core/plugin.ts +56 -0
  41. package/global.d.ts +5 -0
  42. package/index.ts +3 -0
  43. package/package.json +54 -0
  44. package/schema.ts +24 -0
  45. package/styles/base.css +897 -0
  46. package/styles/layers.css +1 -0
  47. package/styles/theme.css +67 -0
  48. package/user-components.ts +3 -0
  49. package/virtual.d.ts +51 -0
@@ -0,0 +1,260 @@
1
+ ---
2
+ import { Icon } from '@astrojs/starlight/components';
3
+ import { Image } from 'astro:assets';
4
+ import { PAGE_TITLE_ID } from '../../core/config/constants';
5
+ import type { HeroLayout } from '../../schema';
6
+ import ContainerSection from '../custom/ContainerSection.astro';
7
+ import LinkButton from '../custom/LinkButton.astro';
8
+
9
+ const { data } = Astro.locals.starlightRoute.entry;
10
+ const { title = data.title, tagline: heroTagline, image, actions = [] } = data.hero || {};
11
+ const heroLayout = ((data.hero as any)?.layout as HeroLayout) ?? 'centered';
12
+ const announcement =
13
+ ((data.hero as any)?.announcement as { text: string; link: string }) ?? undefined;
14
+ const hasImage = !!image;
15
+ const tagline = heroTagline || data.description;
16
+
17
+ const imageAttrs = {
18
+ loading: 'eager' as const,
19
+ decoding: 'async' as const,
20
+ alt: image?.alt || '',
21
+ };
22
+
23
+ let darkImage: ImageMetadata | undefined;
24
+ let lightImage: ImageMetadata | undefined;
25
+ let rawHtml: string | undefined;
26
+
27
+ if (image) {
28
+ if ('file' in image) {
29
+ darkImage = image.file;
30
+ } else if ('dark' in image) {
31
+ darkImage = image.dark;
32
+ lightImage = image.light;
33
+ } else {
34
+ rawHtml = image.html;
35
+ }
36
+ }
37
+ ---
38
+
39
+ <ContainerSection>
40
+ <div class="hero" data-layout={heroLayout} data-has-image={hasImage ? '' : undefined}>
41
+ {
42
+ hasImage && (
43
+ <div class="hero-image">
44
+ {darkImage && (
45
+ <Image
46
+ src={darkImage}
47
+ {...imageAttrs}
48
+ class:list={{ 'light:sl-hidden': Boolean(lightImage) }}
49
+ />
50
+ )}
51
+ {lightImage && (
52
+ <Image src={lightImage} {...imageAttrs} class="dark:sl-hidden" />
53
+ )}
54
+ {rawHtml && <div class="hero-html sl-flex" set:html={rawHtml} />}
55
+ </div>
56
+ )
57
+ }
58
+
59
+ <div class="hero-info">
60
+ <div class="hero-title-container">
61
+ {
62
+ announcement && (
63
+ <LinkButton href={announcement.link} variant="secondary" size="2xs">
64
+ {announcement.text}
65
+ <Icon name="right-arrow" size="16px" />
66
+ </LinkButton>
67
+ )
68
+ }
69
+
70
+ <h1 id={PAGE_TITLE_ID} data-page-title set:html={title} />
71
+ </div>
72
+
73
+ <div class="hero-content-container">
74
+ {tagline && <div class="tagline" set:html={tagline} />}
75
+
76
+ {
77
+ actions.length > 0 && (
78
+ <div class="actions">
79
+ {actions.map(
80
+ ({
81
+ attrs: { class: className, ...attrs } = {},
82
+ icon,
83
+ link: href,
84
+ text,
85
+ variant,
86
+ }) => (
87
+ <LinkButton
88
+ {href}
89
+ {variant}
90
+ class:list={[className]}
91
+ {...attrs}
92
+ >
93
+ {text}
94
+ {icon?.html && <Fragment set:html={icon.html} />}
95
+ </LinkButton>
96
+ )
97
+ )}
98
+ </div>
99
+ )
100
+ }
101
+ </div>
102
+ </div>
103
+ </div>
104
+ </ContainerSection>
105
+
106
+ <style>
107
+ .hero {
108
+ display: flex;
109
+ flex-direction: column;
110
+ align-items: center;
111
+ justify-content: center;
112
+ padding-block: calc(var(--spacing) * 20);
113
+ gap: calc(var(--spacing) * 12);
114
+ }
115
+
116
+ .hero-image {
117
+ max-width: 100%;
118
+ width: 100%;
119
+ }
120
+
121
+ .hero-image img {
122
+ width: 100%;
123
+ height: auto;
124
+ aspect-ratio: attr(width) / attr(height);
125
+ border-radius: var(--radius);
126
+ }
127
+
128
+ .hero-info {
129
+ display: flex;
130
+ text-align: center;
131
+ align-items: center;
132
+ flex-direction: column;
133
+ gap: calc(var(--spacing) * 2);
134
+
135
+ @media (min-width: 768px) {
136
+ gap: calc(var(--spacing) * 4);
137
+ }
138
+ }
139
+
140
+ .hero-title-container {
141
+ display: flex;
142
+ flex-direction: column;
143
+ gap: calc(var(--spacing) * 2);
144
+ align-items: center;
145
+
146
+ h1 {
147
+ font-size: 1.875rem;
148
+ font-weight: 600;
149
+ line-height: 1.1;
150
+ text-wrap-style: balance;
151
+ letter-spacing: var(--spacing-tighter);
152
+ }
153
+
154
+ @media (min-width: 768px) {
155
+ gap: calc(var(--spacing) * 4);
156
+
157
+ h1 {
158
+ font-size: 3rem;
159
+ }
160
+ }
161
+ }
162
+
163
+ .hero-content-container {
164
+ display: flex;
165
+ flex-direction: column;
166
+ gap: calc(var(--spacing) * 2);
167
+
168
+ .tagline {
169
+ max-width: 56rem;
170
+ font-size: 1.125rem;
171
+ text-wrap: balance;
172
+ line-height: 1.75rem;
173
+ }
174
+
175
+ .actions {
176
+ display: flex;
177
+ justify-content: center;
178
+ padding-top: calc(var(--spacing) * 2);
179
+ gap: calc(var(--spacing) * 2);
180
+ }
181
+
182
+ @media (min-width: 768px) {
183
+ gap: calc(var(--spacing) * 4);
184
+ }
185
+ }
186
+
187
+ /* layouts */
188
+
189
+ .hero[data-layout='centered-top'],
190
+ .hero[data-layout='banner'] {
191
+ flex-direction: column-reverse;
192
+ gap: calc(var(--spacing) * 20);
193
+ padding-block-end: calc(var(--spacing) * 8);
194
+ }
195
+
196
+ .hero[data-layout='banner'] {
197
+ @media (min-width: 1024px) {
198
+ .hero-info {
199
+ width: 100%;
200
+ flex-direction: row;
201
+ gap: calc(var(--spacing) * 16);
202
+
203
+ .hero-title-container {
204
+ flex: 1;
205
+ align-items: flex-start;
206
+ text-align: left;
207
+
208
+ h1 {
209
+ text-wrap-style: pretty;
210
+ }
211
+ }
212
+
213
+ .hero-content-container {
214
+ flex: 1;
215
+ align-items: flex-start;
216
+ align-self: flex-end;
217
+ text-align: left;
218
+
219
+ .tagline {
220
+ text-wrap-style: pretty;
221
+ }
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ .hero[data-layout='split-left'],
228
+ .hero[data-layout='split-right'] {
229
+ flex-direction: column;
230
+
231
+ .hero-image {
232
+ order: 0;
233
+ }
234
+
235
+ @media (min-width: 1024px) {
236
+ flex-direction: row;
237
+ gap: calc(var(--spacing) * 16);
238
+
239
+ .hero-info {
240
+ align-items: flex-start;
241
+
242
+ .hero-title-container {
243
+ align-items: flex-start;
244
+ text-align: left;
245
+ }
246
+
247
+ .hero-content-container {
248
+ align-items: flex-start;
249
+ text-align: left;
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ .hero[data-layout='split-left'] {
256
+ .hero-image {
257
+ order: 1;
258
+ }
259
+ }
260
+ </style>
@@ -0,0 +1,17 @@
1
+ ---
2
+ import '@astrojs/starlight/style/markdown.css';
3
+ ---
4
+
5
+ <div class="markdown-wrapper">
6
+ <div class="sl-markdown-content">
7
+ <slot />
8
+ </div>
9
+ </div>
10
+
11
+ <style>
12
+ .markdown-wrapper {
13
+ --foreground: var(--markdown-foreground, var(--foreground));
14
+
15
+ color: var(--foreground);
16
+ }
17
+ </style>
@@ -0,0 +1,121 @@
1
+ ---
2
+ import { marked } from 'marked';
3
+ import userConfig from 'virtual:lucode-starlight-config';
4
+ import Drawer from './parts/Drawer.astro';
5
+
6
+ const { hasSidebar } = Astro.locals.starlightRoute;
7
+ ---
8
+
9
+ <div data-slot="layout">
10
+ <header>
11
+ <div class="container-wrapper px-6">
12
+ <div class="container">
13
+ <slot name="header" />
14
+ </div>
15
+ </div>
16
+ </header>
17
+
18
+ <main>
19
+ {!hasSidebar && <slot />}
20
+
21
+ {
22
+ hasSidebar && (
23
+ <div class="container-wrapper">
24
+ <div class="container container-wrapper-main">
25
+ <aside class="aside">
26
+ <slot name="sidebar" />
27
+ </aside>
28
+
29
+ <slot />
30
+ </div>
31
+ </div>
32
+ )
33
+ }
34
+ </main>
35
+
36
+ <footer>
37
+ <div data-slot="footer-text" set:html={marked.parseInline(userConfig.footerText)} />
38
+ </footer>
39
+ </div>
40
+
41
+ <Drawer />
42
+
43
+ <style>
44
+ [data-slot='layout'] {
45
+ /* group/layout relative z-10 flex min-h-svh flex-col bg-background */
46
+ position: relative;
47
+ z-index: 10;
48
+ display: flex;
49
+ flex-direction: column;
50
+ min-height: 100svh;
51
+ background: var(--background);
52
+
53
+ header {
54
+ /* sticky top-0 z-50 w-full bg-background */
55
+ position: sticky;
56
+ top: 0;
57
+ z-index: 50;
58
+ width: 100%;
59
+ background: var(--background);
60
+
61
+ .container-wrapper {
62
+ padding-inline: calc(var(--spacing) * 6);
63
+
64
+ .container {
65
+ /* flex h-(--header-height) items-center **:data-[slot=separator]:h-4! group-has-data-[slot=designer]/layout:fixed:max-w-none 3xl:fixed:container */
66
+ display: flex;
67
+ height: var(--header-height);
68
+ align-items: center;
69
+ max-width: none;
70
+ }
71
+ }
72
+ }
73
+
74
+ > main {
75
+ /* flex min-h-0 flex-1 flex-col */
76
+ display: flex;
77
+ flex-direction: column;
78
+ flex: 1;
79
+ min-height: 0;
80
+ }
81
+
82
+ > footer {
83
+ border-top: none;
84
+ height: 6rem;
85
+ display: flex;
86
+ align-items: center;
87
+
88
+ [data-slot='footer-text'] {
89
+ width: 100%;
90
+ max-width: var(--container-max-width);
91
+ margin-inline: auto;
92
+ padding-inline: calc(var(--spacing) * 6);
93
+ font-size: 0.875rem;
94
+ line-height: 1.25rem;
95
+ color: var(--muted-foreground);
96
+ text-wrap: balance;
97
+ text-align: center;
98
+ }
99
+
100
+ &:is(:where(body):has(*[data-slot='docs']) *) {
101
+ display: none;
102
+ }
103
+
104
+ &:is(:where(body):has(.docs-nav)) {
105
+ padding-bottom: calc(var(--spacing) * 20);
106
+
107
+ @media (min-width: 640px) {
108
+ padding-bottom: 0;
109
+ }
110
+ }
111
+
112
+ &:is(:where(body):has(.section-soft)) {
113
+ background: var(--surface-40);
114
+ }
115
+
116
+ &:is(:where(body):has(*[data-slot='designer']) *) {
117
+ display: none;
118
+ }
119
+ }
120
+ }
121
+ </style>
@@ -0,0 +1,20 @@
1
+ ---
2
+ import TableOfContents from './TableOfContents.astro';
3
+ ---
4
+
5
+ {
6
+ Astro.locals.starlightRoute.toc && (
7
+ <div>
8
+ <TableOfContents />
9
+ </div>
10
+ )
11
+ }
12
+
13
+ <style>
14
+ div {
15
+ padding-top: calc(var(--spacing) * 4);
16
+ max-height: calc(100vh - var(--header-height) - 1px);
17
+ top: calc(var(--header-height) + 1px);
18
+ position: sticky;
19
+ }
20
+ </style>
@@ -0,0 +1,130 @@
1
+ ---
2
+ import { Icon } from '@astrojs/starlight/components';
3
+ import { PAGE_TITLE_ID } from '../../core/config/constants';
4
+ import userConfig from 'virtual:lucode-starlight-config';
5
+ import { Dropdown, DropdownContent, DropdownItem, DropdownTrigger } from '../custom/dropdown';
6
+ import LinkButton from '../custom/LinkButton.astro';
7
+
8
+ const {
9
+ entry: { data },
10
+ pagination: { prev, next },
11
+ } = Astro.locals.starlightRoute;
12
+
13
+ const { title, description } = data;
14
+ const incudeAIMenu = userConfig.docs?.includeAiUtilities ?? false;
15
+
16
+ const encode = (url: string, text: string) => `https://${url}=${encodeURIComponent(text)}`;
17
+ const aiText = `I'm looking at: ${Astro.url}.\nHelp me understand how to use it. Be ready to explain concepts, give examples, or help debug based on it.`;
18
+ const openAIUrl = encode('chatgpt.com?q', aiText);
19
+ const claudeUrl = encode('claude.ai/new?q', aiText);
20
+ ---
21
+
22
+ <div data-slot="doc-title">
23
+ <div data-slot="doc-title-header">
24
+ <h1 id={PAGE_TITLE_ID}>{title}</h1>
25
+
26
+ <div data-slot="doc-title-actions">
27
+ {
28
+ incudeAIMenu && (
29
+ <Dropdown>
30
+ <DropdownTrigger variant="secondary" size="sm">
31
+ AI tools
32
+ <span
33
+ data-slot="separator"
34
+ style="display: inline-block; width: 1px; height: 50%; background-color: var(--foreground); opacity: 0.1;"
35
+ />
36
+ <Icon name="down-caret" size="1rem" />
37
+ </DropdownTrigger>
38
+ <DropdownContent align="end">
39
+ <DropdownItem as="a" href={openAIUrl} target="_blank" rel="noopener">
40
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
41
+ <path
42
+ d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08-4.778 2.758a.795.795 0 0 0-.393.681zm1.097-2.365 2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5Z"
43
+ fill="currentColor"
44
+ />
45
+ </svg>
46
+ Open in ChatGPT
47
+ </DropdownItem>
48
+ <DropdownItem as="a" href={claudeUrl} target="_blank" rel="noopener">
49
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
50
+ <path
51
+ d="m4.714 15.956 4.718-2.648.079-.23-.08-.128h-.23l-.79-.048-2.695-.073-2.337-.097-2.265-.122-.57-.121-.535-.704.055-.353.48-.321.685.06 1.518.104 2.277.157 1.651.098 2.447.255h.389l.054-.158-.133-.097-.103-.098-2.356-1.596-2.55-1.688-1.336-.972-.722-.491L2 6.223l-.158-1.008.655-.722.88.06.225.061.893.686 1.906 1.476 2.49 1.833.364.304.146-.104.018-.072-.164-.274-1.354-2.446-1.445-2.49-.644-1.032-.17-.619a2.972 2.972 0 0 1-.103-.729L6.287.133 6.7 0l.995.134.42.364.619 1.415L9.735 4.14l1.555 3.03.455.898.243.832.09.255h.159V9.01l.127-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.583.28.48.685-.067.444-.286 1.851-.558 2.903-.365 1.942h.213l.243-.242.983-1.306 1.652-2.064.728-.82.85-.904.547-.431h1.032l.759 1.129-.34 1.166-1.063 1.347-.88 1.142-1.263 1.7-.79 1.36.074.11.188-.02 2.853-.606 1.542-.28 1.84-.315.832.388.09.395-.327.807-1.967.486-2.307.462-3.436.813-.043.03.049.061 1.548.146.662.036h1.62l3.018.225.79.522.473.638-.08.485-1.213.62-1.64-.389-3.825-.91-1.31-.329h-.183v.11l1.093 1.068 2.003 1.81 2.508 2.33.127.578-.321.455-.34-.049-2.204-1.657-.85-.747-1.925-1.62h-.127v.17l.443.649 2.343 3.521.122 1.08-.17.353-.607.213-.668-.122-1.372-1.924-1.415-2.168-1.141-1.943-.14.08-.674 7.254-.316.37-.728.28-.607-.461-.322-.747.322-1.476.388-1.924.316-1.53.285-1.9.17-.632-.012-.042-.14.018-1.432 1.967-2.18 2.945-1.724 1.845-.413.164-.716-.37.066-.662.401-.589 2.386-3.036 1.439-1.882.929-1.086-.006-.158h-.055L4.138 18.56l-1.13.146-.485-.456.06-.746.231-.243 1.907-1.312Z"
52
+ fill="currentColor"
53
+ />
54
+ </svg>
55
+ Open in Claude
56
+ </DropdownItem>
57
+ </DropdownContent>
58
+ </Dropdown>
59
+ )
60
+ }
61
+
62
+ {
63
+ prev && (
64
+ <LinkButton size="icon-md" variant="secondary" href={prev.href}>
65
+ <Icon name="left-arrow" />
66
+ </LinkButton>
67
+ )
68
+ }
69
+
70
+ {
71
+ next && (
72
+ <LinkButton size="icon-md" variant="secondary" href={next.href}>
73
+ <Icon name="right-arrow" />
74
+ </LinkButton>
75
+ )
76
+ }
77
+ </div>
78
+ </div>
79
+
80
+ {description && <p slot="doc-title-description">{description}</p>}
81
+
82
+ <!-- -->
83
+ </div>
84
+
85
+ <style>
86
+ div[data-slot='doc-title'] {
87
+ display: flex;
88
+ flex-direction: column;
89
+ gap: calc(var(--spacing) * 2);
90
+ }
91
+
92
+ [data-slot='doc-title-header'] {
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: space-between;
96
+ gap: calc(var(--spacing) * 2);
97
+
98
+ @media (min-width: 640px) {
99
+ align-items: start;
100
+ }
101
+
102
+ h1 {
103
+ letter-spacing: var(--tracking-tight);
104
+ font-weight: 600;
105
+ font-size: 1.875rem;
106
+ line-height: 2.25rem;
107
+ scroll-margin: 5rem;
108
+ margin: 0;
109
+ color: var(--foreground);
110
+ }
111
+
112
+ [data-slot='doc-title-actions'] {
113
+ display: flex;
114
+ align-items: center;
115
+ gap: calc(var(--spacing) * 2);
116
+ }
117
+ }
118
+
119
+ [slot='doc-title-description'] {
120
+ margin: 0;
121
+ color: var(--muted-foreground);
122
+ font-size: 1rem;
123
+ line-height: 1.75rem;
124
+ text-wrap: balance;
125
+
126
+ @media (min-width: 640px) {
127
+ max-width: 80%;
128
+ }
129
+ }
130
+ </style>
@@ -0,0 +1,101 @@
1
+ ---
2
+ const { pagination } = Astro.locals.starlightRoute;
3
+ const { prev, next } = pagination;
4
+ ---
5
+
6
+ <div class="pagination-links print:hidden">
7
+ {
8
+ prev && (
9
+ <a class="prev" href={prev.href} rel="prev">
10
+ <svg
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ width="24"
13
+ height="24"
14
+ viewBox="0 0 24 24"
15
+ fill="none"
16
+ stroke="currentColor"
17
+ stroke-width="2"
18
+ stroke-linecap="round"
19
+ stroke-linejoin="round"
20
+ >
21
+ <path d="m15 18-6-6 6-6" />
22
+ </svg>
23
+ {prev.label}
24
+ </a>
25
+ )
26
+ }
27
+ {
28
+ next && (
29
+ <a class="next" href={next.href} rel="next">
30
+ {next.label}
31
+
32
+ <svg
33
+ xmlns="http://www.w3.org/2000/svg"
34
+ width="24"
35
+ height="24"
36
+ viewBox="0 0 24 24"
37
+ fill="none"
38
+ stroke="currentColor"
39
+ stroke-width="2"
40
+ stroke-linecap="round"
41
+ stroke-linejoin="round"
42
+ >
43
+ <path d="m9 18 6-6-6-6" />
44
+ </svg>
45
+ </a>
46
+ )
47
+ }
48
+ </div>
49
+
50
+ <style>
51
+ .pagination-links {
52
+ display: flex;
53
+ justify-content: space-between;
54
+ align-items: center;
55
+ flex-direction: row;
56
+ padding-top: calc(var(--spacing) * 4);
57
+ margin-top: calc(var(--spacing) * 8);
58
+ }
59
+
60
+ a {
61
+ color: var(--secondary-foreground);
62
+ text-decoration: inherit;
63
+ background-color: var(--secondary);
64
+
65
+ transition-property:
66
+ color, background-color, border-color, text-decoration-color, fill, stroke;
67
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
68
+ transition-duration: 0.15s;
69
+ font-weight: 500;
70
+ font-size: 0.875rem;
71
+ line-height: 1.25rem;
72
+ padding-top: calc(var(--spacing) * 2);
73
+ padding-bottom: calc(var(--spacing) * 2);
74
+ padding-left: calc(var(--spacing) * 3);
75
+ padding-right: calc(var(--spacing) * 3);
76
+ border-radius: calc(var(--radius) - 2px);
77
+ white-space: nowrap;
78
+ gap: 0.375rem;
79
+ justify-content: center;
80
+ align-items: center;
81
+ height: 2rem;
82
+ display: inline-flex;
83
+ }
84
+
85
+ a:hover {
86
+ background-color: color-mix(in srgb, var(--secondary) 80%, transparent);
87
+ }
88
+
89
+ a > svg {
90
+ flex-shrink: 0;
91
+ width: 1rem;
92
+ height: 1rem;
93
+ pointer-events: none;
94
+ display: block;
95
+ vertical-align: middle;
96
+ }
97
+
98
+ .next {
99
+ margin-left: auto;
100
+ }
101
+ </style>