@dogsbay/ui 0.2.0-beta.6 → 0.2.0-beta.61
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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dogsbay/ui",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.61",
|
|
4
4
|
"description": "Accessible UI components for Astro, inspired by Base UI and shadcn",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
"tailwind-variants": "^0.3.0"
|
|
11
11
|
},
|
|
12
12
|
"peerDependencies": {
|
|
13
|
-
"astro": "^5.0.0 || ^6.0.0",
|
|
14
|
-
"@dogsbay/primitives": "0.2.0-beta.
|
|
15
|
-
"@dogsbay/elements": "0.2.0-beta.
|
|
16
|
-
"@dogsbay/icons": "0.2.0-beta.
|
|
13
|
+
"astro": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
14
|
+
"@dogsbay/primitives": "0.2.0-beta.61",
|
|
15
|
+
"@dogsbay/elements": "0.2.0-beta.61",
|
|
16
|
+
"@dogsbay/icons": "0.2.0-beta.61"
|
|
17
17
|
},
|
|
18
18
|
"optionalDependencies": {
|
|
19
19
|
"shiki": "^4.0.0",
|
|
@@ -126,7 +126,7 @@ function inlineToHtml(nodes: InlineNode[]): string {
|
|
|
126
126
|
case "code": return `<code>${n.text || ""}</code>`;
|
|
127
127
|
case "highlight": return `<mark>${inlineToHtml((n.children || []) as InlineNode[])}</mark>`;
|
|
128
128
|
case "link": return `<a href="${n.href || ""}">${inlineToHtml((n.children || []) as InlineNode[])}</a>`;
|
|
129
|
-
case "image": return `<img src="${n.src
|
|
129
|
+
case "image": return `<img src="${withBaseUrl(n.src as string | undefined)}" alt="${n.alt || ""}">`;
|
|
130
130
|
case "kbd": return (n.keys as string[] || []).map((k: string) => `<kbd>${k}</kbd>`).join("+");
|
|
131
131
|
case "html-inline": return String(n.html || "");
|
|
132
132
|
case "break": return "<br>";
|
|
@@ -142,6 +142,26 @@ function resolveImage(src: string): ImageMetadata | undefined {
|
|
|
142
142
|
const clean = normalized.replace(/#.*$/, "");
|
|
143
143
|
return images[clean];
|
|
144
144
|
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Prefix Astro's `base` config (= urlBase from site.url's path)
|
|
148
|
+
* onto absolute image paths so they resolve on subpath-mounted
|
|
149
|
+
* deploys. Skip for external / data: / already-prefixed URLs.
|
|
150
|
+
* See plans/content-assets-folder.md.
|
|
151
|
+
*/
|
|
152
|
+
function withBaseUrl(src: string | undefined): string {
|
|
153
|
+
if (!src) return "";
|
|
154
|
+
const baseUrl = import.meta.env.BASE_URL.replace(/\/$/, "");
|
|
155
|
+
if (
|
|
156
|
+
!baseUrl ||
|
|
157
|
+
!src.startsWith("/") ||
|
|
158
|
+
src.startsWith(`${baseUrl}/`) ||
|
|
159
|
+
/^(?:[a-z]+:|\/\/)/i.test(src)
|
|
160
|
+
) {
|
|
161
|
+
return src;
|
|
162
|
+
}
|
|
163
|
+
return `${baseUrl}${src}`;
|
|
164
|
+
}
|
|
145
165
|
---
|
|
146
166
|
|
|
147
167
|
{/* ── Prose ──────────────────────────────────────────────── */}
|
|
@@ -296,11 +316,14 @@ function resolveImage(src: string): ImageMetadata | undefined {
|
|
|
296
316
|
{node.type === "hr" && <hr class="my-8 border-border" />}
|
|
297
317
|
{node.type === "html" && (() => {
|
|
298
318
|
let html = node.html || "";
|
|
299
|
-
if (html.includes("<img")
|
|
319
|
+
if (html.includes("<img")) {
|
|
300
320
|
html = html.replace(/src="([^"]+)"/g, (_match: string, src: string) => {
|
|
301
321
|
const resolved = resolveImage(src);
|
|
302
322
|
if (resolved) return `src="${resolved.src}" width="${resolved.width}" height="${resolved.height}"`;
|
|
303
|
-
|
|
323
|
+
// Not in the glob (SVG / PDF / build-without-optimization) —
|
|
324
|
+
// prefix BASE_URL so absolute paths resolve on subpath deploys.
|
|
325
|
+
const prefixed = withBaseUrl(src);
|
|
326
|
+
return prefixed === src ? _match : `src="${prefixed}"`;
|
|
304
327
|
});
|
|
305
328
|
}
|
|
306
329
|
return <Fragment set:html={html} />;
|
|
@@ -47,6 +47,24 @@ function resolveImage(src: string): ImageMetadata | undefined {
|
|
|
47
47
|
|
|
48
48
|
const resolvedImg = node.type === "image" ? resolveImage(node.src as string || "") : undefined;
|
|
49
49
|
|
|
50
|
+
// When the image isn't in the glob map (SVG, PDF, or built with
|
|
51
|
+
// imageOptimization: false), the `<img src={node.src}>` fallback
|
|
52
|
+
// fires below. Prefix Astro's `base` for absolute paths so they
|
|
53
|
+
// resolve on subpath-mounted deploys. See plans/content-assets-folder.md.
|
|
54
|
+
function withBaseUrl(src: string | undefined): string {
|
|
55
|
+
if (!src) return "";
|
|
56
|
+
const baseUrl = import.meta.env.BASE_URL.replace(/\/$/, "");
|
|
57
|
+
if (
|
|
58
|
+
!baseUrl ||
|
|
59
|
+
!src.startsWith("/") ||
|
|
60
|
+
src.startsWith(`${baseUrl}/`) ||
|
|
61
|
+
/^(?:[a-z]+:|\/\/)/i.test(src)
|
|
62
|
+
) {
|
|
63
|
+
return src;
|
|
64
|
+
}
|
|
65
|
+
return `${baseUrl}${src}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
// Icon resolution
|
|
51
69
|
const iconSvg = node.type === "icon" && icons
|
|
52
70
|
? icons.resolve(node.library || "emoji", node.name, node.variant, node.size) as string | null
|
|
@@ -90,7 +108,7 @@ const isEmojiIcon = node.type === "icon" && (node.library === "emoji" || (iconSv
|
|
|
90
108
|
)}
|
|
91
109
|
{node.type === "image" && !resolvedImg && (
|
|
92
110
|
<img
|
|
93
|
-
src={node.src}
|
|
111
|
+
src={withBaseUrl(node.src as string | undefined)}
|
|
94
112
|
alt={node.alt || ""}
|
|
95
113
|
title={node.title}
|
|
96
114
|
width={node.width}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Icon — render an icon by name from any Iconify pack (defaults
|
|
4
|
+
* to Lucide).
|
|
5
|
+
*
|
|
6
|
+
* Resolves at BUILD time via `@dogsbay/icons`'s registry — the
|
|
7
|
+
* SVG is inlined into the static HTML, so there's no client-side
|
|
8
|
+
* fetch and no FOUC. Tree-shakeable: only icons actually used in
|
|
9
|
+
* pages get inlined, since Astro statically evaluates the
|
|
10
|
+
* resolution per template instance.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
*
|
|
14
|
+
* <Icon name="rocket" /> // bare → Lucide rocket
|
|
15
|
+
* <Icon name="lucide:book-open" /> // explicit pack
|
|
16
|
+
* <Icon name="mdi:home" /> // Material Design Icons
|
|
17
|
+
* <Icon name="simple-icons:github" /> // brand icons
|
|
18
|
+
* <Icon name="rocket" class="size-5 text-primary" />
|
|
19
|
+
*
|
|
20
|
+
* Bare names check the emoji map first (so `rocket` → 🚀
|
|
21
|
+
* matches existing inline-icon behaviour), then fall through to
|
|
22
|
+
* Lucide. When the icon doesn't exist anywhere, the component
|
|
23
|
+
* renders nothing — silent fallback rather than a broken-image
|
|
24
|
+
* box, since icons are decorative and consistent typography is
|
|
25
|
+
* preferable to a placeholder.
|
|
26
|
+
*
|
|
27
|
+
* Accessibility: when `alt` is supplied, the icon is announced
|
|
28
|
+
* to screen readers as a labelled image. Without `alt` (the
|
|
29
|
+
* default), the SVG is `aria-hidden` because most icons in this
|
|
30
|
+
* codebase are paired with adjacent text that already conveys
|
|
31
|
+
* the meaning. Pass `alt=""` explicitly to force the
|
|
32
|
+
* decorative-only state when needed by external tooling.
|
|
33
|
+
*/
|
|
34
|
+
import { resolveIcon } from "@dogsbay/icons";
|
|
35
|
+
|
|
36
|
+
interface Props {
|
|
37
|
+
/**
|
|
38
|
+
* Icon shorthand. Either bare (`rocket`, `book-open`) or
|
|
39
|
+
* namespaced (`pack:name`). See file header for examples.
|
|
40
|
+
*/
|
|
41
|
+
name: string;
|
|
42
|
+
/** Tailwind class string. Defaults to `size-4` for inline-text sizing. */
|
|
43
|
+
class?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Accessible label. Set to a non-empty string for icons that
|
|
46
|
+
* convey information without surrounding text. Leave undefined
|
|
47
|
+
* (or pass empty string) for decorative icons paired with a
|
|
48
|
+
* text label.
|
|
49
|
+
*/
|
|
50
|
+
alt?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { name, class: className = "size-4", alt } = Astro.props;
|
|
54
|
+
const svg = resolveIcon(name);
|
|
55
|
+
const labelled = typeof alt === "string" && alt.length > 0;
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
{
|
|
59
|
+
svg ? (
|
|
60
|
+
svg.startsWith("<") ? (
|
|
61
|
+
<span
|
|
62
|
+
class:list={["inline-flex shrink-0", className]}
|
|
63
|
+
role={labelled ? "img" : undefined}
|
|
64
|
+
aria-label={labelled ? alt : undefined}
|
|
65
|
+
aria-hidden={labelled ? undefined : "true"}
|
|
66
|
+
set:html={svg}
|
|
67
|
+
data-icon={name}
|
|
68
|
+
/>
|
|
69
|
+
) : (
|
|
70
|
+
// Emoji fallback — `svg` is actually a Unicode character
|
|
71
|
+
// when the name resolved against the emoji map (e.g.
|
|
72
|
+
// `rocket` → 🚀). Render it as text so font emoji shows.
|
|
73
|
+
<span
|
|
74
|
+
class:list={["inline-flex shrink-0", className]}
|
|
75
|
+
role={labelled ? "img" : undefined}
|
|
76
|
+
aria-label={labelled ? alt : undefined}
|
|
77
|
+
aria-hidden={labelled ? undefined : "true"}
|
|
78
|
+
data-icon={name}
|
|
79
|
+
>
|
|
80
|
+
{svg}
|
|
81
|
+
</span>
|
|
82
|
+
)
|
|
83
|
+
) : null
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Icon } from "./Icon.astro";
|
package/src/image/Image.astro
CHANGED
|
@@ -47,6 +47,27 @@ const captionText = caption || title;
|
|
|
47
47
|
const w = width || (imageData ? imageData.width : undefined);
|
|
48
48
|
const h = height || (imageData ? imageData.height : undefined);
|
|
49
49
|
|
|
50
|
+
// When `imageData` is unavailable (image not in import.meta.glob —
|
|
51
|
+
// e.g. SVG, PDF, or build-time `imageOptimization: false`), the
|
|
52
|
+
// `<img src={src}>` fallback fires with the raw markdown src. For
|
|
53
|
+
// absolute paths like `/_assets/foo.png`, we need to prefix with
|
|
54
|
+
// Astro's `base` so the URL resolves on subpath-mounted deploys
|
|
55
|
+
// (GH Pages project pages, multi-mount Cloudflare).
|
|
56
|
+
//
|
|
57
|
+
// `<AstroImage>` does this automatically; raw `<img>` does not.
|
|
58
|
+
//
|
|
59
|
+
// Skip prefixing for: external URLs (http/https), data: URIs,
|
|
60
|
+
// and paths that already start with `import.meta.env.BASE_URL`
|
|
61
|
+
// (defensive; shouldn't normally happen).
|
|
62
|
+
const baseUrl = import.meta.env.BASE_URL.replace(/\/$/, "");
|
|
63
|
+
const resolvedSrc =
|
|
64
|
+
baseUrl &&
|
|
65
|
+
src.startsWith("/") &&
|
|
66
|
+
!src.startsWith(`${baseUrl}/`) &&
|
|
67
|
+
!/^(?:[a-z]+:|\/\/)/i.test(src)
|
|
68
|
+
? `${baseUrl}${src}`
|
|
69
|
+
: src;
|
|
70
|
+
|
|
50
71
|
const alignClass = align === "left"
|
|
51
72
|
? "float-left mr-4 mb-2"
|
|
52
73
|
: align === "right"
|
|
@@ -71,7 +92,7 @@ const alignClass = align === "left"
|
|
|
71
92
|
/>
|
|
72
93
|
) : (
|
|
73
94
|
<img
|
|
74
|
-
src={
|
|
95
|
+
src={resolvedSrc}
|
|
75
96
|
alt={alt}
|
|
76
97
|
title={title}
|
|
77
98
|
width={w}
|
|
@@ -102,7 +123,7 @@ const alignClass = align === "left"
|
|
|
102
123
|
/>
|
|
103
124
|
) : (
|
|
104
125
|
<img
|
|
105
|
-
src={
|
|
126
|
+
src={resolvedSrc}
|
|
106
127
|
alt={alt}
|
|
107
128
|
title={title}
|
|
108
129
|
width={w}
|