@lapis-notes/workspace 0.1.0 → 0.2.0

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 (77) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE.md +3 -3
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +3 -0
  5. package/dist/plugin-management/CommunityPluginsSettingsPage.svelte +152 -73
  6. package/dist/plugin-management/PluginBrowser.svelte +517 -0
  7. package/dist/plugin-management/PluginBrowser.svelte.d.ts +17 -0
  8. package/dist/plugin-management/PluginBrowserCard.svelte +230 -0
  9. package/dist/plugin-management/PluginBrowserCard.svelte.d.ts +9 -0
  10. package/dist/plugin-management/PluginBrowserDetail.svelte +1168 -0
  11. package/dist/plugin-management/PluginBrowserDetail.svelte.d.ts +20 -0
  12. package/dist/plugin-management/PluginBrowserDirectory.svelte +1069 -0
  13. package/dist/plugin-management/PluginBrowserDirectory.svelte.d.ts +21 -0
  14. package/dist/plugin-management/PluginBrowserLanding.svelte +341 -0
  15. package/dist/plugin-management/PluginBrowserLanding.svelte.d.ts +12 -0
  16. package/dist/plugin-management/PluginRegistryBadge.svelte +41 -0
  17. package/dist/plugin-management/PluginRegistryBadge.svelte.d.ts +8 -0
  18. package/dist/plugin-management/PluginRegistryContentState.svelte +80 -0
  19. package/dist/plugin-management/PluginRegistryContentState.svelte.d.ts +14 -0
  20. package/dist/plugin-management/PluginRegistryImage.svelte +64 -0
  21. package/dist/plugin-management/PluginRegistryImage.svelte.d.ts +9 -0
  22. package/dist/plugin-management/PluginRegistryMedia.svelte +223 -0
  23. package/dist/plugin-management/PluginRegistryMedia.svelte.d.ts +8 -0
  24. package/dist/plugin-management/PluginRegistryMediaViewer.svelte +155 -0
  25. package/dist/plugin-management/PluginRegistryMediaViewer.svelte.d.ts +13 -0
  26. package/dist/plugin-management/PluginRegistryRow.svelte +120 -0
  27. package/dist/plugin-management/PluginRegistryRow.svelte.d.ts +25 -0
  28. package/dist/plugin-management/PluginRegistrySettingsPage.svelte +1009 -512
  29. package/dist/plugin-management/PluginRegistryStatus.svelte +33 -0
  30. package/dist/plugin-management/PluginRegistryStatus.svelte.d.ts +8 -0
  31. package/dist/plugin-management/PluginRegistryToolbar.svelte +159 -0
  32. package/dist/plugin-management/PluginRegistryToolbar.svelte.d.ts +21 -0
  33. package/dist/plugin-management/community-presentation/CommunityChip.svelte +229 -0
  34. package/dist/plugin-management/community-presentation/CommunityChip.svelte.d.ts +4 -0
  35. package/dist/plugin-management/community-presentation/CommunityMemberAvatar.svelte +42 -0
  36. package/dist/plugin-management/community-presentation/CommunityMemberAvatar.svelte.d.ts +13 -0
  37. package/dist/plugin-management/community-presentation/RegistryMediaGallery.svelte +282 -0
  38. package/dist/plugin-management/community-presentation/RegistryMediaGallery.svelte.d.ts +8 -0
  39. package/dist/plugin-management/community-presentation/RegistryMediaViewer.css +179 -0
  40. package/dist/plugin-management/community-presentation/RegistryMediaViewer.svelte +241 -0
  41. package/dist/plugin-management/community-presentation/RegistryMediaViewer.svelte.d.ts +14 -0
  42. package/dist/plugin-management/community-presentation/RegistryPluginChangelog.svelte +216 -0
  43. package/dist/plugin-management/community-presentation/RegistryPluginChangelog.svelte.d.ts +8 -0
  44. package/dist/plugin-management/community-presentation/RegistryVerifiedImage.svelte +179 -0
  45. package/dist/plugin-management/community-presentation/RegistryVerifiedImage.svelte.d.ts +15 -0
  46. package/dist/plugin-management/community-presentation/avatar-model.d.ts +14 -0
  47. package/dist/plugin-management/community-presentation/avatar-model.js +21 -0
  48. package/dist/plugin-management/community-presentation/community-member-avatar.css +137 -0
  49. package/dist/plugin-management/community-presentation/community-typography.css +30 -0
  50. package/dist/plugin-management/community-presentation/model-adapter.d.ts +54 -0
  51. package/dist/plugin-management/community-presentation/model-adapter.js +90 -0
  52. package/dist/plugin-management/community-presentation/model.d.ts +9 -0
  53. package/dist/plugin-management/community-presentation/model.js +40 -0
  54. package/dist/plugin-management/community-presentation/props.d.ts +21 -0
  55. package/dist/plugin-management/community-presentation/props.js +1 -0
  56. package/dist/plugin-management/community-presentation/registry-filter-icons.d.ts +3 -0
  57. package/dist/plugin-management/community-presentation/registry-filter-icons.js +70 -0
  58. package/dist/plugin-management/community-presentation/registry-markdown.d.ts +1 -0
  59. package/dist/plugin-management/community-presentation/registry-markdown.js +57 -0
  60. package/dist/plugin-management/community-presentation/registry-plugin-icons.d.ts +2 -0
  61. package/dist/plugin-management/community-presentation/registry-plugin-icons.js +31 -0
  62. package/dist/plugin-management/plugin-browser-model.d.ts +43 -0
  63. package/dist/plugin-management/plugin-browser-model.js +95 -0
  64. package/dist/plugin-management/plugin-browser-navigation.d.ts +3 -0
  65. package/dist/plugin-management/plugin-browser-navigation.js +36 -0
  66. package/dist/plugin-management/plugin-download-stats.d.ts +1 -0
  67. package/dist/plugin-management/plugin-download-stats.js +15 -0
  68. package/dist/plugin-management/plugin-management.css +1088 -117
  69. package/dist/plugin-management/plugin-registry-controller.svelte.d.ts +29 -0
  70. package/dist/plugin-management/plugin-registry-controller.svelte.js +154 -0
  71. package/dist/plugin-management/plugin-registry-detail-session.svelte.d.ts +18 -0
  72. package/dist/plugin-management/plugin-registry-detail-session.svelte.js +106 -0
  73. package/dist/plugin-management/plugin-registry-detail.d.ts +17 -0
  74. package/dist/plugin-management/plugin-registry-detail.js +1 -0
  75. package/dist/plugin-management/plugins-view.svelte.d.ts +40 -0
  76. package/dist/plugin-management/plugins-view.svelte.js +105 -0
  77. package/package.json +5 -5
@@ -0,0 +1,1168 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ import * as Alert from "@lapismd/design-core/shadcn/alert";
5
+ import { ConfirmDialog } from "@lapismd/design-core/forms/dialog";
6
+ import { Button } from "@lapismd/design-core/shadcn/button";
7
+ import * as DropdownMenu from "@lapismd/design-core/shadcn/dropdown-menu";
8
+ import * as Tabs from "@lapismd/design-core/shadcn/tabs";
9
+ import * as Select from "@lapismd/design-core/shadcn/select";
10
+ import BookOpenIcon from "@lucide/svelte/icons/book-open";
11
+ import BugIcon from "@lucide/svelte/icons/bug";
12
+ import CheckCircleIcon from "@lucide/svelte/icons/circle-check";
13
+ import CircleAlertIcon from "@lucide/svelte/icons/circle-alert";
14
+ import DownloadIcon from "@lucide/svelte/icons/download";
15
+ import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
16
+ import ExternalLinkIcon from "@lucide/svelte/icons/external-link";
17
+
18
+ import {
19
+ formatRegistryBytes,
20
+ registryStatusLabel,
21
+ titleCaseRegistryValue,
22
+ type RegistryCatalogEntry,
23
+ type RegistryInstallAction,
24
+ type RegistryInstallRequest,
25
+ type RegistryMediaItem,
26
+ type RegistryResolvedDetail,
27
+ } from "./community-presentation/model-adapter";
28
+ import CommunityChip from "./community-presentation/CommunityChip.svelte";
29
+ import { registryFilterIcon } from "./community-presentation/registry-filter-icons.js";
30
+ import RegistryMediaGallery from "./community-presentation/RegistryMediaGallery.svelte";
31
+ import RegistryMediaViewer from "./community-presentation/RegistryMediaViewer.svelte";
32
+ import CommunityMemberAvatar from "./community-presentation/CommunityMemberAvatar.svelte";
33
+ import RegistryPluginChangelog from "./community-presentation/RegistryPluginChangelog.svelte";
34
+ import RegistryPluginCard from "./PluginBrowserCard.svelte";
35
+ import RegistryVerifiedImage from "./community-presentation/RegistryVerifiedImage.svelte";
36
+ import { registryPluginIcon } from "./community-presentation/registry-plugin-icons.js";
37
+
38
+ let {
39
+ resolved,
40
+ related = [],
41
+ installAction,
42
+ selectedVersion,
43
+ currentVersion,
44
+ overview,
45
+ changelog,
46
+ downloadSummary,
47
+ onInstall,
48
+ onOpenPlugin,
49
+ onOpenCategory,
50
+ onOpenPlatform,
51
+ onSelectVersion,
52
+ }: {
53
+ resolved: RegistryResolvedDetail;
54
+ related?: readonly RegistryCatalogEntry[];
55
+ installAction?: RegistryInstallAction;
56
+ selectedVersion?: string;
57
+ currentVersion?: string;
58
+ overview?: Snippet;
59
+ changelog?: Snippet;
60
+ downloadSummary?: Snippet;
61
+ onInstall?: (request: RegistryInstallRequest) => void | Promise<void>;
62
+ onOpenPlugin: (plugin: RegistryCatalogEntry) => void;
63
+ onOpenCategory: (category: string) => void;
64
+ onOpenPlatform: (platform: string) => void;
65
+ onSelectVersion: (version: string) => void;
66
+ } = $props();
67
+
68
+ let activeTab = $state("overview");
69
+ let submitting = $state(false);
70
+ let installError = $state("");
71
+ let detailViewerOpen = $state(false);
72
+ let detailViewerItems = $state<readonly RegistryMediaItem[]>([]);
73
+ let detailViewerInitialMode = $state<"preview" | "full" | "light" | "dark">(
74
+ "full",
75
+ );
76
+ let detailViewerOpener = $state<HTMLElement | null>(null);
77
+ let historicalConfirmationOpen = $state(false);
78
+ const entry = $derived(resolved.entry);
79
+ const detail = $derived(resolved.detail);
80
+ const displayedVersion = $derived(selectedVersion ?? detail.latestVersion);
81
+ const trustedCurrentVersion = $derived(currentVersion ?? entry.latestVersion);
82
+ const release = $derived(detail.versions[displayedVersion]);
83
+ const historical = $derived(displayedVersion !== trustedCurrentVersion);
84
+ const hasExternalLinks = $derived(
85
+ Boolean(
86
+ detail.links?.documentation ||
87
+ detail.links?.repository ||
88
+ detail.links?.issues,
89
+ ),
90
+ );
91
+ const releases = $derived(
92
+ Object.values(detail.versions).toSorted((left, right) =>
93
+ left.releaseSequence !== undefined && right.releaseSequence !== undefined
94
+ ? compareDecimal(right.releaseSequence, left.releaseSequence)
95
+ : right.releasedAt.localeCompare(left.releasedAt) ||
96
+ right.version.localeCompare(left.version),
97
+ ),
98
+ );
99
+ const filePatterns = $derived(
100
+ detail.contributes?.editorViews?.flatMap((view) => view.filenamePatterns) ??
101
+ [],
102
+ );
103
+ const effectiveInstallAction = $derived.by<RegistryInstallAction>(() => {
104
+ if (installAction) return installAction;
105
+ if (onInstall === undefined)
106
+ return {
107
+ state: "disabled",
108
+ reason: "Installation is not available in this host",
109
+ };
110
+ if (detail.status !== "active")
111
+ return {
112
+ state: "disabled",
113
+ reason: `${detail.name} is ${registryStatusLabel(detail.status).toLocaleLowerCase()}`,
114
+ };
115
+ if (release === undefined || release.bundle.pending === true)
116
+ return {
117
+ state: "disabled",
118
+ reason: "The verified plugin bundle is not ready",
119
+ };
120
+ return { state: "available" };
121
+ });
122
+ const installDisabled = $derived(
123
+ submitting ||
124
+ effectiveInstallAction.state === "installing" ||
125
+ effectiveInstallAction.state === "installed" ||
126
+ effectiveInstallAction.state === "disabled",
127
+ );
128
+ const installLabel = $derived(
129
+ effectiveInstallAction.label ??
130
+ (submitting || effectiveInstallAction.state === "installing"
131
+ ? "Installing…"
132
+ : effectiveInstallAction.state === "installed"
133
+ ? "Installed"
134
+ : effectiveInstallAction.state === "disabled"
135
+ ? "Install unavailable"
136
+ : effectiveInstallAction.state === "rejected"
137
+ ? "Retry install"
138
+ : "Install in Lapis"),
139
+ );
140
+ const displayedInstallError = $derived(
141
+ installError ||
142
+ (effectiveInstallAction.state === "rejected"
143
+ ? (effectiveInstallAction.reason ?? "The plugin could not be installed")
144
+ : ""),
145
+ );
146
+ const author = $derived(detail.author || detail.owner.name);
147
+ const authorUrl = $derived(
148
+ detail.authorUrl ?? entry.authorUrl ?? detail.owner.url,
149
+ );
150
+ const authorAvatarUrl = $derived(
151
+ detail.authorAvatarUrl ?? entry.authorAvatarUrl,
152
+ );
153
+ const authorMember = $derived({
154
+ id: authorUrl ?? author,
155
+ name: author,
156
+ handle: author,
157
+ initials: initials(author),
158
+ ...(authorAvatarUrl === undefined ? {} : { avatarUrl: authorAvatarUrl }),
159
+ });
160
+ const bundleHealth = $derived(release?.bundle.pending === true ? 1 : 4);
161
+ const PluginIcon = $derived(
162
+ registryPluginIcon(detail.appearance?.icon ?? entry.categories[0]),
163
+ );
164
+
165
+ const dateFormatter = new Intl.DateTimeFormat("en", {
166
+ dateStyle: "medium",
167
+ timeZone: "UTC",
168
+ });
169
+
170
+ function formatDate(value: string | undefined): string {
171
+ return value ? dateFormatter.format(new Date(value)) : "Unknown";
172
+ }
173
+
174
+ function openExternal(url: string): void {
175
+ window.open(url, "_blank", "noopener,noreferrer");
176
+ }
177
+
178
+ function initials(value: string): string {
179
+ return value
180
+ .split(/\s+/u)
181
+ .filter(Boolean)
182
+ .slice(0, 2)
183
+ .map((part) => part[0])
184
+ .join("")
185
+ .toLocaleUpperCase();
186
+ }
187
+
188
+ async function install(): Promise<void> {
189
+ if (installDisabled || onInstall === undefined || release === undefined)
190
+ return;
191
+ if (historical) {
192
+ historicalConfirmationOpen = true;
193
+ return;
194
+ }
195
+ await performInstall();
196
+ }
197
+
198
+ async function performInstall(): Promise<void> {
199
+ if (onInstall === undefined || release === undefined) return;
200
+ submitting = true;
201
+ installError = "";
202
+ try {
203
+ await onInstall({
204
+ pluginId: detail.id,
205
+ version: release.version,
206
+ bundle: release.bundle,
207
+ });
208
+ } catch (cause) {
209
+ installError =
210
+ cause instanceof Error
211
+ ? cause.message
212
+ : "The plugin could not be installed";
213
+ } finally {
214
+ submitting = false;
215
+ }
216
+ }
217
+
218
+ function compareDecimal(left: string, right: string): number {
219
+ return left.length === right.length
220
+ ? left.localeCompare(right)
221
+ : left.length - right.length;
222
+ }
223
+
224
+ function selectVersion(version: string): void {
225
+ activeTab = "overview";
226
+ onSelectVersion(version);
227
+ }
228
+
229
+ function showMedia(
230
+ item: RegistryMediaItem,
231
+ initialMode: "preview" | "full" | "light" | "dark",
232
+ target: HTMLElement,
233
+ ): void {
234
+ detailViewerItems = [item];
235
+ detailViewerInitialMode = initialMode;
236
+ detailViewerOpener = target;
237
+ detailViewerOpen = true;
238
+ }
239
+ </script>
240
+
241
+ {#snippet pluginIcon()}
242
+ <PluginIcon aria-hidden="true" />
243
+ {/snippet}
244
+
245
+ {#snippet authorAvatar(compact = false)}
246
+ <CommunityMemberAvatar
247
+ member={authorMember}
248
+ size={compact ? "small" : "xsmall"}
249
+ decorative
250
+ />
251
+ {/snippet}
252
+
253
+ {#snippet authorLink(compact = false)}
254
+ {#if authorUrl}
255
+ <a
256
+ class="registry-plugin-detail__author-link"
257
+ class:compact
258
+ href={authorUrl}
259
+ target="_blank"
260
+ rel="noopener noreferrer"
261
+ >
262
+ {@render authorAvatar(compact)}
263
+ <span class="registry-plugin-detail__author-name"
264
+ >{compact ? author : `By ${author}`}</span
265
+ >
266
+ </a>
267
+ {:else}
268
+ <span class="registry-plugin-detail__author-link disabled" class:compact>
269
+ {@render authorAvatar(compact)}
270
+ <span class="registry-plugin-detail__author-name"
271
+ >{compact ? author : `By ${author}`}</span
272
+ >
273
+ </span>
274
+ {/if}
275
+ {/snippet}
276
+
277
+ {#snippet filterChip(type: "category" | "platform", value: string)}
278
+ {@const FilterIcon = registryFilterIcon(value)}
279
+ <CommunityChip
280
+ onclick={() => {
281
+ type === "category" ? onOpenCategory(value) : onOpenPlatform(value);
282
+ }}
283
+ >
284
+ <FilterIcon data-icon="inline-start" aria-hidden="true" />
285
+ <span>{titleCaseRegistryValue(value)}</span>
286
+ </CommunityChip>
287
+ {/snippet}
288
+
289
+ <article class="registry-plugin-detail">
290
+ <header
291
+ class="registry-plugin-detail__hero"
292
+ class:with-banner={Boolean(detail.media?.banner)}
293
+ >
294
+ <div class="registry-plugin-detail__hero-copy">
295
+ <div class="registry-plugin-detail__identity">
296
+ <span
297
+ class="registry-plugin-detail__icon"
298
+ style:--registry-plugin-accent={detail.appearance?.accent ??
299
+ "var(--primary)"}
300
+ >
301
+ {#if detail.appearance?.logo}
302
+ <RegistryVerifiedImage
303
+ reference={detail.appearance.logo}
304
+ alt=""
305
+ width={detail.appearance.logo.width}
306
+ height={detail.appearance.logo.height}
307
+ fallback={pluginIcon}
308
+ />
309
+ {:else}
310
+ <PluginIcon aria-hidden="true" />
311
+ {/if}
312
+ </span>
313
+ <div>
314
+ <h1>{detail.name}</h1>
315
+ <div class="registry-plugin-detail__hero-meta">
316
+ {@render authorLink()}
317
+ {#each entry.platforms as platform}
318
+ {@render filterChip("platform", platform)}
319
+ {/each}
320
+ </div>
321
+ </div>
322
+ </div>
323
+ <p class="registry-plugin-detail__description">{detail.description}</p>
324
+ <div class="registry-plugin-detail__actions">
325
+ <Button
326
+ type="button"
327
+ disabled={installDisabled}
328
+ aria-busy={submitting ||
329
+ effectiveInstallAction.state === "installing"}
330
+ title={effectiveInstallAction.state === "disabled"
331
+ ? effectiveInstallAction.reason
332
+ : undefined}
333
+ onclick={() => void install()}
334
+ >
335
+ {#if effectiveInstallAction.state === "installed"}<CheckCircleIcon
336
+ data-icon="inline-start"
337
+ aria-hidden="true"
338
+ />{:else}<DownloadIcon
339
+ data-icon="inline-start"
340
+ aria-hidden="true"
341
+ />{/if}
342
+ {installLabel}
343
+ </Button>
344
+ <DropdownMenu.Root>
345
+ <DropdownMenu.Trigger>
346
+ {#snippet child({ props })}
347
+ <Button
348
+ {...props}
349
+ type="button"
350
+ size="icon"
351
+ variant="secondary"
352
+ aria-label="More actions"
353
+ >
354
+ <EllipsisIcon aria-hidden="true" />
355
+ </Button>
356
+ {/snippet}
357
+ </DropdownMenu.Trigger>
358
+ <DropdownMenu.Content
359
+ align="start"
360
+ class="registry-plugin-detail__actions-menu"
361
+ >
362
+ {#if hasExternalLinks}
363
+ <DropdownMenu.Group>
364
+ {#if detail.links?.documentation}
365
+ <DropdownMenu.Item
366
+ onSelect={() => openExternal(detail.links!.documentation!)}
367
+ ><BookOpenIcon
368
+ data-icon="inline-start"
369
+ aria-hidden="true"
370
+ />Documentation</DropdownMenu.Item
371
+ >
372
+ {/if}
373
+ {#if detail.links?.repository}
374
+ <DropdownMenu.Item
375
+ onSelect={() => openExternal(detail.links!.repository!)}
376
+ ><ExternalLinkIcon
377
+ data-icon="inline-start"
378
+ aria-hidden="true"
379
+ />Repository</DropdownMenu.Item
380
+ >
381
+ {/if}
382
+ {#if detail.links?.issues}
383
+ <DropdownMenu.Item
384
+ onSelect={() => openExternal(detail.links!.issues!)}
385
+ ><BugIcon
386
+ data-icon="inline-start"
387
+ aria-hidden="true"
388
+ />Report bug</DropdownMenu.Item
389
+ >
390
+ {/if}
391
+ </DropdownMenu.Group>
392
+ {/if}
393
+ {#if release && release.bundle.pending !== true}
394
+ {#if hasExternalLinks}<DropdownMenu.Separator />{/if}
395
+ <DropdownMenu.Group>
396
+ <DropdownMenu.Item
397
+ onSelect={() =>
398
+ openExternal(
399
+ release.bundle.downloadUrl ?? release.bundle.url,
400
+ )}
401
+ ><DownloadIcon
402
+ data-icon="inline-start"
403
+ aria-hidden="true"
404
+ />Download .lapis-plugin</DropdownMenu.Item
405
+ >
406
+ </DropdownMenu.Group>
407
+ {/if}
408
+ </DropdownMenu.Content>
409
+ </DropdownMenu.Root>
410
+ </div>
411
+ {#if displayedInstallError}
412
+ <Alert.Root
413
+ variant="destructive"
414
+ class="registry-plugin-detail__install-error"
415
+ >
416
+ <CircleAlertIcon aria-hidden="true" />
417
+ <Alert.Title>Couldn’t install {detail.name}</Alert.Title>
418
+ <Alert.Description>{displayedInstallError}</Alert.Description>
419
+ </Alert.Root>
420
+ {/if}
421
+ </div>
422
+ {#if detail.media?.banner}
423
+ <button
424
+ type="button"
425
+ class="registry-plugin-detail__banner"
426
+ aria-label={`Open ${detail.name} banner: ${detail.media.banner.alt}`}
427
+ onclick={(event) =>
428
+ showMedia(detail.media!.banner!, "dark", event.currentTarget)}
429
+ >
430
+ <RegistryVerifiedImage
431
+ reference={detail.media.banner.images.dark}
432
+ alt=""
433
+ width={detail.media.banner.images.dark.width}
434
+ height={detail.media.banner.images.dark.height}
435
+ />
436
+ </button>
437
+ {/if}
438
+ </header>
439
+
440
+ {#if detail.gallery?.length}
441
+ <RegistryMediaGallery items={detail.gallery} pluginName={detail.name} />
442
+ {/if}
443
+
444
+ <div class="registry-plugin-detail__layout">
445
+ <section class="registry-plugin-detail__content">
446
+ <Tabs.Root bind:value={activeTab}>
447
+ <Tabs.List variant="line" aria-label="Plugin sections">
448
+ <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
449
+ <Tabs.Trigger value="changelog">Changelog</Tabs.Trigger>
450
+ <Tabs.Trigger value="releases"
451
+ >Releases
452
+ <CommunityChip
453
+ variant="muted"
454
+ size="compact"
455
+ class="registry-plugin-detail__tab-count"
456
+ >{releases.length}</CommunityChip
457
+ ></Tabs.Trigger
458
+ >
459
+ </Tabs.List>
460
+ <Tabs.Content value="overview">
461
+ <div class="registry-plugin-detail__prose">
462
+ {#if detail.media?.overview}
463
+ <button
464
+ type="button"
465
+ class="registry-plugin-detail__overview-media"
466
+ aria-label={`Open ${detail.name} overview image: ${detail.media.overview.alt}`}
467
+ onclick={(event) =>
468
+ showMedia(
469
+ detail.media!.overview!,
470
+ "preview",
471
+ event.currentTarget,
472
+ )}
473
+ >
474
+ <RegistryVerifiedImage
475
+ reference={detail.media.overview.images.preview}
476
+ alt=""
477
+ width={detail.media.overview.images.preview.width}
478
+ height={detail.media.overview.images.preview.height}
479
+ loading="lazy"
480
+ />
481
+ </button>
482
+ {/if}
483
+ {#if detail.highlights?.length}
484
+ <section>
485
+ <h2>Highlights</h2>
486
+ <ul>
487
+ {#each detail.highlights as highlight}<li>
488
+ {highlight}
489
+ </li>{/each}
490
+ </ul>
491
+ </section>
492
+ {/if}
493
+ <p>{detail.description}</p>
494
+ {#if overview}{@render overview()}{/if}
495
+ {#if filePatterns.length > 0}
496
+ <section>
497
+ <h2>File handlers</h2>
498
+ <div class="registry-plugin-detail__file-patterns">
499
+ {#each filePatterns as pattern}<code>{pattern}</code>{/each}
500
+ </div>
501
+ </section>
502
+ {/if}
503
+ </div>
504
+ </Tabs.Content>
505
+ <Tabs.Content value="changelog">
506
+ {#if changelog}{@render changelog()}{:else}<RegistryPluginChangelog
507
+ latestVersion={detail.latestVersion}
508
+ {releases}
509
+ />{/if}
510
+ </Tabs.Content>
511
+ <Tabs.Content value="releases">
512
+ <div class="registry-plugin-detail__releases">
513
+ {#each releases as item (item.version)}
514
+ <article>
515
+ <div>
516
+ <h3>
517
+ <a
518
+ href={`#/registry/plugins/${encodeURIComponent(detail.id)}/versions/${encodeURIComponent(item.version)}`}
519
+ onclick={(event) => {
520
+ event.preventDefault();
521
+ selectVersion(item.version);
522
+ }}>{item.version}</a
523
+ >
524
+ </h3>
525
+ <p>
526
+ Lapis {item.minAppVersion}+ · {item.platforms
527
+ .map(titleCaseRegistryValue)
528
+ .join(", ")} · {formatRegistryBytes(item.bundle.size)}
529
+ </p>
530
+ </div>
531
+ <CommunityChip
532
+ variant={item.bundle.pending ? "outline" : "muted"}
533
+ tone={item.bundle.pending ? "warning" : "success"}
534
+ >{item.bundle.pending
535
+ ? "Pending bundle"
536
+ : "Signed bundle"}</CommunityChip
537
+ >
538
+ </article>
539
+ {/each}
540
+ </div>
541
+ </Tabs.Content>
542
+ </Tabs.Root>
543
+ </section>
544
+
545
+ <aside class="registry-plugin-detail__sidebar">
546
+ <section
547
+ class="registry-plugin-detail__health"
548
+ aria-label="Plugin health"
549
+ >
550
+ <div data-complete="true">
551
+ <span><strong>Metadata</strong><strong>Signed</strong></span>
552
+ <div
553
+ class="registry-plugin-detail__health-bar"
554
+ data-ui-part="registry-plugin-health-bar"
555
+ role="progressbar"
556
+ aria-label="Metadata health"
557
+ aria-valuemin="0"
558
+ aria-valuemax="4"
559
+ aria-valuenow="4"
560
+ aria-valuetext="Signed, 4 of 4 checks ready"
561
+ >
562
+ {#each Array.from({ length: 4 }) as _}<i data-active="true"
563
+ ></i>{/each}
564
+ </div>
565
+ </div>
566
+ <div data-complete={bundleHealth === 4 ? "true" : "false"}>
567
+ <span
568
+ ><strong>Plugin bundle</strong><strong
569
+ >{bundleHealth === 4 ? "Ready" : "Pending"}</strong
570
+ ></span
571
+ >
572
+ <div
573
+ class="registry-plugin-detail__health-bar"
574
+ data-ui-part="registry-plugin-health-bar"
575
+ role="progressbar"
576
+ aria-label="Plugin bundle health"
577
+ aria-valuemin="0"
578
+ aria-valuemax="4"
579
+ aria-valuenow={bundleHealth}
580
+ aria-valuetext={`${bundleHealth === 4 ? "Ready" : "Pending"}, ${bundleHealth} of 4 checks ready`}
581
+ >
582
+ {#each Array.from({ length: 4 }) as _, index}
583
+ <i data-active={index < bundleHealth ? "true" : "false"}></i>
584
+ {/each}
585
+ </div>
586
+ </div>
587
+ </section>
588
+ <section>
589
+ <h2>About</h2>
590
+ <p>{detail.description}</p>
591
+ <div class="registry-plugin-detail__chips">
592
+ {#each entry.categories as category}
593
+ {@render filterChip("category", category)}
594
+ {/each}
595
+ </div>
596
+ </section>
597
+ <section>
598
+ <h2>Details</h2>
599
+ <dl>
600
+ <div>
601
+ <dt>Current version</dt>
602
+ <dd>
603
+ {#if releases.length > 1}
604
+ <Select.Root
605
+ type="single"
606
+ value={displayedVersion}
607
+ onValueChange={(value) => {
608
+ if (value) selectVersion(value);
609
+ }}
610
+ >
611
+ <Select.Trigger
612
+ size="sm"
613
+ class="registry-plugin-detail__version-trigger"
614
+ aria-label="Select plugin version"
615
+ role="combobox"
616
+ >
617
+ {displayedVersion}
618
+ <span>{historical ? "Historical" : "Latest"}</span>
619
+ </Select.Trigger>
620
+ <Select.Content aria-label="Plugin versions">
621
+ <Select.Group>
622
+ {#each releases as item (item.version)}
623
+ <Select.Item value={item.version} label={item.version}>
624
+ {item.version} · {item.version ===
625
+ trustedCurrentVersion
626
+ ? "Latest"
627
+ : "Historical"}
628
+ </Select.Item>
629
+ {/each}
630
+ </Select.Group>
631
+ </Select.Content>
632
+ </Select.Root>
633
+ {:else}
634
+ {detail.latestVersion}
635
+ {/if}
636
+ </dd>
637
+ </div>
638
+ <div>
639
+ <dt>Last updated</dt>
640
+ <dd>{formatDate(release?.releasedAt)}</dd>
641
+ </div>
642
+ <div>
643
+ <dt>Releases</dt>
644
+ <dd>{releases.length}</dd>
645
+ </div>
646
+ <div>
647
+ <dt>Status</dt>
648
+ <dd>{registryStatusLabel(detail.status)}</dd>
649
+ </div>
650
+ <div>
651
+ <dt>Compatible with</dt>
652
+ <dd>Lapis {release?.minAppVersion ?? entry.minAppVersion}+</dd>
653
+ </div>
654
+ <div>
655
+ <dt>Platforms</dt>
656
+ <dd>{entry.platforms.map(titleCaseRegistryValue).join(", ")}</dd>
657
+ </div>
658
+ <div>
659
+ <dt>License</dt>
660
+ <dd>{detail.license ?? "Not specified"}</dd>
661
+ </div>
662
+ <div>
663
+ <dt>Trust</dt>
664
+ <dd>{detail.owner.verified ? "Verified" : "Community"}</dd>
665
+ </div>
666
+ <div>
667
+ <dt>Bundle size</dt>
668
+ <dd>
669
+ {release ? formatRegistryBytes(release.bundle.size) : "Unknown"}
670
+ </dd>
671
+ </div>
672
+ </dl>
673
+ </section>
674
+ <section>
675
+ <h2>Author</h2>
676
+ <div class="registry-plugin-detail__author">
677
+ {@render authorLink(true)}
678
+ </div>
679
+ </section>
680
+ {#if downloadSummary}<section>
681
+ <h2>Downloads</h2>
682
+ {@render downloadSummary()}
683
+ </section>{/if}
684
+ </aside>
685
+ </div>
686
+
687
+ {#if related.length > 0}
688
+ <section class="registry-plugin-detail__related">
689
+ <h2>Related plugins</h2>
690
+ <div>
691
+ {#each related as plugin (plugin.id)}<RegistryPluginCard
692
+ {plugin}
693
+ variant="compact"
694
+ onOpen={onOpenPlugin}
695
+ />{/each}
696
+ </div>
697
+ </section>
698
+ {/if}
699
+
700
+ <ConfirmDialog
701
+ bind:open={historicalConfirmationOpen}
702
+ pending={submitting}
703
+ error={installError}
704
+ onConfirm={async () => {
705
+ await performInstall();
706
+ return !installError;
707
+ }}
708
+ >
709
+ {#snippet title()}Install historical release?{/snippet}
710
+ {#snippet description()}
711
+ {#if effectiveInstallAction.installedVersion}
712
+ This replaces installed version {effectiveInstallAction.installedVersion}
713
+ with historical version {displayedVersion}. The current trusted release
714
+ is {trustedCurrentVersion}.
715
+ {:else}
716
+ You are installing historical version {displayedVersion}. The current
717
+ trusted release is {trustedCurrentVersion}.
718
+ {/if}
719
+ {/snippet}
720
+ {#snippet confirmLabel()}Install {displayedVersion}{/snippet}
721
+ </ConfirmDialog>
722
+
723
+ <RegistryMediaViewer
724
+ items={detailViewerItems}
725
+ pluginName={detail.name}
726
+ opener={detailViewerOpener}
727
+ initialMode={detailViewerInitialMode}
728
+ bind:open={detailViewerOpen}
729
+ />
730
+ </article>
731
+
732
+ <style>
733
+ .registry-plugin-detail {
734
+ display: grid;
735
+ min-width: 0;
736
+ gap: clamp(1.5rem, 4cqi, 3rem);
737
+ width: min(100%, 70rem);
738
+ margin-inline: auto;
739
+ padding: clamp(1rem, 4cqi, 3rem);
740
+ overflow-wrap: anywhere;
741
+ }
742
+ .registry-plugin-detail__hero {
743
+ display: grid;
744
+ gap: 2rem;
745
+ align-items: center;
746
+ }
747
+
748
+ .registry-plugin-detail__hero.with-banner {
749
+ grid-template-columns: minmax(0, 1fr) minmax(22.5rem, 32.5rem);
750
+ gap: 3rem;
751
+ }
752
+ .registry-plugin-detail__hero-copy {
753
+ display: grid;
754
+ min-width: 0;
755
+ gap: 1.125rem;
756
+ }
757
+ .registry-plugin-detail__identity {
758
+ display: grid;
759
+ grid-template-columns: 5rem minmax(0, 1fr);
760
+ align-items: center;
761
+ gap: 1.5rem;
762
+ }
763
+ .registry-plugin-detail__identity > div {
764
+ min-width: 0;
765
+ }
766
+ .registry-plugin-detail__identity h1 {
767
+ margin: 0;
768
+ font-size: var(--community-text-page);
769
+ font-weight: 600;
770
+ line-height: 1.1;
771
+ letter-spacing: -0.035em;
772
+ }
773
+ .registry-plugin-detail__icon {
774
+ display: grid;
775
+ width: 5rem;
776
+ height: 5rem;
777
+ place-items: center;
778
+ overflow: hidden;
779
+ border: 1px solid
780
+ color-mix(in srgb, var(--registry-plugin-accent) 38%, var(--border));
781
+ border-radius: calc(var(--radius) * 2);
782
+ color: var(--registry-plugin-accent);
783
+ background: color-mix(
784
+ in srgb,
785
+ var(--registry-plugin-accent) 14%,
786
+ var(--muted)
787
+ );
788
+ }
789
+ .registry-plugin-detail__icon :global(svg) {
790
+ width: 2.25rem;
791
+ height: 2.25rem;
792
+ }
793
+ .registry-plugin-detail__icon :global(img) {
794
+ width: 76%;
795
+ height: 76%;
796
+ object-fit: contain;
797
+ }
798
+ .registry-plugin-detail__hero-meta,
799
+ .registry-plugin-detail__actions,
800
+ .registry-plugin-detail__chips {
801
+ margin-top: 1rem;
802
+ display: flex;
803
+ flex-wrap: wrap;
804
+ align-items: center;
805
+ gap: 0.5rem;
806
+ }
807
+ .registry-plugin-detail__hero-meta {
808
+ margin-top: 0.35rem;
809
+ color: var(--muted-foreground);
810
+ }
811
+ .registry-plugin-detail__author-link {
812
+ display: inline-flex;
813
+ align-items: center;
814
+ border: 1px solid var(--border);
815
+ color: var(--foreground);
816
+ text-decoration: none;
817
+ transition:
818
+ background-color 140ms ease,
819
+ border-color 140ms ease,
820
+ color 140ms ease,
821
+ box-shadow 140ms ease;
822
+ }
823
+ .registry-plugin-detail__author-link {
824
+ gap: 0.35rem;
825
+ border-color: transparent;
826
+ border-radius: 999px;
827
+ padding: 0.125rem 0.35rem 0.125rem 0.125rem;
828
+ color: var(--muted-foreground);
829
+ }
830
+ .registry-plugin-detail__author-link.compact {
831
+ gap: 0.55rem;
832
+ padding: 0.25rem 0.5rem 0.25rem 0.25rem;
833
+ color: var(--foreground);
834
+ font-weight: 600;
835
+ }
836
+ .registry-plugin-detail__author-link:not(.disabled):hover,
837
+ .registry-plugin-detail__author-link:not(.disabled):focus-visible {
838
+ color: var(--muted-foreground);
839
+ background: transparent;
840
+ }
841
+ .registry-plugin-detail__author-link:not(.disabled):hover
842
+ .registry-plugin-detail__author-name,
843
+ .registry-plugin-detail__author-link:not(.disabled):focus-visible
844
+ .registry-plugin-detail__author-name {
845
+ color: var(--foreground);
846
+ }
847
+ .registry-plugin-detail__author-link:not(.disabled):focus-visible {
848
+ outline: 2px solid var(--ring);
849
+ outline-offset: 2px;
850
+ }
851
+ .registry-plugin-detail__description {
852
+ max-width: 48rem;
853
+ margin: 0;
854
+ font-size: var(--community-text-section);
855
+ line-height: 1.4;
856
+ }
857
+ :global(.registry-plugin-detail__install-error[data-ui-component="alert"]) {
858
+ width: min(100%, 38rem);
859
+ border-color: color-mix(in srgb, var(--destructive) 30%, var(--border));
860
+ border-radius: calc(var(--radius) + 0.25rem);
861
+ color: color-mix(in srgb, var(--destructive) 78%, var(--foreground));
862
+ background: color-mix(in srgb, var(--destructive) 6%, var(--card));
863
+ box-shadow: 0 0.25rem 1rem
864
+ color-mix(in srgb, var(--destructive) 8%, transparent);
865
+ }
866
+
867
+ :global(
868
+ .registry-plugin-detail__install-error[data-ui-component="alert"] > svg
869
+ ) {
870
+ margin-block-start: 0.125rem;
871
+ }
872
+
873
+ :global(
874
+ .registry-plugin-detail__install-error [data-ui-part="alert-description"]
875
+ ) {
876
+ color: inherit;
877
+ opacity: 1;
878
+ }
879
+
880
+ :global(
881
+ .registry-plugin-detail__actions-menu[data-ui-part="dropdown-menu-content"]
882
+ ) {
883
+ min-width: 13rem;
884
+ }
885
+
886
+ :global(
887
+ .registry-plugin-detail__actions-menu [data-ui-part="dropdown-menu-item"]
888
+ ) {
889
+ white-space: nowrap;
890
+ }
891
+
892
+ .registry-plugin-detail__banner {
893
+ display: block;
894
+ width: 100%;
895
+ overflow: hidden;
896
+ border: 0;
897
+ border-radius: 1rem;
898
+ padding: 0;
899
+ background: #09090b;
900
+ cursor: zoom-in;
901
+ }
902
+ .registry-plugin-detail__banner :global(img) {
903
+ display: block;
904
+ width: 100%;
905
+ aspect-ratio: 3 / 2;
906
+ object-fit: cover;
907
+ }
908
+ .registry-plugin-detail__layout {
909
+ display: grid;
910
+ grid-template-columns: minmax(0, 1fr) 18rem;
911
+ gap: clamp(1.5rem, 4vw, 3rem);
912
+ align-items: start;
913
+ }
914
+ .registry-plugin-detail__content {
915
+ min-width: 0;
916
+ }
917
+ .registry-plugin-detail__content :global([data-ui-part="tabs"]) {
918
+ display: grid;
919
+ }
920
+ .registry-plugin-detail__content :global([data-ui-part="tabs-list"]) {
921
+ justify-content: flex-start;
922
+ width: 100%;
923
+ border-bottom: 1px solid var(--border);
924
+ border-radius: 0;
925
+ padding: 0;
926
+ }
927
+ .registry-plugin-detail__content :global([data-ui-part="tabs-trigger"]) {
928
+ flex: 0 0 auto;
929
+ align-self: stretch;
930
+ height: auto;
931
+ border-radius: 0;
932
+ padding: 0.65rem 0.85rem;
933
+ }
934
+ .registry-plugin-detail__content
935
+ :global([data-ui-part="tabs-trigger"]::after) {
936
+ /* This unpadded rail has one-pixel trigger and list borders. */
937
+ bottom: -2px;
938
+ }
939
+ .registry-plugin-detail__content
940
+ :global([data-ui-part="tabs-trigger"][data-state="active"]) {
941
+ color: var(--primary);
942
+ }
943
+ .registry-plugin-detail__content
944
+ :global([data-ui-part="tabs-trigger"][data-state="active"]::after) {
945
+ background: var(--primary);
946
+ }
947
+ .registry-plugin-detail__content :global([data-ui-part="tabs-content"]) {
948
+ min-width: 0;
949
+ }
950
+ :global(.registry-plugin-detail__tab-count[data-ui-part="community-chip"]) {
951
+ min-width: 1.25rem;
952
+ justify-content: center;
953
+ font-variant-numeric: tabular-nums;
954
+ }
955
+ .registry-plugin-detail__prose {
956
+ display: grid;
957
+ gap: 1.25rem;
958
+ padding-block: 1.25rem;
959
+ line-height: 1.65;
960
+ }
961
+ .registry-plugin-detail__overview-media {
962
+ display: block;
963
+ width: 100%;
964
+ overflow: hidden;
965
+ border: 0;
966
+ border-radius: 0.65rem;
967
+ padding: 0;
968
+ background: #09090b;
969
+ cursor: zoom-in;
970
+ }
971
+ .registry-plugin-detail__overview-media :global(img) {
972
+ display: block;
973
+ width: 100%;
974
+ aspect-ratio: 3 / 2;
975
+ object-fit: cover;
976
+ }
977
+ .registry-plugin-detail__banner:focus-visible,
978
+ .registry-plugin-detail__overview-media:focus-visible {
979
+ outline: 2px solid var(--ring);
980
+ outline-offset: 2px;
981
+ }
982
+ .registry-plugin-detail__prose h2,
983
+ .registry-plugin-detail__related h2,
984
+ .registry-plugin-detail__sidebar h2 {
985
+ margin: 0 0 0.65rem;
986
+ font-size: var(--community-text-section);
987
+ font-weight: 700;
988
+ }
989
+ .registry-plugin-detail__file-patterns {
990
+ display: flex;
991
+ flex-wrap: wrap;
992
+ gap: 0.4rem;
993
+ }
994
+ .registry-plugin-detail__file-patterns code {
995
+ border-radius: var(--radius);
996
+ padding: 0.25rem 0.45rem;
997
+ background: var(--muted);
998
+ }
999
+ .registry-plugin-detail__releases {
1000
+ display: grid;
1001
+ padding-block: 1rem;
1002
+ }
1003
+ .registry-plugin-detail__releases article {
1004
+ display: flex;
1005
+ flex-wrap: wrap;
1006
+ align-items: center;
1007
+ justify-content: space-between;
1008
+ gap: 1rem;
1009
+ border-bottom: 1px solid var(--border);
1010
+ padding: 1rem 0;
1011
+ }
1012
+ .registry-plugin-detail__releases h3,
1013
+ .registry-plugin-detail__releases p {
1014
+ margin: 0;
1015
+ }
1016
+ .registry-plugin-detail__releases a {
1017
+ color: inherit;
1018
+ text-decoration: none;
1019
+ }
1020
+ .registry-plugin-detail__releases a:hover,
1021
+ .registry-plugin-detail__releases a:focus-visible {
1022
+ color: var(--primary);
1023
+ text-decoration: underline;
1024
+ }
1025
+ :global(.registry-plugin-detail__version-trigger) {
1026
+ min-width: 10rem;
1027
+ max-width: 100%;
1028
+ justify-content: space-between;
1029
+ }
1030
+ :global(.registry-plugin-detail__version-trigger > span) {
1031
+ color: var(--muted-foreground);
1032
+ font-size: var(--community-text-secondary);
1033
+ }
1034
+ .registry-plugin-detail__releases p {
1035
+ color: var(--muted-foreground);
1036
+ font-size: var(--community-text-body);
1037
+ }
1038
+ .registry-plugin-detail__sidebar {
1039
+ display: grid;
1040
+ min-width: 0;
1041
+ gap: 1.25rem;
1042
+ }
1043
+ .registry-plugin-detail__sidebar > section {
1044
+ border-top: 1px solid var(--border);
1045
+ padding-top: 1rem;
1046
+ }
1047
+ .registry-plugin-detail__sidebar p {
1048
+ margin: 0;
1049
+ color: var(--muted-foreground);
1050
+ font-size: var(--community-text-body);
1051
+ line-height: 1.45;
1052
+ }
1053
+ .registry-plugin-detail__health {
1054
+ display: grid;
1055
+ gap: 0.85rem;
1056
+ }
1057
+ .registry-plugin-detail__health div {
1058
+ display: grid;
1059
+ gap: 0.35rem;
1060
+ }
1061
+ .registry-plugin-detail__health span {
1062
+ display: flex;
1063
+ justify-content: space-between;
1064
+ gap: 1rem;
1065
+ font-size: var(--community-text-secondary);
1066
+ }
1067
+ .registry-plugin-detail__health
1068
+ > div[data-complete="true"]
1069
+ span
1070
+ strong:last-child {
1071
+ color: color-mix(in srgb, #16a34a 78%, var(--foreground));
1072
+ }
1073
+ .registry-plugin-detail__health-bar {
1074
+ display: grid;
1075
+ grid-template-columns: repeat(4, minmax(0, 1fr));
1076
+ gap: 0.25rem;
1077
+ height: 0.375rem;
1078
+ }
1079
+ .registry-plugin-detail__health-bar i {
1080
+ display: block;
1081
+ border-radius: 999px;
1082
+ background: var(--muted);
1083
+ }
1084
+ .registry-plugin-detail__health-bar i[data-active="true"] {
1085
+ background: var(--primary);
1086
+ }
1087
+ .registry-plugin-detail__health
1088
+ > div[data-complete="true"]
1089
+ .registry-plugin-detail__health-bar
1090
+ i[data-active="true"] {
1091
+ background: color-mix(in srgb, #16a34a 78%, var(--primary));
1092
+ }
1093
+ dl {
1094
+ display: grid;
1095
+ gap: 0.55rem;
1096
+ margin: 0;
1097
+ }
1098
+ dl div {
1099
+ display: grid;
1100
+ grid-template-columns: minmax(0, 1fr) auto;
1101
+ gap: 0.75rem;
1102
+ font-size: var(--community-text-secondary);
1103
+ }
1104
+ dt {
1105
+ color: var(--muted-foreground);
1106
+ }
1107
+ dd {
1108
+ min-width: 0;
1109
+ margin: 0;
1110
+ text-align: end;
1111
+ }
1112
+ .registry-plugin-detail__author {
1113
+ display: flex;
1114
+ align-items: center;
1115
+ gap: 0.6rem;
1116
+ }
1117
+ .registry-plugin-detail__related {
1118
+ display: grid;
1119
+ gap: 0.75rem;
1120
+ }
1121
+ .registry-plugin-detail__related > div {
1122
+ display: grid;
1123
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1124
+ gap: 1.5rem;
1125
+ }
1126
+ @container plugin-browser (max-width: 58rem) {
1127
+ .registry-plugin-detail__hero.with-banner,
1128
+ .registry-plugin-detail__layout {
1129
+ grid-template-columns: minmax(0, 1fr);
1130
+ }
1131
+ .registry-plugin-detail__sidebar {
1132
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1133
+ }
1134
+ }
1135
+ @container plugin-browser (max-width: 38rem) {
1136
+ .registry-plugin-detail__identity {
1137
+ grid-template-columns: 4rem minmax(0, 1fr);
1138
+ align-items: start;
1139
+ }
1140
+ .registry-plugin-detail__icon {
1141
+ width: 4rem;
1142
+ height: 4rem;
1143
+ }
1144
+ .registry-plugin-detail__sidebar,
1145
+ .registry-plugin-detail__related > div {
1146
+ grid-template-columns: 1fr;
1147
+ }
1148
+ }
1149
+ @container plugin-browser (max-width: 26rem) {
1150
+ .registry-plugin-detail__content :global([data-ui-part="tabs-list"]) {
1151
+ height: auto;
1152
+ min-height: 2.25rem;
1153
+ flex-wrap: wrap;
1154
+ }
1155
+ .registry-plugin-detail__content :global([data-ui-part="tabs-trigger"]) {
1156
+ padding-inline: 0.25rem;
1157
+ }
1158
+ dl div {
1159
+ display: flex;
1160
+ flex-wrap: wrap;
1161
+ align-items: center;
1162
+ }
1163
+ dd {
1164
+ max-width: 100%;
1165
+ margin-inline-start: auto;
1166
+ }
1167
+ }
1168
+ </style>