@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,223 @@
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import type { PluginCatalogDetail, RegistryMediaV2 } from "@lapis-notes/api";
4
+ import { Button } from "@lapismd/design-core/shadcn/button";
5
+ import ChevronLeft from "@lucide/svelte/icons/chevron-left";
6
+ import ChevronRight from "@lucide/svelte/icons/chevron-right";
7
+ import PluginRegistryImage from "./PluginRegistryImage.svelte";
8
+ import PluginRegistryMediaViewer from "./PluginRegistryMediaViewer.svelte";
9
+
10
+ let {
11
+ detail,
12
+ placement = "overview",
13
+ }: {
14
+ detail: PluginCatalogDetail;
15
+ placement?: "gallery" | "overview" | "banner";
16
+ } = $props();
17
+ const items = $derived(
18
+ placement === "gallery"
19
+ ? (detail.gallery ?? [])
20
+ : [detail.media?.[placement]].filter((item): item is RegistryMediaV2 =>
21
+ Boolean(item),
22
+ ),
23
+ );
24
+ let track = $state<HTMLDivElement>();
25
+ let opened = $state(false);
26
+ let selectedIndex = $state(0);
27
+ let inlineIndex = $state(0);
28
+ let atStart = $state(true);
29
+ let atEnd = $state(false);
30
+ let opener = $state<HTMLElement | null>(null);
31
+
32
+ onMount(() => {
33
+ if (!track) return;
34
+ const observer = new ResizeObserver(updateTrackState);
35
+ observer.observe(track);
36
+ updateTrackState();
37
+ return () => observer.disconnect();
38
+ });
39
+
40
+ function updateTrackState() {
41
+ if (!track) return;
42
+ atStart = track.scrollLeft <= 1;
43
+ atEnd = track.scrollLeft >= track.scrollWidth - track.clientWidth - 1;
44
+ const origin = track.getBoundingClientRect().left;
45
+ const children = Array.from(track.children) as HTMLElement[];
46
+ inlineIndex = children.reduce(
47
+ (closest, child, index) =>
48
+ Math.abs(child.getBoundingClientRect().left - origin) <
49
+ Math.abs(children[closest]!.getBoundingClientRect().left - origin)
50
+ ? index
51
+ : closest,
52
+ 0,
53
+ );
54
+ }
55
+
56
+ function moveTrack(offset: number) {
57
+ if (!track) return;
58
+ const next = Math.min(Math.max(inlineIndex + offset, 0), items.length - 1);
59
+ const target = track.children[next] as HTMLElement | undefined;
60
+ if (!target) return;
61
+ track.scrollTo({
62
+ left:
63
+ track.scrollLeft +
64
+ target.getBoundingClientRect().left -
65
+ track.getBoundingClientRect().left,
66
+ behavior: matchMedia("(prefers-reduced-motion: reduce)").matches
67
+ ? "auto"
68
+ : "smooth",
69
+ });
70
+ }
71
+ </script>
72
+
73
+ {#if items.length}
74
+ <section
75
+ class="registry-media"
76
+ data-media-placement={placement}
77
+ aria-label={placement === "gallery"
78
+ ? `${detail.name} screenshots`
79
+ : `${detail.name} ${placement} image`}
80
+ >
81
+ <div class="registry-media__frame">
82
+ <div
83
+ bind:this={track}
84
+ class="registry-media__track"
85
+ class:gallery={placement === "gallery"}
86
+ role="group"
87
+ aria-label={placement === "gallery"
88
+ ? `${detail.name} screenshot carousel`
89
+ : `${detail.name} ${placement}`}
90
+ onscroll={updateTrackState}
91
+ >
92
+ {#each items as item, index (item)}
93
+ <button
94
+ type="button"
95
+ class="registry-media__item"
96
+ data-state={placement !== "gallery" ||
97
+ (index >= inlineIndex && index < inlineIndex + 2)
98
+ ? "active"
99
+ : "preview"}
100
+ aria-label={`Open image: ${item.alt}`}
101
+ onclick={(event) => {
102
+ selectedIndex = index;
103
+ opener = event.currentTarget;
104
+ opened = true;
105
+ }}
106
+ >
107
+ <PluginRegistryImage
108
+ media={item}
109
+ variant={placement === "overview" ? "preview" : "dark"}
110
+ />
111
+ </button>
112
+ {/each}
113
+ </div>
114
+ {#if placement === "gallery" && items.length > 1}
115
+ <Button
116
+ class="registry-media__previous"
117
+ variant="secondary"
118
+ size="icon"
119
+ aria-label="Previous screenshots"
120
+ disabled={atStart}
121
+ onclick={() => moveTrack(-1)}><ChevronLeft /></Button
122
+ >
123
+ <Button
124
+ class="registry-media__next"
125
+ variant="secondary"
126
+ size="icon"
127
+ aria-label="Next screenshots"
128
+ disabled={atEnd}
129
+ onclick={() => moveTrack(1)}><ChevronRight /></Button
130
+ >
131
+ {/if}
132
+ </div>
133
+ </section>
134
+ <PluginRegistryMediaViewer
135
+ {items}
136
+ pluginName={detail.name}
137
+ {opener}
138
+ initialMode={placement === "gallery" || placement === "banner"
139
+ ? "dark"
140
+ : "full"}
141
+ bind:open={opened}
142
+ bind:selectedIndex
143
+ />
144
+ {/if}
145
+
146
+ <style>
147
+ .registry-media,
148
+ .registry-media__frame {
149
+ min-width: 0;
150
+ }
151
+ .registry-media__frame {
152
+ position: relative;
153
+ }
154
+ .registry-media__track {
155
+ min-width: 0;
156
+ }
157
+ .registry-media__track.gallery {
158
+ display: flex;
159
+ gap: 0.5rem;
160
+ overflow-x: auto;
161
+ scrollbar-width: none;
162
+ scroll-snap-type: x mandatory;
163
+ overscroll-behavior-x: contain;
164
+ }
165
+ .registry-media__track::-webkit-scrollbar {
166
+ display: none;
167
+ }
168
+ .registry-media__item {
169
+ display: block;
170
+ width: 100%;
171
+ min-width: 0;
172
+ overflow: hidden;
173
+ border: 0;
174
+ border-radius: var(--radius);
175
+ padding: 0;
176
+ color: var(--foreground);
177
+ background: var(--muted);
178
+ cursor: zoom-in;
179
+ }
180
+ .gallery > .registry-media__item {
181
+ flex: 0 0 calc(44% - 0.25rem);
182
+ aspect-ratio: 3 / 2;
183
+ scroll-snap-align: start;
184
+ scroll-snap-stop: always;
185
+ }
186
+ .gallery :global(img) {
187
+ height: 100%;
188
+ object-fit: cover;
189
+ }
190
+ .registry-media__item[data-state="preview"] {
191
+ opacity: 0.35;
192
+ }
193
+ .registry-media__item:hover,
194
+ .registry-media__item:focus-visible {
195
+ opacity: 1;
196
+ }
197
+ .registry-media__item:focus-visible {
198
+ outline: 2px solid var(--ring);
199
+ outline-offset: -2px;
200
+ }
201
+ .registry-media__frame :global(.registry-media__previous),
202
+ .registry-media__frame :global(.registry-media__next) {
203
+ position: absolute;
204
+ top: 50%;
205
+ transform: translateY(-50%);
206
+ --ui-button-radius: 999px;
207
+ }
208
+ .registry-media__frame :global(.registry-media__previous) {
209
+ left: 0.5rem;
210
+ }
211
+ .registry-media__frame :global(.registry-media__next) {
212
+ right: 0.5rem;
213
+ }
214
+ .registry-media__frame :global(button:disabled) {
215
+ opacity: 0;
216
+ pointer-events: none;
217
+ }
218
+ @media (max-width: 45rem) {
219
+ .gallery > .registry-media__item {
220
+ flex-basis: 100%;
221
+ }
222
+ }
223
+ </style>
@@ -0,0 +1,8 @@
1
+ import type { PluginCatalogDetail } from "@lapis-notes/api";
2
+ type $$ComponentProps = {
3
+ detail: PluginCatalogDetail;
4
+ placement?: "gallery" | "overview" | "banner";
5
+ };
6
+ declare const PluginRegistryMedia: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type PluginRegistryMedia = ReturnType<typeof PluginRegistryMedia>;
8
+ export default PluginRegistryMedia;
@@ -0,0 +1,155 @@
1
+ <script lang="ts">
2
+ import { tick } from "svelte";
3
+ import type { RegistryMediaV2 } from "@lapis-notes/api";
4
+ import { Button } from "@lapismd/design-core/shadcn/button";
5
+ import * as Dialog from "@lapismd/design-core/shadcn/dialog";
6
+ import * as ToggleGroup from "@lapismd/design-core/shadcn/toggle-group";
7
+ import ChevronLeft from "@lucide/svelte/icons/chevron-left";
8
+ import ChevronRight from "@lucide/svelte/icons/chevron-right";
9
+ import X from "@lucide/svelte/icons/x";
10
+ import PluginRegistryImage from "./PluginRegistryImage.svelte";
11
+
12
+ type Mode = "full" | "light" | "dark";
13
+ let {
14
+ items,
15
+ pluginName,
16
+ open = $bindable(false),
17
+ selectedIndex = $bindable(0),
18
+ initialMode = "full",
19
+ opener = null,
20
+ }: {
21
+ items: readonly RegistryMediaV2[];
22
+ pluginName: string;
23
+ open?: boolean;
24
+ selectedIndex?: number;
25
+ initialMode?: Mode;
26
+ opener?: HTMLElement | null;
27
+ } = $props();
28
+ let mode = $state<Mode>("full");
29
+ let imageFrame = $state<HTMLDivElement>();
30
+ let wasOpen = false;
31
+ const lastIndex = $derived(Math.max(0, items.length - 1));
32
+ $effect(() => {
33
+ if (open && !wasOpen) mode = initialMode;
34
+ if (!open && wasOpen) {
35
+ const target = opener;
36
+ void tick().then(() => target?.isConnected && target.focus());
37
+ }
38
+ wasOpen = open;
39
+ });
40
+ function move(index: number) {
41
+ selectedIndex = Math.min(Math.max(index, 0), lastIndex);
42
+ imageFrame?.focus();
43
+ }
44
+ function onkeydown(event: KeyboardEvent) {
45
+ if (
46
+ (event.target as HTMLElement).closest(
47
+ '[data-ui-component="toggle-group"]',
48
+ )
49
+ )
50
+ return;
51
+ const next = {
52
+ ArrowLeft: selectedIndex - 1,
53
+ ArrowRight: selectedIndex + 1,
54
+ Home: 0,
55
+ End: lastIndex,
56
+ }[event.key];
57
+ if (next !== undefined) {
58
+ event.preventDefault();
59
+ move(next);
60
+ }
61
+ }
62
+ </script>
63
+
64
+ <Dialog.Root bind:open>
65
+ {#if open}
66
+ <Dialog.Content
67
+ class="registry-media-viewer"
68
+ showCloseButton={false}
69
+ {onkeydown}
70
+ style="--ui-dialog-width: min(72rem, calc(100vw - 2rem)); --ui-dialog-max-width: 72rem; width: min(72rem, calc(100vw - 2rem)); max-width: 72rem; max-height: calc(100dvh - 2rem);"
71
+ >
72
+ <Dialog.Title class="sr-only">{pluginName} media viewer</Dialog.Title>
73
+ <div class="registry-media-viewer__toolbar">
74
+ <ToggleGroup.Root
75
+ type="single"
76
+ variant="outline"
77
+ size="sm"
78
+ value={mode}
79
+ onValueChange={(value) => {
80
+ if (value) mode = value as Mode;
81
+ }}
82
+ aria-label="Image theme"
83
+ >
84
+ <ToggleGroup.Item value="light">Light</ToggleGroup.Item>
85
+ <ToggleGroup.Item value="dark">Dark</ToggleGroup.Item>
86
+ <ToggleGroup.Item value="full">Split</ToggleGroup.Item>
87
+ </ToggleGroup.Root>
88
+ <Button
89
+ variant="ghost"
90
+ size="icon"
91
+ aria-label="Close media viewer"
92
+ onclick={() => (open = false)}><X /></Button
93
+ >
94
+ </div>
95
+ <div
96
+ class="registry-media-viewer__image"
97
+ bind:this={imageFrame}
98
+ role="listbox"
99
+ aria-label={`${pluginName} media`}
100
+ tabindex="0"
101
+ >
102
+ {#if items[selectedIndex]}
103
+ <div role="option" aria-selected="true">
104
+ <PluginRegistryImage
105
+ media={items[selectedIndex]!}
106
+ variant={mode}
107
+ presentation="viewer"
108
+ />
109
+ </div>
110
+ {/if}
111
+ </div>
112
+ <div class="registry-media-viewer__footer">
113
+ <Button
114
+ variant="ghost"
115
+ size="icon"
116
+ aria-label="Previous image"
117
+ disabled={selectedIndex === 0}
118
+ onclick={() => move(selectedIndex - 1)}><ChevronLeft /></Button
119
+ >
120
+ <p aria-live="polite">
121
+ {selectedIndex + 1} of {items.length}: {items[selectedIndex]?.alt}
122
+ </p>
123
+ <Button
124
+ variant="ghost"
125
+ size="icon"
126
+ aria-label="Next image"
127
+ disabled={selectedIndex === lastIndex}
128
+ onclick={() => move(selectedIndex + 1)}><ChevronRight /></Button
129
+ >
130
+ </div>
131
+ </Dialog.Content>
132
+ {/if}
133
+ </Dialog.Root>
134
+
135
+ <style>
136
+ .registry-media-viewer__toolbar,
137
+ .registry-media-viewer__footer {
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: space-between;
141
+ gap: 1rem;
142
+ }
143
+ .registry-media-viewer__image {
144
+ min-width: 0;
145
+ min-height: 0;
146
+ display: grid;
147
+ place-items: center;
148
+ }
149
+ .registry-media-viewer__footer p {
150
+ margin: 0;
151
+ text-align: center;
152
+ font-size: 0.8125rem;
153
+ color: var(--muted-foreground);
154
+ }
155
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { RegistryMediaV2 } from "@lapis-notes/api";
2
+ type Mode = "full" | "light" | "dark";
3
+ type $$ComponentProps = {
4
+ items: readonly RegistryMediaV2[];
5
+ pluginName: string;
6
+ open?: boolean;
7
+ selectedIndex?: number;
8
+ initialMode?: Mode;
9
+ opener?: HTMLElement | null;
10
+ };
11
+ declare const PluginRegistryMediaViewer: import("svelte").Component<$$ComponentProps, {}, "open" | "selectedIndex">;
12
+ type PluginRegistryMediaViewer = ReturnType<typeof PluginRegistryMediaViewer>;
13
+ export default PluginRegistryMediaViewer;
@@ -0,0 +1,120 @@
1
+ <script lang="ts">
2
+ import PackageIcon from "@lucide/svelte/icons/package";
3
+ import type { Snippet } from "svelte";
4
+ import PluginRegistryBadge, {
5
+ type PluginRegistryBadgeTone,
6
+ } from "./PluginRegistryBadge.svelte";
7
+ import PluginRegistryStatus from "./PluginRegistryStatus.svelte";
8
+
9
+ export interface RegistryRowBadge {
10
+ label: string;
11
+ tone?: PluginRegistryBadgeTone;
12
+ }
13
+
14
+ let {
15
+ id,
16
+ name,
17
+ description,
18
+ version,
19
+ metadata = [],
20
+ badges = [],
21
+ status,
22
+ selected = false,
23
+ onopen,
24
+ actions,
25
+ }: {
26
+ id: string;
27
+ name: string;
28
+ description: string;
29
+ version?: string;
30
+ metadata?: string[];
31
+ badges?: RegistryRowBadge[];
32
+ status?: {
33
+ label: string;
34
+ reason?: string;
35
+ tone?: "neutral" | "success" | "warning" | "danger";
36
+ };
37
+ selected?: boolean;
38
+ onopen?: () => void;
39
+ actions?: Snippet;
40
+ } = $props();
41
+ </script>
42
+
43
+ <article
44
+ class="lapis-plugin-registry-row"
45
+ data-ui-component="plugin-registry-row"
46
+ data-plugin-id={id}
47
+ data-selected={selected || undefined}
48
+ data-has-actions={actions ? "true" : undefined}
49
+ >
50
+ <div class="lapis-plugin-registry-row__icon" aria-hidden="true">
51
+ <PackageIcon />
52
+ </div>
53
+ {#if onopen}
54
+ <button
55
+ type="button"
56
+ class="lapis-plugin-registry-row__main"
57
+ aria-label={`View details for ${name}`}
58
+ onclick={onopen}
59
+ >
60
+ <span class="lapis-plugin-registry-row__heading">
61
+ <span class="lapis-plugin-registry-row__identity">
62
+ <strong>{name}</strong>
63
+ {#each badges as item (item.label)}
64
+ <PluginRegistryBadge label={item.label} tone={item.tone} />
65
+ {/each}
66
+ {#if status}
67
+ <PluginRegistryStatus label={status.label} tone={status.tone} />
68
+ {/if}
69
+ </span>
70
+ {#if version}
71
+ <span class="lapis-plugin-registry-row__version">{version}</span>
72
+ {/if}
73
+ </span>
74
+ <span class="lapis-plugin-registry-row__description">{description}</span>
75
+ {#if metadata.length}
76
+ <span class="lapis-plugin-registry-row__metadata">
77
+ {#each metadata as item (item)}<span>{item}</span>{/each}
78
+ </span>
79
+ {/if}
80
+ {#if status?.reason}
81
+ <span
82
+ class="lapis-plugin-registry-row__status-reason"
83
+ data-status-tone={status.tone ?? "neutral"}>{status.reason}</span
84
+ >
85
+ {/if}
86
+ </button>
87
+ {:else}
88
+ <div class="lapis-plugin-registry-row__main">
89
+ <div class="lapis-plugin-registry-row__heading">
90
+ <span class="lapis-plugin-registry-row__identity">
91
+ <strong>{name}</strong>
92
+ {#each badges as item (item.label)}
93
+ <PluginRegistryBadge label={item.label} tone={item.tone} />
94
+ {/each}
95
+ {#if status}
96
+ <PluginRegistryStatus label={status.label} tone={status.tone} />
97
+ {/if}
98
+ </span>
99
+ {#if version}
100
+ <span class="lapis-plugin-registry-row__version">{version}</span>
101
+ {/if}
102
+ </div>
103
+ <p class="lapis-plugin-registry-row__description">{description}</p>
104
+ {#if metadata.length}
105
+ <div class="lapis-plugin-registry-row__metadata">
106
+ {#each metadata as item (item)}<span>{item}</span>{/each}
107
+ </div>
108
+ {/if}
109
+ {#if status?.reason}
110
+ <p
111
+ class="lapis-plugin-registry-row__status-reason"
112
+ data-status-tone={status.tone ?? "neutral"}>{status.reason}</p
113
+ >
114
+ {/if}
115
+ </div>
116
+ {/if}
117
+ {#if actions}
118
+ <div class="lapis-plugin-registry-row__actions">{@render actions()}</div>
119
+ {/if}
120
+ </article>
@@ -0,0 +1,25 @@
1
+ import type { Snippet } from "svelte";
2
+ import { type PluginRegistryBadgeTone } from "./PluginRegistryBadge.svelte";
3
+ export interface RegistryRowBadge {
4
+ label: string;
5
+ tone?: PluginRegistryBadgeTone;
6
+ }
7
+ type $$ComponentProps = {
8
+ id: string;
9
+ name: string;
10
+ description: string;
11
+ version?: string;
12
+ metadata?: string[];
13
+ badges?: RegistryRowBadge[];
14
+ status?: {
15
+ label: string;
16
+ reason?: string;
17
+ tone?: "neutral" | "success" | "warning" | "danger";
18
+ };
19
+ selected?: boolean;
20
+ onopen?: () => void;
21
+ actions?: Snippet;
22
+ };
23
+ declare const PluginRegistryRow: import("svelte").Component<$$ComponentProps, {}, "">;
24
+ type PluginRegistryRow = ReturnType<typeof PluginRegistryRow>;
25
+ export default PluginRegistryRow;