@lapis-notes/workspace 0.1.0 → 0.2.1

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 +34 -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 +558 -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 +44 -0
  63. package/dist/plugin-management/plugin-browser-model.js +104 -0
  64. package/dist/plugin-management/plugin-browser-navigation.d.ts +3 -0
  65. package/dist/plugin-management/plugin-browser-navigation.js +38 -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 +41 -0
  76. package/dist/plugin-management/plugins-view.svelte.js +109 -0
  77. package/package.json +5 -5
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import AlertTriangle from "@lucide/svelte/icons/alert-triangle";
3
+ import CheckCircle2 from "@lucide/svelte/icons/check-circle-2";
4
+ import ShieldAlert from "@lucide/svelte/icons/shield-alert";
5
+
6
+ let {
7
+ label,
8
+ reason,
9
+ tone = "neutral",
10
+ }: {
11
+ label: string;
12
+ reason?: string;
13
+ tone?: "neutral" | "success" | "warning" | "danger";
14
+ } = $props();
15
+ </script>
16
+
17
+ <div
18
+ class="lapis-plugin-registry-status"
19
+ data-ui-component="plugin-registry-status"
20
+ data-status-tone={tone}
21
+ >
22
+ <span class="lapis-plugin-registry-status__label">
23
+ {#if tone === "success"}
24
+ <CheckCircle2 aria-hidden="true" />
25
+ {:else if tone === "danger"}
26
+ <ShieldAlert aria-hidden="true" />
27
+ {:else if tone === "warning"}
28
+ <AlertTriangle aria-hidden="true" />
29
+ {/if}
30
+ {label}
31
+ </span>
32
+ {#if reason}<span class="lapis-plugin-registry-status__reason">{reason}</span>{/if}
33
+ </div>
@@ -0,0 +1,8 @@
1
+ type $$ComponentProps = {
2
+ label: string;
3
+ reason?: string;
4
+ tone?: "neutral" | "success" | "warning" | "danger";
5
+ };
6
+ declare const PluginRegistryStatus: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type PluginRegistryStatus = ReturnType<typeof PluginRegistryStatus>;
8
+ export default PluginRegistryStatus;
@@ -0,0 +1,159 @@
1
+ <script lang="ts">
2
+ import SlidersHorizontal from "@lucide/svelte/icons/sliders-horizontal";
3
+ import { SearchFilterBar } from "@lapismd/design-core/filter";
4
+ import {
5
+ FilterCommandPicker,
6
+ type FilterCommandOption,
7
+ } from "@lapismd/design-core/forms";
8
+ import * as Button from "@lapismd/design-core/shadcn/button";
9
+
10
+ type ToolbarTab = "installed" | "browse" | "updates";
11
+
12
+ let {
13
+ tab,
14
+ search = $bindable(""),
15
+ platform = $bindable("all"),
16
+ channel = $bindable("all"),
17
+ category = $bindable("all"),
18
+ installedState = $bindable("all"),
19
+ compatibleOnly = $bindable(false),
20
+ enabledState = $bindable("all"),
21
+ provenance = $bindable("all"),
22
+ updateState = $bindable("all"),
23
+ sort = $bindable("name"),
24
+ categories = [],
25
+ filterCount,
26
+ resultCount,
27
+ onreset,
28
+ }: {
29
+ tab: ToolbarTab;
30
+ search?: string;
31
+ platform?: string;
32
+ channel?: string;
33
+ category?: string;
34
+ installedState?: string;
35
+ compatibleOnly?: boolean;
36
+ enabledState?: string;
37
+ provenance?: string;
38
+ updateState?: string;
39
+ sort?: string;
40
+ categories?: string[];
41
+ filterCount: number;
42
+ resultCount: number;
43
+ onreset: () => void;
44
+ } = $props();
45
+
46
+ let filtersExpanded = $state(false);
47
+
48
+ const platformOptions: FilterCommandOption[] = [
49
+ { value: "all", label: "All platforms", description: "Show every supported host." },
50
+ { value: "web", label: "Web", description: "Runs in browser-hosted Lapis apps." },
51
+ { value: "electron", label: "Electron", description: "Runs in Electron desktop hosts." },
52
+ { value: "desktop", label: "Desktop", description: "Runs in supported desktop hosts." },
53
+ ];
54
+ const channelOptions: FilterCommandOption[] = [
55
+ { value: "all", label: "All channels", description: "Official and community releases." },
56
+ { value: "official", label: "Official", description: "Curated and verified by the Lapis registry." },
57
+ { value: "community", label: "Community", description: "Published by community maintainers." },
58
+ ];
59
+ const installedOptions: FilterCommandOption[] = [
60
+ { value: "all", label: "Any install state" },
61
+ { value: "available", label: "Available", description: "Not currently included or installed." },
62
+ { value: "installed", label: "Installed", description: "Included in the profile or vault." },
63
+ ];
64
+ const enabledOptions: FilterCommandOption[] = [
65
+ { value: "all", label: "Any status" },
66
+ { value: "enabled", label: "Enabled" },
67
+ { value: "disabled", label: "Disabled" },
68
+ { value: "revoked", label: "Revoked" },
69
+ { value: "restart", label: "Restart required" },
70
+ ];
71
+ const provenanceOptions: FilterCommandOption[] = [
72
+ { value: "all", label: "Any provenance" },
73
+ { value: "official", label: "Official" },
74
+ { value: "community", label: "Community" },
75
+ { value: "manual", label: "Manual" },
76
+ { value: "development", label: "Development" },
77
+ ];
78
+ const updateOptions: FilterCommandOption[] = [
79
+ { value: "all", label: "Any update status" },
80
+ { value: "ready", label: "Ready" },
81
+ { value: "incompatible", label: "Incompatible" },
82
+ { value: "revoked", label: "Revoked" },
83
+ ];
84
+
85
+ const browseSortOptions: FilterCommandOption[] = [
86
+ { value: "name", label: "Name" },
87
+ { value: "recent", label: "Recently updated" },
88
+ {
89
+ value: "downloads",
90
+ label: "Most downloaded",
91
+ description: "Highest approximate download requests during the last 30 days.",
92
+ },
93
+ ];
94
+ const installedSortOptions: FilterCommandOption[] = [
95
+ { value: "name", label: "Name" },
96
+ { value: "recent", label: "Recently updated" },
97
+ ];
98
+ const updateSortOptions: FilterCommandOption[] = [
99
+ { value: "status", label: "Status first" },
100
+ { value: "name", label: "Name" },
101
+ ];
102
+ let categoryOptions = $derived<FilterCommandOption[]>([
103
+ { value: "all", label: "All categories" },
104
+ ...categories.map((item) => ({ value: item, label: item })),
105
+ ]);
106
+ </script>
107
+
108
+ <div
109
+ class="lapis-plugin-registry-toolbar"
110
+ data-ui-component="plugin-registry-toolbar"
111
+ data-toolbar-tab={tab}
112
+ >
113
+ <SearchFilterBar
114
+ value={search}
115
+ placeholder={`Search ${tab === "browse" ? "plugins" : tab}`}
116
+ ariaLabel={`Search ${tab}`}
117
+ showFilterToggle
118
+ bind:filtersExpanded
119
+ showClearAll={filterCount > 0}
120
+ clearAllDisabled={filterCount === 0}
121
+ clearAllLabel="Reset search and filters"
122
+ expandFiltersLabel={`Show ${tab} filters`}
123
+ collapseFiltersLabel={`Hide ${tab} filters`}
124
+ onValueChange={(next) => (search = next)}
125
+ onClearSearch={() => {
126
+ search = "";
127
+ }}
128
+ onClearAll={onreset}
129
+ >
130
+ {#snippet filters()}
131
+ <div class="lapis-plugin-registry-toolbar__controls">
132
+ {#if tab === "browse"}
133
+ <FilterCommandPicker label="Platform" ariaLabel="Filter by platform" options={platformOptions} value={platform} onChange={(next) => { platform = next; }} />
134
+ <FilterCommandPicker label="Channel" ariaLabel="Filter by channel" options={channelOptions} value={channel} onChange={(next) => { channel = next; }} />
135
+ <FilterCommandPicker label="Category" ariaLabel="Filter by category" options={categoryOptions} value={category} onChange={(next) => { category = next; }} />
136
+ <FilterCommandPicker label="Installed" ariaLabel="Filter by installed state" options={installedOptions} value={installedState} onChange={(next) => { installedState = next; }} />
137
+ <Button.Root
138
+ size="sm"
139
+ variant={compatibleOnly ? "secondary" : "outline"}
140
+ aria-pressed={compatibleOnly}
141
+ onclick={() => (compatibleOnly = !compatibleOnly)}
142
+ ><SlidersHorizontal data-icon="inline-start" />Compatible only</Button.Root>
143
+ <FilterCommandPicker label="Sort" ariaLabel="Sort Browse plugins" options={browseSortOptions} value={sort} onChange={(next) => { sort = next; }} />
144
+ {:else if tab === "installed"}
145
+ <FilterCommandPicker label="Status" ariaLabel="Filter installed status" options={enabledOptions} value={enabledState} onChange={(next) => { enabledState = next; }} />
146
+ <FilterCommandPicker label="Provenance" ariaLabel="Filter installed provenance" options={provenanceOptions} value={provenance} onChange={(next) => { provenance = next; }} />
147
+ <FilterCommandPicker label="Sort" ariaLabel="Sort installed plugins" options={installedSortOptions} value={sort} onChange={(next) => { sort = next; }} />
148
+ {:else}
149
+ <FilterCommandPicker label="Status" ariaLabel="Filter update status" options={updateOptions} value={updateState} onChange={(next) => { updateState = next; }} />
150
+ <FilterCommandPicker label="Sort" ariaLabel="Sort updates" options={updateSortOptions} value={sort} onChange={(next) => { sort = next; }} />
151
+ {/if}
152
+ </div>
153
+ {/snippet}
154
+ </SearchFilterBar>
155
+ <div class="lapis-plugin-registry-toolbar__summary" aria-live="polite">
156
+ <span>{resultCount} {resultCount === 1 ? "result" : "results"}</span>
157
+ <span>{filterCount} {filterCount === 1 ? "active filter" : "active filters"}</span>
158
+ </div>
159
+ </div>
@@ -0,0 +1,21 @@
1
+ type ToolbarTab = "installed" | "browse" | "updates";
2
+ type $$ComponentProps = {
3
+ tab: ToolbarTab;
4
+ search?: string;
5
+ platform?: string;
6
+ channel?: string;
7
+ category?: string;
8
+ installedState?: string;
9
+ compatibleOnly?: boolean;
10
+ enabledState?: string;
11
+ provenance?: string;
12
+ updateState?: string;
13
+ sort?: string;
14
+ categories?: string[];
15
+ filterCount: number;
16
+ resultCount: number;
17
+ onreset: () => void;
18
+ };
19
+ declare const PluginRegistryToolbar: import("svelte").Component<$$ComponentProps, {}, "search" | "provenance" | "sort" | "channel" | "category" | "platform" | "compatibleOnly" | "installedState" | "enabledState" | "updateState">;
20
+ type PluginRegistryToolbar = ReturnType<typeof PluginRegistryToolbar>;
21
+ export default PluginRegistryToolbar;
@@ -0,0 +1,229 @@
1
+ <script lang="ts">
2
+ import { Badge } from "@lapismd/design-core/shadcn/badge";
3
+ import { Button } from "@lapismd/design-core/shadcn/button";
4
+ import type { CommunityChipProps } from "./props.js";
5
+
6
+ let {
7
+ children,
8
+ leading,
9
+ variant = "outline",
10
+ tone = "neutral",
11
+ size = "default",
12
+ shape = "pill",
13
+ href,
14
+ onclick,
15
+ buttonProps,
16
+ linkProps,
17
+ disabled = false,
18
+ pressed,
19
+ ariaLabel,
20
+ ariaHidden = false,
21
+ title,
22
+ class: className = "",
23
+ }: CommunityChipProps = $props();
24
+
25
+ const badgeVariant = $derived(
26
+ variant === "muted" || variant === "subtle"
27
+ ? "secondary"
28
+ : variant === "solid"
29
+ ? "default"
30
+ : "outline",
31
+ );
32
+ const action = $derived(href !== undefined || onclick !== undefined);
33
+ const classes = $derived(`community-chip ${className}`.trim());
34
+ </script>
35
+
36
+ {#snippet content()}
37
+ {#if leading}{@render leading()}{/if}
38
+ {@render children()}
39
+ {/snippet}
40
+
41
+ {#if href !== undefined}
42
+ <Badge
43
+ {...linkProps}
44
+ {href}
45
+ variant={badgeVariant}
46
+ class={classes}
47
+ data-ui-part="community-chip"
48
+ data-chip-variant={variant}
49
+ data-chip-tone={tone}
50
+ data-chip-size={size}
51
+ data-chip-shape={shape}
52
+ data-action={action ? "" : undefined}
53
+ aria-label={ariaLabel}
54
+ aria-hidden={ariaHidden ? "true" : undefined}
55
+ aria-disabled={disabled ? "true" : undefined}
56
+ tabindex={disabled ? -1 : undefined}
57
+ {title}
58
+ onclick={(event) => {
59
+ if (disabled) {
60
+ event.preventDefault();
61
+ return;
62
+ }
63
+ linkProps?.onclick?.(event);
64
+ if (!event.defaultPrevented) onclick?.(event);
65
+ }}
66
+ >
67
+ {@render content()}
68
+ </Badge>
69
+ {:else if onclick !== undefined}
70
+ <Button
71
+ {...buttonProps}
72
+ type="button"
73
+ variant={badgeVariant}
74
+ size={size === "inline" ? "xs" : "sm"}
75
+ class={classes}
76
+ data-ui-part="community-chip"
77
+ data-chip-variant={variant}
78
+ data-chip-tone={tone}
79
+ data-chip-size={size}
80
+ data-chip-shape={shape}
81
+ data-action=""
82
+ aria-label={ariaLabel}
83
+ aria-hidden={ariaHidden ? "true" : undefined}
84
+ aria-pressed={pressed}
85
+ {disabled}
86
+ {title}
87
+ onclick={(event) => {
88
+ buttonProps?.onclick?.(event);
89
+ if (!event.defaultPrevented) onclick(event);
90
+ }}
91
+ >
92
+ {@render content()}
93
+ </Button>
94
+ {:else}
95
+ <Badge
96
+ variant={badgeVariant}
97
+ class={classes}
98
+ data-ui-part="community-chip"
99
+ data-chip-variant={variant}
100
+ data-chip-tone={tone}
101
+ data-chip-size={size}
102
+ data-chip-shape={shape}
103
+ aria-label={ariaLabel}
104
+ aria-hidden={ariaHidden ? "true" : undefined}
105
+ {title}
106
+ >
107
+ {@render content()}
108
+ </Badge>
109
+ {/if}
110
+
111
+ <style>
112
+ :global(.community-chip[data-ui-component="badge"]),
113
+ :global(.community-chip[data-ui-component="button"]) {
114
+ height: 1.45rem;
115
+ min-height: 1.45rem;
116
+ gap: 0.3rem;
117
+ border-radius: calc(var(--radius) * 2.6);
118
+ padding-inline: 0.55rem;
119
+ font-size: var(--community-text-secondary);
120
+ font-weight: 550;
121
+ line-height: 1;
122
+ }
123
+
124
+ :global(.community-chip[data-chip-size="compact"]) {
125
+ height: 1.25rem;
126
+ min-width: 1.25rem;
127
+ min-height: 1.25rem;
128
+ padding-inline: 0.4rem;
129
+ font-variant-numeric: tabular-nums;
130
+ }
131
+
132
+ :global(.community-chip[data-chip-size="indicator"]) {
133
+ height: 1rem;
134
+ min-width: 1rem;
135
+ min-height: 1rem;
136
+ padding-inline: 0.2rem;
137
+ font-size: var(--community-text-secondary);
138
+ font-variant-numeric: tabular-nums;
139
+ }
140
+
141
+ :global(.community-chip[data-ui-component][data-chip-size="inline"]) {
142
+ width: fit-content;
143
+ max-width: 100%;
144
+ height: 1lh;
145
+ min-height: 0;
146
+ gap: 0.22em;
147
+ border: 0;
148
+ border-radius: calc(var(--radius) - 0.2rem);
149
+ padding: 0 0.25em;
150
+ box-decoration-break: clone;
151
+ font: inherit;
152
+ font-weight: 600;
153
+ line-height: inherit;
154
+ text-align: start;
155
+ vertical-align: baseline;
156
+ -webkit-box-decoration-break: clone;
157
+ }
158
+
159
+ :global(.community-chip[data-chip-shape="square"]) {
160
+ border-radius: 0.28rem;
161
+ }
162
+
163
+ :global(
164
+ .community-chip[data-chip-variant="muted"][data-chip-shape="square"]
165
+ ) {
166
+ border-color: transparent;
167
+ color: inherit;
168
+ background: color-mix(in srgb, currentcolor 7%, transparent);
169
+ }
170
+
171
+ :global(.community-chip[data-chip-variant="subtle"]) {
172
+ border: 0;
173
+ }
174
+
175
+ :global(
176
+ .community-chip[data-chip-variant="subtle"][data-chip-tone="neutral"]
177
+ ) {
178
+ color: var(--foreground);
179
+ background: color-mix(in srgb, var(--foreground) 8%, transparent);
180
+ }
181
+
182
+ :global(.community-chip[data-chip-tone="accent"]) {
183
+ border-color: color-mix(in srgb, var(--primary) 38%, var(--border));
184
+ color: var(--foreground);
185
+ background: color-mix(in srgb, var(--primary) 12%, var(--background));
186
+ }
187
+
188
+ :global(.community-chip[data-chip-tone="success"]) {
189
+ border-color: color-mix(in srgb, #16a34a 44%, var(--border));
190
+ color: color-mix(in srgb, #16a34a 65%, var(--foreground));
191
+ background: color-mix(in srgb, #16a34a 14%, var(--background));
192
+ }
193
+
194
+ :global(.community-chip[data-chip-tone="warning"]) {
195
+ border-color: color-mix(in srgb, #d97706 42%, var(--border));
196
+ color: color-mix(in srgb, #b45309 78%, var(--foreground));
197
+ background: color-mix(in srgb, #f59e0b 13%, var(--background));
198
+ }
199
+
200
+ :global(.community-chip[data-chip-tone="danger"]) {
201
+ border-color: color-mix(in srgb, var(--destructive) 42%, var(--border));
202
+ color: var(--foreground);
203
+ background: color-mix(in srgb, var(--destructive) 10%, var(--background));
204
+ }
205
+
206
+ :global(
207
+ .community-chip[data-ui-component="badge"][data-action]:hover,
208
+ .community-chip[data-ui-component="button"][data-action]:hover
209
+ ) {
210
+ background-color: color-mix(
211
+ in srgb,
212
+ var(--foreground) 10%,
213
+ var(--background)
214
+ );
215
+ color: var(--accent-foreground);
216
+ }
217
+
218
+ :global(.community-chip[aria-disabled="true"]) {
219
+ opacity: 0.5;
220
+ pointer-events: none;
221
+ }
222
+
223
+ :global(.community-chip > svg),
224
+ :global(.community-chip [data-icon]) {
225
+ width: 0.8rem;
226
+ height: 0.8rem;
227
+ flex: none;
228
+ }
229
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { CommunityChipProps } from "./props.js";
2
+ declare const CommunityChip: import("svelte").Component<CommunityChipProps, {}, "">;
3
+ type CommunityChip = ReturnType<typeof CommunityChip>;
4
+ export default CommunityChip;
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ import { communityMemberAvatarFallbackTone } from "./avatar-model";
3
+ import "./community-member-avatar.css";
4
+ let {
5
+ member,
6
+ size = "small",
7
+ decorative = false,
8
+ }: {
9
+ member: { name: string; initials: string; avatarUrl?: string };
10
+ size?: "small" | "xsmall";
11
+ decorative?: boolean;
12
+ } = $props();
13
+ const fallbackTone = $derived(communityMemberAvatarFallbackTone(member.name));
14
+ let failed = $state(false);
15
+ $effect(() => {
16
+ member.avatarUrl;
17
+ failed = false;
18
+ });
19
+ </script>
20
+
21
+ <span
22
+ class="community-member-avatar"
23
+ data-ui-component="community-member-avatar"
24
+ data-size={size}
25
+ data-shape="circle"
26
+ aria-hidden={decorative ? "true" : undefined}
27
+ aria-label={decorative ? undefined : `${member.name} avatar`}
28
+ role={decorative ? undefined : "img"}
29
+ >
30
+ <span
31
+ class="community-member-avatar__image"
32
+ data-ui-part="image"
33
+ data-content={member.avatarUrl && !failed ? "image" : "initials"}
34
+ data-fallback-tone={member.avatarUrl && !failed ? undefined : fallbackTone}
35
+ >
36
+ {#if member.avatarUrl && !failed}<img
37
+ src={member.avatarUrl}
38
+ alt=""
39
+ onerror={() => (failed = true)}
40
+ />{:else}<span aria-hidden="true">{member.initials}</span>{/if}
41
+ </span>
42
+ </span>
@@ -0,0 +1,13 @@
1
+ import "./community-member-avatar.css";
2
+ type $$ComponentProps = {
3
+ member: {
4
+ name: string;
5
+ initials: string;
6
+ avatarUrl?: string;
7
+ };
8
+ size?: "small" | "xsmall";
9
+ decorative?: boolean;
10
+ };
11
+ declare const CommunityMemberAvatar: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type CommunityMemberAvatar = ReturnType<typeof CommunityMemberAvatar>;
13
+ export default CommunityMemberAvatar;