@prosefly/astro-components 0.1.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 (67) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +108 -0
  3. package/dist/astro-shim.d.ts +4 -0
  4. package/dist/client/accordions.d.ts +7 -0
  5. package/dist/client/accordions.d.ts.map +1 -0
  6. package/dist/client/accordions.js +70 -0
  7. package/dist/client/accordions.js.map +1 -0
  8. package/dist/client/image-gallery.d.ts +7 -0
  9. package/dist/client/image-gallery.d.ts.map +1 -0
  10. package/dist/client/image-gallery.js +103 -0
  11. package/dist/client/image-gallery.js.map +1 -0
  12. package/dist/client/tabs.d.ts +7 -0
  13. package/dist/client/tabs.d.ts.map +1 -0
  14. package/dist/client/tabs.js +160 -0
  15. package/dist/client/tabs.js.map +1 -0
  16. package/dist/icon/index.d.ts +8 -0
  17. package/dist/icon/index.d.ts.map +1 -0
  18. package/dist/icon/index.js +131 -0
  19. package/dist/icon/index.js.map +1 -0
  20. package/dist/icon/middleware.d.ts +3 -0
  21. package/dist/icon/middleware.d.ts.map +1 -0
  22. package/dist/icon/middleware.js +28 -0
  23. package/dist/icon/middleware.js.map +1 -0
  24. package/dist/index.d.ts +13 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +12 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/markdown/image-gallery-plugin.d.ts +3 -0
  29. package/dist/markdown/image-gallery-plugin.d.ts.map +1 -0
  30. package/dist/markdown/image-gallery-plugin.js +125 -0
  31. package/dist/markdown/image-gallery-plugin.js.map +1 -0
  32. package/dist/markdown/image-gallery.css +119 -0
  33. package/dist/markdown/image-gallery.js +1 -0
  34. package/dist/markdown/index.d.ts +3 -0
  35. package/dist/markdown/index.d.ts.map +1 -0
  36. package/dist/markdown/index.js +3 -0
  37. package/dist/markdown/index.js.map +1 -0
  38. package/dist/markdown/package-manager-tabs.d.ts +3 -0
  39. package/dist/markdown/package-manager-tabs.d.ts.map +1 -0
  40. package/dist/markdown/package-manager-tabs.js +329 -0
  41. package/dist/markdown/package-manager-tabs.js.map +1 -0
  42. package/dist/mdx/AccordionItem.astro +54 -0
  43. package/dist/mdx/Accordions.astro +126 -0
  44. package/dist/mdx/Badge.astro +140 -0
  45. package/dist/mdx/Callout.astro +124 -0
  46. package/dist/mdx/Card.astro +138 -0
  47. package/dist/mdx/CardGrid.astro +18 -0
  48. package/dist/mdx/FileTree.astro +231 -0
  49. package/dist/mdx/FileTreeItem.astro +97 -0
  50. package/dist/mdx/Icon.astro +132 -0
  51. package/dist/mdx/Steps.astro +88 -0
  52. package/dist/mdx/TabItem.astro +21 -0
  53. package/dist/mdx/Tabs.astro +272 -0
  54. package/dist/mdx/rehype-file-tree.d.ts +14 -0
  55. package/dist/mdx/rehype-file-tree.d.ts.map +1 -0
  56. package/dist/mdx/rehype-file-tree.js +205 -0
  57. package/dist/mdx/rehype-file-tree.js.map +1 -0
  58. package/dist/mdx/rehype-steps.d.ts +4 -0
  59. package/dist/mdx/rehype-steps.d.ts.map +1 -0
  60. package/dist/mdx/rehype-steps.js +74 -0
  61. package/dist/mdx/rehype-steps.js.map +1 -0
  62. package/dist/mdx/rehype-tabs.d.ts +19 -0
  63. package/dist/mdx/rehype-tabs.d.ts.map +1 -0
  64. package/dist/mdx/rehype-tabs.js +127 -0
  65. package/dist/mdx/rehype-tabs.js.map +1 -0
  66. package/dist/virtual.d.ts +8 -0
  67. package/package.json +57 -0
@@ -0,0 +1,132 @@
1
+ ---
2
+ import { getIconData, iconToSVG, replaceIDs, validateIconSet } from '@iconify/utils';
3
+
4
+ interface Props {
5
+ name: string;
6
+ title?: string;
7
+ size?: number | string;
8
+ width?: number | string;
9
+ height?: number | string;
10
+ class?: string;
11
+ focusable?: string;
12
+ apiBase?: string;
13
+ }
14
+
15
+ type IconCollection = ReturnType<typeof validateIconSet>;
16
+
17
+ const iconDataCache = new Map<string, Promise<IconCollection>>();
18
+ const preloadedIconDataCache = new WeakMap<object, IconCollection>();
19
+
20
+ function parseIconName(name: string): { prefix: string; icon: string } {
21
+ const separatorIndex = name.indexOf(':');
22
+
23
+ if (separatorIndex <= 0 || separatorIndex === name.length - 1) {
24
+ throw new Error(`Icon name "${name}" must use the "prefix:icon" format.`);
25
+ }
26
+
27
+ return {
28
+ prefix: name.slice(0, separatorIndex),
29
+ icon: name.slice(separatorIndex + 1),
30
+ };
31
+ }
32
+
33
+ async function loadIconCollection(
34
+ prefix: string,
35
+ icon: string,
36
+ apiBase: string,
37
+ ): Promise<IconCollection> {
38
+ const normalizedApiBase = apiBase.replace(/\/$/, '');
39
+ const cacheKey = `${normalizedApiBase}:${prefix}:${icon}`;
40
+ const cached = iconDataCache.get(cacheKey);
41
+
42
+ if (cached) {
43
+ return cached;
44
+ }
45
+
46
+ const request = fetch(
47
+ `${normalizedApiBase}/${encodeURIComponent(prefix)}.json?icons=${encodeURIComponent(icon)}`,
48
+ ).then(async (response) => {
49
+ if (!response.ok) {
50
+ throw new Error(
51
+ `Failed to load Iconify data for "${prefix}:${icon}" from ${normalizedApiBase}.`,
52
+ );
53
+ }
54
+
55
+ return validateIconSet(await response.json());
56
+ });
57
+
58
+ iconDataCache.set(cacheKey, request);
59
+ return request;
60
+ }
61
+
62
+ function getPreloadedIconCollection(prefix: string, icon: string): IconCollection | undefined {
63
+ const locals = Astro.locals as {
64
+ proseflyIconCollections?: Record<string, unknown>;
65
+ };
66
+ const rawCollection = locals.proseflyIconCollections?.[prefix];
67
+
68
+ if (!rawCollection || typeof rawCollection !== 'object') {
69
+ return undefined;
70
+ }
71
+
72
+ const rawCollectionObject = rawCollection as object;
73
+ const cachedCollection = preloadedIconDataCache.get(rawCollectionObject);
74
+
75
+ if (cachedCollection) {
76
+ return getIconData(cachedCollection, icon) ? cachedCollection : undefined;
77
+ }
78
+
79
+ const collection = validateIconSet(rawCollection);
80
+
81
+ if (!collection) {
82
+ return undefined;
83
+ }
84
+
85
+ preloadedIconDataCache.set(rawCollectionObject, collection);
86
+
87
+ return getIconData(collection, icon) ? collection : undefined;
88
+ }
89
+
90
+ const {
91
+ name,
92
+ title,
93
+ size,
94
+ width = size ?? '1em',
95
+ height = size ?? '1em',
96
+ class: className,
97
+ focusable = 'false',
98
+ apiBase = 'https://api.iconify.design',
99
+ ...attributes
100
+ } = Astro.props;
101
+ const { prefix, icon } = parseIconName(name);
102
+ const collection =
103
+ getPreloadedIconCollection(prefix, icon) ?? (await loadIconCollection(prefix, icon, apiBase));
104
+ const iconData = getIconData(collection, icon);
105
+
106
+ if (!iconData) {
107
+ throw new Error(`Icon "${name}" was not found in the Iconify API response.`);
108
+ }
109
+
110
+ const renderedIcon = iconToSVG(iconData, { height, width });
111
+ const body = replaceIDs(renderedIcon.body);
112
+ const svgAttributes = {
113
+ ...renderedIcon.attributes,
114
+ ...attributes,
115
+ 'aria-hidden': title ? undefined : ('true' as const),
116
+ focusable,
117
+ role: title ? ('img' as const) : undefined,
118
+ };
119
+ ---
120
+
121
+ <svg class:list={['pl-icon', className]} {...svgAttributes}>
122
+ {title && <title>{title}</title>}
123
+ <Fragment set:html={body} />
124
+ </svg>
125
+
126
+ <style>
127
+ .pl-icon {
128
+ display: inline-block;
129
+ flex-shrink: 0;
130
+ vertical-align: -0.125em;
131
+ }
132
+ </style>
@@ -0,0 +1,88 @@
1
+ ---
2
+ import { processSteps } from './rehype-steps';
3
+
4
+ const content = await Astro.slots.render('default');
5
+ const { html } = processSteps(content);
6
+ ---
7
+
8
+ <Fragment set:html={html} />
9
+
10
+ <style is:global>
11
+ .pl-steps {
12
+ --pl-steps-bullet-size: 1.75rem;
13
+ --pl-steps-bullet-margin: 0.375rem;
14
+ --pl-steps-line: var(--pl-border-subtle, #e5e7eb);
15
+ --pl-steps-marker-background: var(--pl-surface, #f4f6f8);
16
+ --pl-steps-marker-foreground: var(--pl-text, #344054);
17
+ counter-reset: pl-steps-counter var(--pl-steps-start, 0);
18
+ list-style: none;
19
+ margin-block: 1.5rem;
20
+ padding-inline-start: 0;
21
+ }
22
+
23
+ .pl-steps > li {
24
+ counter-increment: pl-steps-counter;
25
+ min-height: calc(
26
+ var(--pl-steps-bullet-size) + var(--pl-steps-bullet-margin)
27
+ );
28
+ padding-block-end: 1px;
29
+ padding-inline-start: calc(var(--pl-steps-bullet-size) + 1rem);
30
+ position: relative;
31
+ }
32
+
33
+ .pl-steps > li + li {
34
+ margin-block-start: 0;
35
+ }
36
+
37
+ .pl-steps > li::before {
38
+ background: var(--pl-steps-marker-background);
39
+ border-radius: 999px;
40
+ color: var(--pl-steps-marker-foreground);
41
+ content: counter(pl-steps-counter);
42
+ font-size: 0.8125rem;
43
+ font-weight: 600;
44
+ height: var(--pl-steps-bullet-size);
45
+ inset-block-start: 0;
46
+ inset-inline-start: 0;
47
+ line-height: var(--pl-steps-bullet-size);
48
+ position: absolute;
49
+ text-align: center;
50
+ width: var(--pl-steps-bullet-size);
51
+ z-index: 1;
52
+ }
53
+
54
+ .pl-steps > li::after {
55
+ background: var(--pl-steps-line);
56
+ bottom: var(--pl-steps-bullet-margin);
57
+ content: "";
58
+ inset-inline-start: calc((var(--pl-steps-bullet-size) - 1px) / 2);
59
+ position: absolute;
60
+ top: calc(var(--pl-steps-bullet-size) + var(--pl-steps-bullet-margin));
61
+ width: 1px;
62
+ }
63
+
64
+ .pl-steps > li:last-child::after {
65
+ display: none;
66
+ }
67
+
68
+ .pl-steps > li > :first-child {
69
+ --pl-steps-first-line-height: 1.75em;
70
+ --pl-steps-shift-y: calc(
71
+ 0.5 *
72
+ (var(--pl-steps-bullet-size) - var(--pl-steps-first-line-height))
73
+ );
74
+ margin-block-start: 0;
75
+ margin-bottom: var(--pl-steps-shift-y);
76
+ transform: translateY(var(--pl-steps-shift-y));
77
+ }
78
+
79
+ .pl-steps > li > :first-child:where(h1, h2, h3, h4, h5, h6) {
80
+ --pl-steps-first-line-height: 1.25em;
81
+ }
82
+
83
+ @supports (--prop: 1lh) {
84
+ .pl-steps > li > :first-child {
85
+ --pl-steps-first-line-height: 1lh;
86
+ }
87
+ }
88
+ </style>
@@ -0,0 +1,21 @@
1
+ ---
2
+ import { TabItemTagName } from './rehype-tabs';
3
+
4
+ interface Props {
5
+ icon?: string;
6
+ label: string;
7
+ value?: string;
8
+ }
9
+
10
+ const { icon, label, value } = Astro.props;
11
+
12
+ if (!label) {
13
+ throw new Error('Missing prop `label` on `<TabItem>`.');
14
+ }
15
+
16
+ const TabItemTag = TabItemTagName;
17
+ ---
18
+
19
+ <TabItemTag data-icon={icon} data-label={label} data-value={value}>
20
+ <slot />
21
+ </TabItemTag>
@@ -0,0 +1,272 @@
1
+ ---
2
+ import Icon from './Icon.astro';
3
+ import { processPanels } from './rehype-tabs';
4
+
5
+ interface Props {
6
+ copy?: boolean | string;
7
+ defaultValue?: string;
8
+ syncKey?: string;
9
+ }
10
+
11
+ const { copy, defaultValue, syncKey } = Astro.props;
12
+ const hasCopy = copy === true || copy === 'true' || copy === '';
13
+ const tabsId = `pl-tabs-${globalThis.crypto.randomUUID()}`;
14
+ const panelHtml = await Astro.slots.render('default');
15
+ const { activeIndex, html, panels } = processPanels(panelHtml, {
16
+ defaultValue,
17
+ idPrefix: tabsId,
18
+ });
19
+ const hasIcons = panels.some((panel) => panel.icon);
20
+ const activePanel = panels[activeIndex] ?? panels[0];
21
+ ---
22
+
23
+ <section class="pl-tabs" data-pl-tabs data-sync-key={syncKey}>
24
+ <div class="pl-tabs__header">
25
+ {
26
+ hasIcons && (
27
+ <div
28
+ aria-hidden="true"
29
+ class="pl-tabs__active-icons"
30
+ hidden={!activePanel?.icon}
31
+ >
32
+ {panels.map((panel, index) => (
33
+ panel.icon && (
34
+ <span
35
+ class="pl-tabs__active-icon"
36
+ data-pl-tabs-icon-for={panel.panelId}
37
+ hidden={index !== activeIndex}
38
+ >
39
+ <Icon name={panel.icon} />
40
+ </span>
41
+ )
42
+ ))}
43
+ </div>
44
+ )
45
+ }
46
+ <div aria-label="Tabs" class="pl-tabs__list" role="tablist">
47
+ {
48
+ panels.map((panel, index) => (
49
+ <button
50
+ aria-controls={panel.panelId}
51
+ aria-selected={index === activeIndex ? 'true' : 'false'}
52
+ class="pl-tabs__trigger"
53
+ data-pl-tabs-trigger
54
+ data-tab-label={panel.label}
55
+ data-tab-panel={panel.panelId}
56
+ data-tab-value={panel.value}
57
+ id={panel.tabId}
58
+ role="tab"
59
+ tabindex={index === activeIndex ? '0' : '-1'}
60
+ type="button"
61
+ >
62
+ {panel.label}
63
+ </button>
64
+ ))
65
+ }
66
+ </div>
67
+ {
68
+ hasCopy && (
69
+ <button
70
+ aria-label="Copy code"
71
+ class="pl-tabs__copy"
72
+ data-pl-tabs-copy
73
+ title="Copy code"
74
+ type="button"
75
+ >
76
+ <span class="pl-tabs__copy-icon" data-pl-tabs-copy-icon="copy">
77
+ <Icon name="lucide:copy" />
78
+ </span>
79
+ <span class="pl-tabs__copy-icon" data-pl-tabs-copy-icon="check" hidden>
80
+ <Icon name="lucide:check" />
81
+ </span>
82
+ </button>
83
+ )
84
+ }
85
+ </div>
86
+ <div class="pl-tabs__panels" set:html={html} />
87
+ </section>
88
+
89
+ <script>
90
+ import '../client/tabs';
91
+ </script>
92
+
93
+ <style is:global>
94
+ .pl-tabs {
95
+ background: var(--pl-surface, #f4f6f8);
96
+ border: 1px solid var(--pl-border-subtle, #e5e7eb);
97
+ border-radius: min(var(--pl-radius-lg, 0.75rem), 1rem);
98
+ margin-block: 1.25rem;
99
+ overflow: hidden;
100
+ }
101
+
102
+ .pl-tabs__header {
103
+ align-items: center;
104
+ background: var(--pl-surface, #f4f6f8);
105
+ border-bottom: 1px solid var(--pl-border-subtle, #e5e7eb);
106
+ display: flex;
107
+ gap: 0.625rem;
108
+ padding: 0.5rem 1rem;
109
+ }
110
+
111
+ .pl-tabs__active-icons {
112
+ align-items: center;
113
+ color: var(--pl-text, #344054);
114
+ display: inline-grid;
115
+ flex: 0 0 auto;
116
+ justify-content: center;
117
+ line-height: 0;
118
+ }
119
+
120
+ .pl-tabs__active-icons[hidden],
121
+ .pl-tabs__active-icon[hidden],
122
+ .pl-tabs__copy-icon[hidden],
123
+ .pl-tabs__panel[hidden] {
124
+ display: none;
125
+ }
126
+
127
+ .pl-tabs__active-icon {
128
+ align-items: center;
129
+ display: inline-flex;
130
+ height: 1rem;
131
+ justify-content: center;
132
+ line-height: 0;
133
+ width: 1rem;
134
+ }
135
+
136
+ .pl-tabs__active-icon > .pl-icon {
137
+ display: block;
138
+ height: 1rem;
139
+ width: 1rem;
140
+ }
141
+
142
+ .pl-tabs__list {
143
+ align-items: center;
144
+ background: transparent;
145
+ display: inline-flex;
146
+ flex: 1 1 auto;
147
+ gap: 0.25rem;
148
+ max-width: 100%;
149
+ min-width: 0;
150
+ overflow-x: auto;
151
+ padding: 0;
152
+ scrollbar-width: none;
153
+ }
154
+
155
+ .pl-tabs__list::-webkit-scrollbar {
156
+ display: none;
157
+ }
158
+
159
+ .pl-tabs__trigger {
160
+ appearance: none;
161
+ background: transparent;
162
+ border: 1px solid transparent;
163
+ border-radius: var(--pl-radius-md, 0.5rem);
164
+ color: var(--pl-text-muted, #667085);
165
+ cursor: pointer;
166
+ flex: 0 0 auto;
167
+ font: inherit;
168
+ font-size: 0.875rem;
169
+ font-weight: 500;
170
+ line-height: 1.25rem;
171
+ padding: 0.25rem 0.5rem;
172
+ transition:
173
+ background-color 160ms ease,
174
+ border-color 160ms ease,
175
+ box-shadow 160ms ease,
176
+ color 160ms ease;
177
+ }
178
+
179
+ .pl-tabs__trigger:hover {
180
+ color: var(--pl-text-strong, var(--pl-text, #182230));
181
+ }
182
+
183
+ .pl-tabs__trigger:focus-visible {
184
+ outline: 2px solid var(--pl-accent, #4f46e5);
185
+ outline-offset: 2px;
186
+ }
187
+
188
+ .pl-tabs__trigger[aria-selected='true'] {
189
+ background: var(--pl-background, #ffffff);
190
+ border-color: var(--pl-border-muted, #d0d5dd);
191
+ box-shadow: none;
192
+ color: var(--pl-text-strong, var(--pl-text, #182230));
193
+ }
194
+
195
+ .pl-tabs__copy {
196
+ align-items: center;
197
+ appearance: none;
198
+ background: transparent;
199
+ border: 1px solid transparent;
200
+ border-radius: var(--pl-radius-md, 0.5rem);
201
+ color: var(--pl-text-muted, #667085);
202
+ cursor: pointer;
203
+ display: inline-flex;
204
+ flex: 0 0 auto;
205
+ height: 1.875rem;
206
+ justify-content: center;
207
+ margin-inline-start: auto;
208
+ padding: 0;
209
+ transition:
210
+ background-color 160ms ease,
211
+ border-color 160ms ease,
212
+ color 160ms ease;
213
+ width: 1.875rem;
214
+ }
215
+
216
+ .pl-tabs__copy:hover {
217
+ background: var(--pl-background, #ffffff);
218
+ border-color: var(--pl-border-muted, #d0d5dd);
219
+ color: var(--pl-text-strong, var(--pl-text, #182230));
220
+ }
221
+
222
+ .pl-tabs__copy:focus-visible {
223
+ outline: 2px solid var(--pl-accent, #4f46e5);
224
+ outline-offset: 2px;
225
+ }
226
+
227
+ .pl-tabs__copy-icon {
228
+ align-items: center;
229
+ display: inline-flex;
230
+ height: 1rem;
231
+ justify-content: center;
232
+ line-height: 0;
233
+ width: 1rem;
234
+ }
235
+
236
+ .pl-tabs__copy-icon > .pl-icon {
237
+ display: block;
238
+ height: 1rem;
239
+ width: 1rem;
240
+ }
241
+
242
+ .pl-tabs__panels {
243
+ background: var(--pl-background, #fff);
244
+ padding: 1rem;
245
+ }
246
+
247
+ .pl-tabs__panel {
248
+ color: var(--pl-text, #344054);
249
+ }
250
+
251
+ .pl-tabs__panel:focus-visible {
252
+ border-radius: var(--pl-radius-md, 0.5rem);
253
+ outline: 2px solid var(--pl-accent, #4f46e5);
254
+ outline-offset: 2px;
255
+ }
256
+
257
+ .pl-tabs__panel > :first-child {
258
+ margin-block-start: 0;
259
+ }
260
+
261
+ .pl-tabs__panel > :last-child {
262
+ margin-block-end: 0;
263
+ }
264
+
265
+ .pl-tabs__panel pre {
266
+ margin: 0;
267
+ padding: 0;
268
+ background: transparent;
269
+ border: none;
270
+ border-radius: 0;
271
+ }
272
+ </style>
@@ -0,0 +1,14 @@
1
+ type FileTreeKind = 'directory' | 'code' | 'config' | 'file' | 'image' | 'style' | 'text';
2
+ export interface FileTreeEntry {
3
+ children: FileTreeEntry[];
4
+ commentHtml: string;
5
+ highlighted: boolean;
6
+ isDirectory: boolean;
7
+ isPlaceholder: boolean;
8
+ kind: FileTreeKind;
9
+ nameHtml: string;
10
+ nameText: string;
11
+ }
12
+ export declare function parseFileTree(html: string | undefined): FileTreeEntry[];
13
+ export {};
14
+ //# sourceMappingURL=rehype-file-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rehype-file-tree.d.ts","sourceRoot":"","sources":["../../src/mdx/rehype-file-tree.ts"],"names":[],"mappings":"AAKA,KAAK,YAAY,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1F,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAiCD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,EAAE,CAWvE"}