@ewanc26/svelte-standard-site 0.2.2 → 0.2.4

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 (69) hide show
  1. package/dist/components/ActionBar.svelte +85 -0
  2. package/dist/components/ActionBar.svelte.d.ts +13 -0
  3. package/dist/components/Avatar.svelte +104 -0
  4. package/dist/components/Avatar.svelte.d.ts +19 -0
  5. package/dist/components/Comment.svelte +172 -0
  6. package/dist/components/Comment.svelte.d.ts +22 -0
  7. package/dist/components/CommentsSection.svelte +89 -0
  8. package/dist/components/DocumentCard.svelte +126 -56
  9. package/dist/components/DocumentCard.svelte.d.ts +51 -0
  10. package/dist/components/Footnotes.svelte +72 -0
  11. package/dist/components/Footnotes.svelte.d.ts +13 -0
  12. package/dist/components/RecommendButton.svelte +153 -0
  13. package/dist/components/RecommendButton.svelte.d.ts +17 -0
  14. package/dist/components/ThemeProvider.svelte +92 -0
  15. package/dist/components/ThemeProvider.svelte.d.ts +13 -0
  16. package/dist/components/Toast.svelte +177 -0
  17. package/dist/components/Toast.svelte.d.ts +32 -0
  18. package/dist/components/Watermark.svelte +100 -0
  19. package/dist/components/Watermark.svelte.d.ts +17 -0
  20. package/dist/components/common/ThemedCard.svelte +15 -15
  21. package/dist/components/common/ThemedCard.svelte.d.ts +5 -0
  22. package/dist/components/document/BlockRenderer.svelte +3 -0
  23. package/dist/components/document/DocumentRenderer.svelte +41 -1
  24. package/dist/components/document/RichText.svelte +87 -2
  25. package/dist/components/document/RichText.svelte.d.ts +2 -0
  26. package/dist/components/document/blocks/OrderedListBlock.svelte +152 -0
  27. package/dist/components/document/blocks/UnorderedListBlock.svelte +1 -1
  28. package/dist/components/index.d.ts +28 -0
  29. package/dist/components/index.js +30 -0
  30. package/dist/index.d.ts +5 -4
  31. package/dist/index.js +6 -4
  32. package/dist/publisher.d.ts +73 -0
  33. package/dist/publisher.js +185 -0
  34. package/dist/schemas.d.ts +1162 -2
  35. package/dist/schemas.js +316 -0
  36. package/dist/types.d.ts +393 -2
  37. package/dist/types.js +1 -1
  38. package/dist/utils/native-comments.d.ts +68 -0
  39. package/dist/utils/native-comments.js +149 -0
  40. package/dist/utils/theme-helpers.d.ts +41 -1
  41. package/dist/utils/theme-helpers.js +98 -1
  42. package/dist/utils/theme.d.ts +48 -1
  43. package/dist/utils/theme.js +158 -0
  44. package/package.json +62 -65
  45. package/src/lib/components/ActionBar.svelte +85 -0
  46. package/src/lib/components/Avatar.svelte +104 -0
  47. package/src/lib/components/Comment.svelte +172 -0
  48. package/src/lib/components/CommentsSection.svelte +89 -0
  49. package/src/lib/components/DocumentCard.svelte +126 -56
  50. package/src/lib/components/Footnotes.svelte +72 -0
  51. package/src/lib/components/RecommendButton.svelte +153 -0
  52. package/src/lib/components/ThemeProvider.svelte +92 -0
  53. package/src/lib/components/Toast.svelte +177 -0
  54. package/src/lib/components/Watermark.svelte +100 -0
  55. package/src/lib/components/common/ThemedCard.svelte +15 -15
  56. package/src/lib/components/document/BlockRenderer.svelte +3 -0
  57. package/src/lib/components/document/DocumentRenderer.svelte +41 -1
  58. package/src/lib/components/document/RichText.svelte +87 -2
  59. package/src/lib/components/document/blocks/OrderedListBlock.svelte +152 -0
  60. package/src/lib/components/document/blocks/UnorderedListBlock.svelte +1 -1
  61. package/src/lib/components/index.ts +32 -0
  62. package/src/lib/index.ts +119 -5
  63. package/src/lib/publisher.ts +251 -0
  64. package/src/lib/schemas.ts +411 -0
  65. package/src/lib/types.ts +506 -2
  66. package/src/lib/utils/native-comments.ts +197 -0
  67. package/src/lib/utils/theme-helpers.ts +136 -3
  68. package/src/lib/utils/theme.ts +189 -1
  69. package/dist/components/document/blocks/UnorderedListBlock.svelte.d.ts +0 -9
@@ -1,4 +1,4 @@
1
- import { rgbToCSS } from './theme.js';
1
+ import { rgbToCSS, colorToCSS, isRGBA } from './theme.js';
2
2
  /**
3
3
  * Generate color-mix CSS for theme colors with transparency
4
4
  */
@@ -48,6 +48,57 @@ export function getThemedAccent(hasTheme, opacity) {
48
48
  color: 'var(--theme-accent)'
49
49
  };
50
50
  }
51
+ /**
52
+ * Get theme-aware page background
53
+ */
54
+ export function getThemedPageBackground(hasTheme, showPageBackground) {
55
+ if (!hasTheme)
56
+ return {};
57
+ if (showPageBackground === false)
58
+ return {};
59
+ return { backgroundColor: 'var(--theme-page-background)' };
60
+ }
61
+ /**
62
+ * Get background image styles
63
+ */
64
+ export function getBackgroundImageStyles(bgImage) {
65
+ if (!bgImage?.url)
66
+ return {};
67
+ return {
68
+ backgroundImage: `url(${bgImage.url})`,
69
+ backgroundSize: 'cover',
70
+ backgroundPosition: 'center'
71
+ };
72
+ }
73
+ /**
74
+ * Get font styles from theme
75
+ */
76
+ export function getFontStyles(theme) {
77
+ if (!theme)
78
+ return {};
79
+ const headingFont = theme.headingFont;
80
+ const bodyFont = theme.bodyFont;
81
+ if (bodyFont) {
82
+ return { fontFamily: `"${bodyFont}", system-ui, sans-serif` };
83
+ }
84
+ return {};
85
+ }
86
+ /**
87
+ * Get heading font styles from theme
88
+ */
89
+ export function getHeadingFontStyles(theme) {
90
+ if (!theme?.headingFont)
91
+ return {};
92
+ return { fontFamily: `"${theme.headingFont}", system-ui, sans-serif` };
93
+ }
94
+ /**
95
+ * Get page width styles from theme
96
+ */
97
+ export function getPageWidthStyles(theme) {
98
+ if (!theme?.pageWidth)
99
+ return {};
100
+ return { maxWidth: `${theme.pageWidth}px` };
101
+ }
51
102
  /**
52
103
  * Convert BasicTheme to CSS custom properties
53
104
  */
@@ -61,3 +112,49 @@ export function themeToCssVars(theme) {
61
112
  '--theme-accent-foreground': rgbToCSS(theme.accentForeground)
62
113
  };
63
114
  }
115
+ /**
116
+ * Convert ExtendedTheme to CSS custom properties
117
+ */
118
+ export function extendedThemeToCssVars(theme) {
119
+ if (!theme)
120
+ return {};
121
+ const vars = {};
122
+ if (theme.backgroundColor) {
123
+ vars['--theme-background'] = colorToCSS(theme.backgroundColor);
124
+ }
125
+ // Extended theme uses different property names
126
+ if (theme.pageBackground) {
127
+ vars['--theme-page-background'] = colorToCSS(theme.pageBackground);
128
+ }
129
+ if (theme.primary) {
130
+ vars['--theme-accent'] = colorToCSS(theme.primary);
131
+ }
132
+ if (theme.accentBackground) {
133
+ vars['--theme-accent-background'] = colorToCSS(theme.accentBackground);
134
+ }
135
+ if (theme.accentText) {
136
+ vars['--theme-accent-foreground'] = colorToCSS(theme.accentText);
137
+ }
138
+ if (theme.headingFont) {
139
+ vars['--theme-heading-font'] = `"${theme.headingFont}", system-ui, sans-serif`;
140
+ }
141
+ if (theme.bodyFont) {
142
+ vars['--theme-body-font'] = `"${theme.bodyFont}", system-ui, sans-serif`;
143
+ }
144
+ if (theme.pageWidth) {
145
+ vars['--theme-page-width'] = `${theme.pageWidth}px`;
146
+ }
147
+ return vars;
148
+ }
149
+ /**
150
+ * Convert any theme to CSS custom properties
151
+ */
152
+ export function anyThemeToCssVars(theme) {
153
+ if (!theme)
154
+ return {};
155
+ // Check for basic theme properties
156
+ if ('background' in theme && 'foreground' in theme) {
157
+ return themeToCssVars(theme);
158
+ }
159
+ return extendedThemeToCssVars(theme);
160
+ }
@@ -1,12 +1,28 @@
1
- import type { RGBColor } from '../types.js';
1
+ import type { RGBColor, RGBAColor, Color, BasicTheme, ExtendedTheme, BackgroundImage } from '../types.js';
2
+ /**
3
+ * Type guard for RGBA color
4
+ */
5
+ export declare function isRGBA(color: Color): color is RGBAColor;
2
6
  /**
3
7
  * Convert RGB color object to CSS rgb() string
4
8
  */
5
9
  export declare function rgbToCSS(color: RGBColor): string;
10
+ /**
11
+ * Convert RGBA color object to CSS rgba() string
12
+ */
13
+ export declare function rgbaToCSS(color: RGBAColor): string;
14
+ /**
15
+ * Convert Color (RGB or RGBA) to CSS string
16
+ */
17
+ export declare function colorToCSS(color: Color): string;
6
18
  /**
7
19
  * Convert RGB color object to hex string
8
20
  */
9
21
  export declare function rgbToHex(color: RGBColor): string;
22
+ /**
23
+ * Convert RGBA color object to hex string with alpha
24
+ */
25
+ export declare function rgbaToHex(color: RGBAColor): string;
10
26
  /**
11
27
  * Get theme CSS variables from BasicTheme
12
28
  */
@@ -16,3 +32,34 @@ export declare function getThemeVars(theme: {
16
32
  accent: RGBColor;
17
33
  accentForeground: RGBColor;
18
34
  }): Record<string, string>;
35
+ /**
36
+ * Get CSS for background image
37
+ */
38
+ export declare function getBackgroundImageCSS(bgImage: BackgroundImage | undefined): Record<string, string>;
39
+ /**
40
+ * Convert BasicTheme to CSS custom properties
41
+ */
42
+ export declare function basicThemeToCssVars(theme: BasicTheme): Record<string, string>;
43
+ /**
44
+ * Convert ExtendedTheme to CSS custom properties
45
+ */
46
+ export declare function extendedThemeToCssVars(theme: ExtendedTheme): Record<string, string>;
47
+ /**
48
+ * Convert any theme (Basic or Extended) to CSS custom properties
49
+ */
50
+ export declare function themeToCssVars(theme: BasicTheme | ExtendedTheme | undefined): Record<string, string>;
51
+ /**
52
+ * Get font family CSS with fallbacks
53
+ */
54
+ export declare function getFontFamilyCSS(fontName: string | undefined, fallback: string): string;
55
+ /**
56
+ * Generate Google Fonts URL for custom fonts
57
+ */
58
+ export declare function getGoogleFontsUrl(fonts: {
59
+ headingFont?: string;
60
+ bodyFont?: string;
61
+ }): string | null;
62
+ /**
63
+ * Get all theme CSS variables including defaults
64
+ */
65
+ export declare function getAllThemeVars(theme?: BasicTheme | ExtendedTheme): Record<string, string>;
@@ -1,9 +1,32 @@
1
+ /**
2
+ * Type guard for RGBA color
3
+ */
4
+ export function isRGBA(color) {
5
+ return 'a' in color;
6
+ }
1
7
  /**
2
8
  * Convert RGB color object to CSS rgb() string
3
9
  */
4
10
  export function rgbToCSS(color) {
5
11
  return `rgb(${color.r}, ${color.g}, ${color.b})`;
6
12
  }
13
+ /**
14
+ * Convert RGBA color object to CSS rgba() string
15
+ */
16
+ export function rgbaToCSS(color) {
17
+ // Alpha is 0-100, convert to 0-1
18
+ const alpha = color.a / 100;
19
+ return `rgba(${color.r}, ${color.g}, ${color.b}, ${alpha})`;
20
+ }
21
+ /**
22
+ * Convert Color (RGB or RGBA) to CSS string
23
+ */
24
+ export function colorToCSS(color) {
25
+ if (isRGBA(color)) {
26
+ return rgbaToCSS(color);
27
+ }
28
+ return rgbToCSS(color);
29
+ }
7
30
  /**
8
31
  * Convert RGB color object to hex string
9
32
  */
@@ -11,6 +34,14 @@ export function rgbToHex(color) {
11
34
  const toHex = (n) => n.toString(16).padStart(2, '0');
12
35
  return `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}`;
13
36
  }
37
+ /**
38
+ * Convert RGBA color object to hex string with alpha
39
+ */
40
+ export function rgbaToHex(color) {
41
+ const toHex = (n) => n.toString(16).padStart(2, '0');
42
+ const alpha = Math.round((color.a / 100) * 255);
43
+ return `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}${toHex(alpha)}`;
44
+ }
14
45
  /**
15
46
  * Get theme CSS variables from BasicTheme
16
47
  */
@@ -22,3 +53,130 @@ export function getThemeVars(theme) {
22
53
  '--theme-accent-foreground': rgbToCSS(theme.accentForeground)
23
54
  };
24
55
  }
56
+ /**
57
+ * Get CSS for background image
58
+ */
59
+ export function getBackgroundImageCSS(bgImage) {
60
+ if (!bgImage?.url)
61
+ return {};
62
+ const styles = {
63
+ 'background-image': `url(${bgImage.url})`,
64
+ 'background-size': 'cover',
65
+ 'background-position': 'center',
66
+ 'background-repeat': 'no-repeat'
67
+ };
68
+ if (bgImage.opacity !== undefined) {
69
+ // For opacity, we need to use a pseudo-element or overlay
70
+ // This is handled separately in the component
71
+ }
72
+ if (bgImage.blur !== undefined) {
73
+ styles['background-blur'] = `${bgImage.blur}px`;
74
+ }
75
+ return styles;
76
+ }
77
+ /**
78
+ * Convert BasicTheme to CSS custom properties
79
+ */
80
+ export function basicThemeToCssVars(theme) {
81
+ return {
82
+ '--theme-background': rgbToCSS(theme.background),
83
+ '--theme-foreground': rgbToCSS(theme.foreground),
84
+ '--theme-accent': rgbToCSS(theme.accent),
85
+ '--theme-accent-foreground': rgbToCSS(theme.accentForeground)
86
+ };
87
+ }
88
+ /**
89
+ * Convert ExtendedTheme to CSS custom properties
90
+ */
91
+ export function extendedThemeToCssVars(theme) {
92
+ const vars = {};
93
+ if (theme.backgroundColor) {
94
+ vars['--theme-background'] = colorToCSS(theme.backgroundColor);
95
+ }
96
+ if (theme.pageBackground) {
97
+ vars['--theme-page-background'] = colorToCSS(theme.pageBackground);
98
+ }
99
+ if (theme.primary) {
100
+ vars['--theme-primary'] = colorToCSS(theme.primary);
101
+ }
102
+ if (theme.accentBackground) {
103
+ vars['--theme-accent-background'] = colorToCSS(theme.accentBackground);
104
+ }
105
+ if (theme.accentText) {
106
+ vars['--theme-accent-text'] = colorToCSS(theme.accentText);
107
+ }
108
+ if (theme.headingFont) {
109
+ vars['--theme-heading-font'] = theme.headingFont;
110
+ }
111
+ if (theme.bodyFont) {
112
+ vars['--theme-body-font'] = theme.bodyFont;
113
+ }
114
+ if (theme.pageWidth) {
115
+ vars['--theme-page-width'] = `${theme.pageWidth}px`;
116
+ }
117
+ // Map extended theme properties to standard theme variables for compatibility
118
+ if (theme.backgroundColor) {
119
+ vars['--theme-background'] = colorToCSS(theme.backgroundColor);
120
+ }
121
+ // Use primary as accent if accent is not defined
122
+ if (theme.primary && !theme.accentBackground) {
123
+ vars['--theme-accent'] = colorToCSS(theme.primary);
124
+ }
125
+ return vars;
126
+ }
127
+ /**
128
+ * Convert any theme (Basic or Extended) to CSS custom properties
129
+ */
130
+ export function themeToCssVars(theme) {
131
+ if (!theme)
132
+ return {};
133
+ // Check if it's a basic theme
134
+ if ('background' in theme && 'foreground' in theme && 'accent' in theme && 'accentForeground' in theme) {
135
+ return basicThemeToCssVars(theme);
136
+ }
137
+ // Otherwise treat as extended theme
138
+ return extendedThemeToCssVars(theme);
139
+ }
140
+ /**
141
+ * Get font family CSS with fallbacks
142
+ */
143
+ export function getFontFamilyCSS(fontName, fallback) {
144
+ if (!fontName)
145
+ return fallback;
146
+ return `"${fontName}", ${fallback}`;
147
+ }
148
+ /**
149
+ * Generate Google Fonts URL for custom fonts
150
+ */
151
+ export function getGoogleFontsUrl(fonts) {
152
+ const families = [];
153
+ if (fonts.headingFont) {
154
+ families.push(`${encodeURIComponent(fonts.headingFont)}:wght@400;600;700`);
155
+ }
156
+ if (fonts.bodyFont) {
157
+ families.push(`${encodeURIComponent(fonts.bodyFont)}:wght@400;500;600`);
158
+ }
159
+ if (families.length === 0)
160
+ return null;
161
+ return `https://fonts.googleapis.com/css2?family=${families.join('&family=')}&display=swap`;
162
+ }
163
+ /**
164
+ * Get all theme CSS variables including defaults
165
+ */
166
+ export function getAllThemeVars(theme) {
167
+ const defaults = {
168
+ '--theme-background': 'rgb(255, 255, 255)',
169
+ '--theme-foreground': 'rgb(0, 0, 0)',
170
+ '--theme-accent': 'rgb(0, 0, 225)',
171
+ '--theme-accent-foreground': 'rgb(255, 255, 255)',
172
+ '--theme-page-background': 'rgb(255, 255, 255)',
173
+ '--theme-primary': 'rgb(0, 0, 225)',
174
+ '--theme-accent-background': 'rgb(0, 0, 225)',
175
+ '--theme-accent-text': 'rgb(255, 255, 255)',
176
+ '--theme-heading-font': 'system-ui, -apple-system, sans-serif',
177
+ '--theme-body-font': 'system-ui, -apple-system, sans-serif',
178
+ '--theme-page-width': '800px'
179
+ };
180
+ const themeVars = themeToCssVars(theme);
181
+ return { ...defaults, ...themeVars };
182
+ }
package/package.json CHANGED
@@ -1,13 +1,48 @@
1
1
  {
2
2
  "name": "@ewanc26/svelte-standard-site",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "SvelteKit library for reading and writing AT Protocol longform content via site.standard.* records — with a complete design system, federated comments, publishing tools, and content verification.",
5
+ "author": "Ewan Croft",
5
6
  "license": "AGPL-3.0-only",
6
- "author": {
7
- "name": "Ewan Croft",
8
- "url": "https://github.com/ewanc26"
9
- },
10
7
  "type": "module",
8
+ "keywords": [
9
+ "svelte",
10
+ "sveltekit",
11
+ "atproto",
12
+ "at-protocol",
13
+ "bluesky",
14
+ "site-standard",
15
+ "blog",
16
+ "cms",
17
+ "design-system",
18
+ "components",
19
+ "dark-mode",
20
+ "light-mode",
21
+ "theme",
22
+ "publishing",
23
+ "federation",
24
+ "comments"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/ewanc26/pkgs.git",
29
+ "directory": "packages/svelte-standard-site"
30
+ },
31
+ "homepage": "https://github.com/ewanc26/pkgs/tree/main/packages/svelte-standard-site",
32
+ "bugs": {
33
+ "url": "https://github.com/ewanc26/pkgs/issues"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "src/lib",
41
+ "README.md"
42
+ ],
43
+ "sideEffects": [
44
+ "**/*.css"
45
+ ],
11
46
  "svelte": "./dist/index.js",
12
47
  "types": "./dist/index.d.ts",
13
48
  "exports": {
@@ -25,8 +60,8 @@
25
60
  "default": "./dist/utils/content.js"
26
61
  },
27
62
  "./comments": {
28
- "types": "./dist/utils/comments.d.ts",
29
- "default": "./dist/utils/comments.js"
63
+ "types": "./dist/utils/native-comments.d.ts",
64
+ "default": "./dist/utils/native-comments.js"
30
65
  },
31
66
  "./verification": {
32
67
  "types": "./dist/utils/verification.d.ts",
@@ -47,23 +82,23 @@
47
82
  "default": "./dist/styles/themes.css"
48
83
  }
49
84
  },
50
- "files": [
51
- "dist",
52
- "src/lib",
53
- "README.md"
54
- ],
55
- "sideEffects": [
56
- "**/*.css"
57
- ],
58
- "publishConfig": {
59
- "access": "public"
60
- },
61
- "peerDependencies": {
62
- "@sveltejs/kit": "^2.0.0",
63
- "svelte": "^5.0.0"
85
+ "scripts": {
86
+ "build": "svelte-kit sync && svelte-package -i src/lib -o dist && publint",
87
+ "dev": "svelte-kit sync && svelte-package -i src/lib -o dist --watch",
88
+ "dev:app": "vite dev",
89
+ "preview": "vite preview",
90
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
91
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
92
+ "test": "vitest run",
93
+ "test:watch": "vitest",
94
+ "format": "prettier --write .",
95
+ "lint": "prettier --check ."
64
96
  },
65
97
  "dependencies": {
66
98
  "@atproto/api": "^0.19.3",
99
+ "@ewanc26/atproto": "workspace:*",
100
+ "@ewanc26/tid": "workspace:*",
101
+ "@ewanc26/utils": "workspace:*",
67
102
  "@lucide/svelte": "^0.577.0",
68
103
  "katex": "^0.16.38",
69
104
  "rehype-slug": "^6.0.0",
@@ -73,10 +108,11 @@
73
108
  "remark-rehype": "^11.1.2",
74
109
  "shiki": "^3.21.0",
75
110
  "unified": "^11.0.5",
76
- "zod": "^3.24.0",
77
- "@ewanc26/atproto": "0.2.4",
78
- "@ewanc26/tid": "1.1.2",
79
- "@ewanc26/utils": "0.1.4"
111
+ "zod": "^3.24.0"
112
+ },
113
+ "peerDependencies": {
114
+ "@sveltejs/kit": "^2.0.0",
115
+ "svelte": "^5.0.0"
80
116
  },
81
117
  "devDependencies": {
82
118
  "@sveltejs/adapter-auto": "^7.0.1",
@@ -96,44 +132,5 @@
96
132
  "typescript": "^5.9.3",
97
133
  "vite": "^7.2.6",
98
134
  "vitest": "^4.1.0"
99
- },
100
- "keywords": [
101
- "svelte",
102
- "sveltekit",
103
- "atproto",
104
- "at-protocol",
105
- "bluesky",
106
- "site-standard",
107
- "blog",
108
- "cms",
109
- "design-system",
110
- "components",
111
- "dark-mode",
112
- "light-mode",
113
- "theme",
114
- "publishing",
115
- "federation",
116
- "comments"
117
- ],
118
- "repository": {
119
- "type": "git",
120
- "url": "git+https://github.com/ewanc26/pkgs.git",
121
- "directory": "packages/svelte-standard-site"
122
- },
123
- "homepage": "https://github.com/ewanc26/pkgs/tree/main/packages/svelte-standard-site",
124
- "bugs": {
125
- "url": "https://github.com/ewanc26/pkgs/issues"
126
- },
127
- "scripts": {
128
- "build": "svelte-kit sync && svelte-package -i src/lib -o dist && publint",
129
- "dev": "svelte-kit sync && svelte-package -i src/lib -o dist --watch",
130
- "dev:app": "vite dev",
131
- "preview": "vite preview",
132
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
133
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
134
- "test": "vitest run",
135
- "test:watch": "vitest",
136
- "format": "prettier --write .",
137
- "lint": "prettier --check ."
138
135
  }
139
- }
136
+ }
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ /** Snippet for action items */
4
+ children: import('svelte').Snippet;
5
+ /** Position of the action bar */
6
+ position?: 'left' | 'bottom' | 'right';
7
+ /** Has theme applied */
8
+ hasTheme?: boolean;
9
+ /** Sticky positioning */
10
+ sticky?: boolean;
11
+ }
12
+
13
+ const { children, position = 'left', hasTheme = false, sticky = false }: Props = $props();
14
+ </script>
15
+
16
+ <div
17
+ class="action-bar {position}"
18
+ class:themed={hasTheme}
19
+ class:sticky
20
+ >
21
+ {@render children()}
22
+ </div>
23
+
24
+ <style>
25
+ .action-bar {
26
+ display: flex;
27
+ gap: 0.5rem;
28
+ padding: 0.5rem;
29
+ border-radius: 0.5rem;
30
+ background-color: rgb(255 255 255);
31
+ box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);
32
+ }
33
+
34
+ .action-bar.themed {
35
+ background-color: var(--theme-page-background, var(--theme-background));
36
+ }
37
+
38
+ .action-bar.left {
39
+ flex-direction: column;
40
+ align-items: center;
41
+ }
42
+
43
+ .action-bar.bottom {
44
+ flex-direction: row;
45
+ align-items: center;
46
+ }
47
+
48
+ .action-bar.right {
49
+ flex-direction: column;
50
+ align-items: center;
51
+ }
52
+
53
+ .action-bar.sticky {
54
+ position: sticky;
55
+ }
56
+
57
+ .action-bar.left.sticky {
58
+ top: 1rem;
59
+ }
60
+
61
+ .action-bar.bottom.sticky {
62
+ bottom: 1rem;
63
+ }
64
+
65
+ .action-bar :global(button) {
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ width: 2.5rem;
70
+ height: 2.5rem;
71
+ border-radius: 9999px;
72
+ background: transparent;
73
+ border: none;
74
+ cursor: pointer;
75
+ transition: background-color 0.15s;
76
+ }
77
+
78
+ .action-bar :global(button:hover) {
79
+ background-color: rgb(243 244 246);
80
+ }
81
+
82
+ .action-bar.themed :global(button:hover) {
83
+ background-color: color-mix(in srgb, var(--theme-foreground) 10%, transparent);
84
+ }
85
+ </style>
@@ -0,0 +1,104 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ /** User's DID */
4
+ did: string;
5
+ /** User's handle (optional, for display) */
6
+ handle?: string;
7
+ /** User's display name */
8
+ displayName?: string;
9
+ /** Avatar image URL */
10
+ src?: string;
11
+ /** Size in pixels */
12
+ size?: number;
13
+ /** Whether to link to profile */
14
+ link?: boolean;
15
+ /** Has theme applied */
16
+ hasTheme?: boolean;
17
+ }
18
+
19
+ const {
20
+ did,
21
+ handle,
22
+ displayName,
23
+ src,
24
+ size = 40,
25
+ link = true,
26
+ hasTheme = false
27
+ }: Props = $props();
28
+
29
+ // Generate initials from display name or handle
30
+ const initials = $derived(
31
+ (displayName || handle || did)
32
+ .split(/[.\s_@]+/)
33
+ .slice(0, 2)
34
+ .map((s) => s.charAt(0).toUpperCase())
35
+ .join('')
36
+ );
37
+
38
+ // Profile URL
39
+ const profileUrl = $derived(`https://leaflet.pub/p/${did}`);
40
+
41
+ // Size classes
42
+ const sizeStyle = $derived(`width: ${size}px; height: ${size}px; font-size: ${size * 0.4}px;`);
43
+ </script>
44
+
45
+ {#if link}
46
+ <a
47
+ href={profileUrl}
48
+ target="_blank"
49
+ rel="noopener noreferrer"
50
+ class="avatar-link"
51
+ title={displayName || handle || did}
52
+ >
53
+ {#if src}
54
+ <img
55
+ src={src}
56
+ alt={displayName || handle || 'Avatar'}
57
+ class="avatar rounded-full object-cover"
58
+ style={sizeStyle}
59
+ loading="lazy"
60
+ />
61
+ {:else}
62
+ <div
63
+ class="avatar-placeholder rounded-full flex items-center justify-center font-medium"
64
+ class:themed={hasTheme}
65
+ style={sizeStyle}
66
+ >
67
+ {initials}
68
+ </div>
69
+ {/if}
70
+ </a>
71
+ {:else if src}
72
+ <img
73
+ src={src}
74
+ alt={displayName || handle || 'Avatar'}
75
+ class="avatar rounded-full object-cover"
76
+ style={sizeStyle}
77
+ loading="lazy"
78
+ />
79
+ {:else}
80
+ <div
81
+ class="avatar-placeholder rounded-full flex items-center justify-center font-medium"
82
+ class:themed={hasTheme}
83
+ style={sizeStyle}
84
+ >
85
+ {initials}
86
+ </div>
87
+ {/if}
88
+
89
+ <style>
90
+ .avatar-link {
91
+ display: inline-block;
92
+ text-decoration: none;
93
+ }
94
+
95
+ .avatar-placeholder {
96
+ background-color: rgb(229 231 235);
97
+ color: rgb(107 114 128);
98
+ }
99
+
100
+ .avatar-placeholder.themed {
101
+ background-color: color-mix(in srgb, var(--theme-accent) 20%, transparent);
102
+ color: var(--theme-accent);
103
+ }
104
+ </style>