@getvitops/astro 0.2.0 → 0.3.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.
package/dist/index.d.mts CHANGED
@@ -1,7 +1,6 @@
1
- import { DefaultTreeAdapterTypes } from "parse5";
2
1
  import { Format } from "@getvitops/generator";
3
2
  import { AstroIntegration } from "astro";
4
- import { HTMLAttributes, HTMLTag } from "astro/types";
3
+ export * from "@getvitops/utils";
5
4
 
6
5
  //#region src/integration.d.ts
7
6
  interface GetvitopsFaviconOptions {
@@ -31,122 +30,5 @@ interface GetvitopsOptions {
31
30
  }
32
31
  declare function getvitops(opts?: GetvitopsOptions): AstroIntegration;
33
32
  //#endregion
34
- //#region types.d.ts
35
- /** A translatable value: a plain string, or a locale → string map. */
36
- type Localizable = string | Record<string, string>;
37
- /**
38
- * An HTML attribute bag. `class` may be a string or a class:list-style array
39
- * (falsy entries dropped); everything else is passed through to the element.
40
- */
41
- interface Attrs {
42
- class?: string | (string | false | null | undefined)[];
43
- [attr: string]: unknown;
44
- }
45
- /** An image reference used by image links. */
46
- interface ImageRef {
47
- src: string;
48
- alt?: Localizable;
49
- [key: string]: unknown;
50
- }
51
- /** A link with a text label. */
52
- interface TextLink {
53
- label: Localizable;
54
- href: string;
55
- attrs?: Attrs;
56
- [key: string]: unknown;
57
- }
58
- /** A link whose content is an image. */
59
- interface ImageLink {
60
- image: ImageRef;
61
- href: string;
62
- attrs?: Attrs;
63
- [key: string]: unknown;
64
- }
65
- /** Any link — text or image. */
66
- type Link = TextLink | ImageLink;
67
- /**
68
- * A generic content element wrapper (discriminated by `type: 'element'`),
69
- * carrying a payload `T` rendered as `Tag`.
70
- */
71
- interface Elmnt<T = unknown, Tag extends HTMLTag = 'div'> {
72
- type: 'element';
73
- tag?: Tag;
74
- attrs?: Attrs;
75
- content?: T;
76
- [key: string]: unknown;
77
- }
78
- /**
79
- * A renderable node tree — a text string (Localizable) or an element with
80
- * optional attrs and either `text` shorthand or `children`.
81
- */
82
- type ContentNode = string | {
83
- tag: string;
84
- attrs?: Attrs;
85
- text?: Localizable;
86
- children?: ContentNode[];
87
- };
88
- //#endregion
89
- //#region src/html.d.ts
90
- type ChildNode = DefaultTreeAdapterTypes.ChildNode;
91
- declare function parseRenderedSlots(html: string): ChildNode[];
92
- declare function getAttribute(node: ChildNode, attrName: string): string | null;
93
- declare function serialize(nodes: ChildNode | ChildNode[]): string[];
94
- /**
95
- * Style value types that can be combined. StyleObject is structurally
96
- * compatible with astroHTML.JSX.CSSProperties (camelCase or --custom-prop keys).
97
- */
98
- type StyleObject = Record<string, string | number | null | undefined>;
99
- type StyleValue = string | StyleObject | null | undefined;
100
- type StyleList = StyleValue | StyleValue[];
101
- /**
102
- * Combine multiple style formats into a single CSS string
103
- * Similar to Astro's class:list but for inline styles
104
- */
105
- declare function styleList(...styles: StyleList[]): string;
106
- /**
107
- * Element descriptor matching the El interface from env.d.ts
108
- */
109
- interface El<Tag extends HTMLTag = HTMLTag> {
110
- text: string;
111
- tag: Tag;
112
- attributes?: HTMLAttributes<Tag>;
113
- }
114
- /**
115
- * Normalize a string or El object into an HTML string for use with set:html
116
- */
117
- declare function toHtml<Tag extends HTMLTag>(content: string | El<Tag>): string;
118
- /**
119
- * Render an array of ContentNodes to an HTML string.
120
- *
121
- * @param nodes - Array of element nodes and/or text strings
122
- * @param resolveText - Optional function to resolve Localizable values.
123
- * Defaults to returning strings as-is and first locale value for maps.
124
- */
125
- declare function nodesToHtml(nodes: ContentNode[], resolveText?: (value: Localizable) => string): string;
126
- //#endregion
127
- //#region src/types.d.ts
128
- declare function isElmnt<T, Tag extends HTMLTag = 'div'>(item: any): item is Elmnt<T, Tag>;
129
- declare function isLink(item: any): item is Link;
130
- declare function isTextLink(item: any): item is TextLink;
131
- declare function isImageLink(item: any): item is ImageLink;
132
- //#endregion
133
- //#region src/parts.d.ts
134
- /**
135
- * Resolve per-part attrs from a presentation type config.
136
- * For the 'root' part, merges legacy `attrs` with `parts.root` (backward compat).
137
- */
138
- declare function partAttrs(present: {
139
- attrs?: Attrs;
140
- parts?: Record<string, Attrs>;
141
- } | undefined, part: string): Attrs;
142
- //#endregion
143
- //#region src/i18n.d.ts
144
- /**
145
- * Resolve a Localizable value to a string for the given locale.
146
- * If the value is a plain string, it is returned as-is (treated as defaultLocale).
147
- * If it is a locale map, the best match is returned.
148
- */
149
- declare function t(value: Localizable | undefined, locale: string, defaultLocale: string): string;
150
- //#endregion
151
- export { Attrs, ChildNode, ContentNode, El, Elmnt, type GetvitopsCssOptions, type GetvitopsFaviconOptions, type GetvitopsOptions, ImageLink, ImageRef, Link, Localizable, StyleList, StyleValue, TextLink, getvitops as default, getAttribute, isElmnt, isImageLink, isLink, isTextLink, nodesToHtml, parseRenderedSlots, partAttrs, serialize, styleList, t, toHtml };
33
+ export { type GetvitopsCssOptions, type GetvitopsFaviconOptions, type GetvitopsOptions, getvitops as default };
152
34
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/integration.ts","../types.ts","../src/html.ts","../src/types.ts","../src/parts.ts","../src/i18n.ts"],"mappings":";;;;;;UAuBiB,uBAAA;;EAEf,MAAA;EACA,YAAA;EAHe;EAKf,IAAA;;EAEA,UAAA;EACA,eAAA;AAAA;AAAA,UAGe,mBAAA;EAJf;EAMA,KAAA;EALe;EAOf,MAAA,GAAS,MAAM;EAJA;EAMf,GAAA;AAAA;AAAA,UAGe,gBAAA;EACf,OAAA,GAAU,uBAAA;EANV;EAQA,aAAA;EANA;EAQA,GAAA,GAAM,mBAAmB;AAAA;AAAA,iBA0BH,SAAA,CAAU,IAAA,GAAM,gBAAA,GAAwB,gBAAgB;;;;KC/DpE,WAAA,YAAuB,MAAM;;;;;UAMxB,KAAA;EACf,KAAA;EAAA,CACC,IAAY;AAAA;;UAIE,QAAA;EACf,GAAA;EACA,GAAA,GAAM,WAAW;EAAA,CAChB,GAAA;AAAA;;UAIc,QAAA;EACf,KAAA,EAAO,WAAA;EACP,IAAA;EACA,KAAA,GAAQ,KAAK;EAAA,CACZ,GAAA;AAAA;;UAIc,SAAA;EACf,KAAA,EAAO,QAAA;EACP,IAAA;EACA,KAAA,GAAQ,KAAK;EAAA,CACZ,GAAA;AAAA;;KAIS,IAAA,GAAO,QAAA,GAAW,SAAS;;;;;UAMtB,KAAA,0BAA+B,OAAA;EAC9C,IAAA;EACA,GAAA,GAAM,GAAA;EACN,KAAA,GAAQ,KAAA;EACR,OAAA,GAAU,CAAA;EAAA,CACT,GAAA;AAAA;;;AA9CsC;AAMzC;KA+CY,WAAA;EAGN,GAAA;EACA,KAAA,GAAQ,KAAA;EACR,IAAA,GAAO,WAAA;EACP,QAAA,GAAW,WAAA;AAAA;;;KClEL,SAAA,GAAY,uBAAA,CAAwB,SAAS;AAAA,iBAEzC,kBAAA,CAAmB,IAAA,WAAe,SAAS;AAAA,iBAK3C,YAAA,CAAa,IAAA,EAAM,SAAS,EAAE,QAAA;AAAA,iBAQ9B,SAAA,CAAU,KAAA,EAAO,SAAA,GAAY,SAAS;;;;;KAUjD,WAAA,GAAc,MAAM;AAAA,KACb,UAAA,YAAsB,WAAW;AAAA,KACjC,SAAA,GAAY,UAAA,GAAa,UAAU;;;AFA9B;AAGjB;iBEiBgB,SAAA,IAAa,MAAA,EAAQ,SAAS;;;;UAuB7B,EAAA,aAAe,OAAA,GAAU,OAAA;EACxC,IAAA;EACA,GAAA,EAAK,GAAA;EACL,UAAA,GAAa,cAAA,CAAe,GAAA;AAAA;AFlC9B;;;AAAA,iBE2EgB,MAAA,aAAmB,OAAA,EAAS,OAAA,WAAkB,EAAA,CAAG,GAAA;;;;;;;AFtEtC;iBEwJX,WAAA,CACd,KAAA,EAAO,WAAA,IACP,WAAA,IAAc,KAAA,EAAO,WAAW;;;iBCvMlB,OAAA,gBAAuB,OAAA,UAAiB,IAAA,QAAY,IAAA,IAAQ,KAAA,CAAM,CAAA,EAAG,GAAA;AAAA,iBAIrE,MAAA,CAAO,IAAA,QAAY,IAAA,IAAQ,IAAI;AAAA,iBAI/B,UAAA,CAAW,IAAA,QAAY,IAAA,IAAQ,QAAQ;AAAA,iBAIvC,WAAA,CAAY,IAAA,QAAY,IAAA,IAAQ,SAAS;;;;;;;iBCTzC,SAAA,CACd,OAAA;EAAW,KAAA,GAAQ,KAAA;EAAO,KAAA,GAAQ,MAAA,SAAe,KAAA;AAAA,eACjD,IAAA,WACC,KAAA;;;;;;;;iBCFa,CAAA,CACd,KAAA,EAAO,WAAW,cAClB,MAAA,UACA,aAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/integration.ts"],"mappings":";;;;;UAuBiB,uBAAA;;EAEf,MAAA;EACA,YAAA;EAHe;EAKf,IAAA;;EAEA,UAAA;EACA,eAAA;AAAA;AAAA,UAGe,mBAAA;EAJf;EAMA,KAAA;EALe;EAOf,MAAA,GAAS,MAAM;EAJA;EAMf,GAAA;AAAA;AAAA,UAGe,gBAAA;EACf,OAAA,GAAU,uBAAA;EANV;EAQA,aAAA;EANA;EAQA,GAAA,GAAM,mBAAmB;AAAA;AAAA,iBA0BH,SAAA,CAAU,IAAA,GAAM,gBAAA,GAAwB,gBAAgB"}
package/dist/index.mjs CHANGED
@@ -4,7 +4,8 @@ import { fileURLToPath } from "node:url";
4
4
  import { faviconLinks, generateFavicons, writeFaviconManifest } from "@getvitops/utils";
5
5
  import vitops from "@getvitops/vite";
6
6
  import tailwindcss from "@tailwindcss/vite";
7
- import { parseFragment, serialize as serialize$1 } from "parse5";
7
+
8
+ export * from "@getvitops/utils"
8
9
 
9
10
  //#region src/integration.ts
10
11
  /**
@@ -99,184 +100,5 @@ function getvitops(opts = {}) {
99
100
  }
100
101
 
101
102
  //#endregion
102
- //#region src/html.ts
103
- function parseRenderedSlots(html) {
104
- return parseFragment(html).childNodes;
105
- }
106
- function getAttribute(node, attrName) {
107
- if ("attrs" in node) {
108
- const attr = node.attrs.find((a) => a.name === attrName);
109
- return attr ? attr.value : null;
110
- }
111
- return null;
112
- }
113
- function serialize(nodes) {
114
- return (Array.isArray(nodes) ? nodes : [nodes]).map((node) => serialize$1(node));
115
- }
116
- /**
117
- * Convert camelCase to kebab-case, preserving CSS custom properties (--var-name)
118
- */
119
- function toKebabCase(str) {
120
- if (str.startsWith("--")) return str;
121
- return str.replace(/([A-Z])/g, "-$1").toLowerCase();
122
- }
123
- function styleObjectToString(obj) {
124
- return Object.entries(obj).filter(([, value]) => value != null).map(([key, value]) => `${toKebabCase(key)}: ${value}`).join("; ");
125
- }
126
- /**
127
- * Combine multiple style formats into a single CSS string
128
- * Similar to Astro's class:list but for inline styles
129
- */
130
- function styleList(...styles) {
131
- const result = [];
132
- const process = (item) => {
133
- if (item == null) return;
134
- if (Array.isArray(item)) item.forEach(process);
135
- else if (typeof item === "string") {
136
- const trimmed = item.trim();
137
- if (trimmed) result.push(trimmed);
138
- } else {
139
- const str = styleObjectToString(item);
140
- if (str) result.push(str);
141
- }
142
- };
143
- styles.forEach(process);
144
- return result.join("; ");
145
- }
146
- /**
147
- * Type guard to check if a value is an El object
148
- */
149
- function isEl(value) {
150
- return typeof value === "object" && value !== null && "tag" in value && "text" in value;
151
- }
152
- /**
153
- * Convert HTML attributes object to a string for use in markup
154
- */
155
- function attributesToString(attrs) {
156
- const parts = [];
157
- for (const [key, value] of Object.entries(attrs)) {
158
- if (value == null || value === false) continue;
159
- if (value === true) parts.push(key);
160
- else if (key === "style" && typeof value === "object") parts.push(`style="${styleList(value)}"`);
161
- else {
162
- const escaped = (Array.isArray(value) ? value.filter((v) => typeof v === "string" && v !== "").join(" ") : typeof value === "object" ? JSON.stringify(value) : String(value)).replace(/"/g, "&quot;");
163
- parts.push(`${key}="${escaped}"`);
164
- }
165
- }
166
- return parts.join(" ");
167
- }
168
- /**
169
- * Normalize a string or El object into an HTML string for use with set:html
170
- */
171
- function toHtml(content) {
172
- if (!isEl(content)) return content;
173
- const { tag, text, attributes } = content;
174
- const attrStr = attributes ? attributesToString(attributes) : "";
175
- const t = String(tag);
176
- return attrStr ? `<${t} ${attrStr}>${text}</${t}>` : `<${t}>${text}</${t}>`;
177
- }
178
- /** Void elements that must not have a closing tag */
179
- const VOID_ELEMENTS = new Set([
180
- "area",
181
- "base",
182
- "br",
183
- "col",
184
- "embed",
185
- "hr",
186
- "img",
187
- "input",
188
- "link",
189
- "meta",
190
- "param",
191
- "source",
192
- "track",
193
- "wbr"
194
- ]);
195
- /** Default text resolver — strings pass through, locale maps return first value */
196
- function defaultResolveText(value) {
197
- if (typeof value === "string") return value;
198
- return Object.values(value)[0] ?? "";
199
- }
200
- /** Escape text content for safe HTML output */
201
- function escapeHtml(text) {
202
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
203
- }
204
- /**
205
- * Render a single ContentNode to an HTML string.
206
- */
207
- function nodeToHtml(node, resolveText) {
208
- if (typeof node === "string") return escapeHtml(resolveText(node));
209
- const { tag, attrs, text, children } = node;
210
- const attrStr = attrs ? attributesToString(attrs) : "";
211
- const open = attrStr ? `<${tag} ${attrStr}>` : `<${tag}>`;
212
- if (VOID_ELEMENTS.has(tag)) return open;
213
- let inner = "";
214
- if (text != null) inner = escapeHtml(resolveText(text));
215
- else if (children?.length) inner = children.map((child) => nodeToHtml(child, resolveText)).join("");
216
- return `${open}${inner}</${tag}>`;
217
- }
218
- /**
219
- * Render an array of ContentNodes to an HTML string.
220
- *
221
- * @param nodes - Array of element nodes and/or text strings
222
- * @param resolveText - Optional function to resolve Localizable values.
223
- * Defaults to returning strings as-is and first locale value for maps.
224
- */
225
- function nodesToHtml(nodes, resolveText = defaultResolveText) {
226
- return nodes.map((node) => nodeToHtml(node, resolveText)).join("");
227
- }
228
-
229
- //#endregion
230
- //#region src/types.ts
231
- function isElmnt(item) {
232
- return item && item.type === "element";
233
- }
234
- function isLink(item) {
235
- return item && ("label" in item || "image" in item) && "href" in item;
236
- }
237
- function isTextLink(item) {
238
- return item && "label" in item && "href" in item;
239
- }
240
- function isImageLink(item) {
241
- return item && "image" in item && "href" in item;
242
- }
243
-
244
- //#endregion
245
- //#region src/parts.ts
246
- /**
247
- * Resolve per-part attrs from a presentation type config.
248
- * For the 'root' part, merges legacy `attrs` with `parts.root` (backward compat).
249
- */
250
- function partAttrs(present, part) {
251
- const fromParts = present?.parts?.[part];
252
- if (part === "root") {
253
- const legacy = present?.attrs;
254
- if (!legacy && !fromParts) return {};
255
- const { class: lc, ...lr } = legacy ?? {};
256
- const { class: pc, ...pr } = fromParts ?? {};
257
- const classes = [lc, pc].flat().filter((c) => typeof c === "string" && c !== "");
258
- return {
259
- ...classes.length ? { class: classes } : {},
260
- ...lr,
261
- ...pr
262
- };
263
- }
264
- return fromParts ?? {};
265
- }
266
-
267
- //#endregion
268
- //#region src/i18n.ts
269
- /**
270
- * Resolve a Localizable value to a string for the given locale.
271
- * If the value is a plain string, it is returned as-is (treated as defaultLocale).
272
- * If it is a locale map, the best match is returned.
273
- */
274
- function t(value, locale, defaultLocale) {
275
- if (value == null) return "";
276
- if (typeof value === "string") return value;
277
- return value[locale] ?? value[defaultLocale] ?? "";
278
- }
279
-
280
- //#endregion
281
- export { getvitops as default, getAttribute, isElmnt, isImageLink, isLink, isTextLink, nodesToHtml, parseRenderedSlots, partAttrs, serialize, styleList, t, toHtml };
103
+ export { getvitops as default };
282
104
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["s"],"sources":["../src/integration.ts","../src/html.ts","../src/types.ts","../src/parts.ts","../src/i18n.ts"],"sourcesContent":["/**\n * @getvitops/astro — the Astro integration. One entry wires the design system's\n * `<head>` contributions into a consumer site:\n * - favicons + PWA manifest (generated into public/),\n * - the web-component runtime bundles (copied into public/vitops/),\n * - (opt-in) the design-system CSS, generated + auto-injected (no manual import).\n * The `<Head />` component (shipped alongside) renders the tags, reading resolved\n * config from the `virtual:getvitops/head` module this integration provides.\n */\nimport { cpSync, existsSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Format } from '@getvitops/generator';\nimport {\n type FaviconLink,\n faviconLinks,\n generateFavicons,\n writeFaviconManifest,\n} from '@getvitops/utils';\nimport vitops from '@getvitops/vite';\nimport tailwindcss from '@tailwindcss/vite';\nimport type { AstroIntegration } from 'astro';\n\nexport interface GetvitopsFaviconOptions {\n /** Source SVG or PNG. */\n source: string;\n lowResSource?: string;\n /** App name → enables `site.webmanifest`. */\n name?: string;\n /** PWA theme color (also `<meta name=\"theme-color\">`). */\n themeColor?: string;\n backgroundColor?: string;\n}\n\nexport interface GetvitopsCssOptions {\n /** Path to design-system.json (default 'design-system.json'). */\n input?: string;\n /** Output format (default 'tailwind'). */\n format?: Format;\n /** Directory the generated CSS is written to (default 'src/styles'). */\n out?: string;\n}\n\nexport interface GetvitopsOptions {\n favicon?: GetvitopsFaviconOptions;\n /** Copy + link the web-component bundles (default true). */\n webComponents?: boolean;\n /** Generate + auto-inject the design-system CSS. Off unless provided. */\n css?: GetvitopsCssOptions;\n}\n\ninterface HeadData {\n favicons: boolean;\n faviconLinks: FaviconLink[];\n themeColor: string | null;\n webComponents: boolean;\n wcBase: string;\n}\n\nconst VIRTUAL_ID = 'virtual:getvitops/head';\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`;\n\nfunction virtualHeadPlugin(data: HeadData) {\n return {\n name: '@getvitops/astro:virtual-head',\n resolveId(id: string) {\n return id === VIRTUAL_ID ? RESOLVED_ID : null;\n },\n load(id: string) {\n return id === RESOLVED_ID ? `export default ${JSON.stringify(data)};` : null;\n },\n };\n}\n\nexport default function getvitops(opts: GetvitopsOptions = {}): AstroIntegration {\n return {\n name: '@getvitops/astro',\n hooks: {\n 'astro:config:setup': async ({ config, updateConfig, injectScript, logger }) => {\n const root = fileURLToPath(config.root);\n const publicDir = fileURLToPath(config.publicDir);\n const webComponents = opts.webComponents !== false;\n const hasManifest = !!(opts.favicon?.name && opts.favicon?.themeColor);\n let hasSvg = false;\n\n // 1. Favicons + manifest → public/\n if (opts.favicon) {\n const source = resolve(root, opts.favicon.source);\n hasSvg = source.endsWith('.svg');\n await generateFavicons({\n source,\n outputDir: publicDir,\n ...(opts.favicon.lowResSource\n ? { lowResSource: resolve(root, opts.favicon.lowResSource) }\n : {}),\n });\n if (opts.favicon.name && opts.favicon.themeColor) {\n await writeFaviconManifest(publicDir, {\n name: opts.favicon.name,\n themeColor: opts.favicon.themeColor,\n ...(opts.favicon.backgroundColor\n ? { backgroundColor: opts.favicon.backgroundColor }\n : {}),\n });\n }\n logger.info('generated favicons + manifest → public/');\n }\n\n // 2. Web-component bundles → public/vitops/\n if (webComponents) {\n const corePkg = fileURLToPath(import.meta.resolve('@getvitops/core/package.json'));\n const coreDist = join(dirname(corePkg), 'dist');\n const dest = join(publicDir, 'vitops');\n if (existsSync(coreDist)) {\n for (const f of ['polyfills.js', 'deferred.js', 'elements.js', 'polyfills']) {\n const src = join(coreDist, f);\n if (existsSync(src)) cpSync(src, join(dest, f), { recursive: true });\n }\n logger.info('copied web-component bundles → public/vitops/');\n } else {\n logger.warn(\n `@getvitops/core dist not found (${coreDist}); run build:core. Skipping WC copy.`,\n );\n }\n }\n\n // 3. Virtual head module for <Head/>\n updateConfig({\n vite: {\n plugins: [\n virtualHeadPlugin({\n favicons: !!opts.favicon,\n faviconLinks: opts.favicon ? faviconLinks({ hasSvg, manifest: hasManifest }) : [],\n themeColor: opts.favicon?.themeColor ?? null,\n webComponents,\n wcBase: '/vitops',\n }),\n ],\n },\n });\n\n // 4. CSS — generate + compile + auto-inject (consumer imports nothing).\n if (opts.css) {\n const format: Format = opts.css.format ?? 'tailwind';\n const out = opts.css.out ?? 'src/styles';\n const plugins = [vitops({ input: opts.css.input ?? 'design-system.json', format, out })];\n // tailwindcss() is typed against a newer vite than @getvitops/vite's\n // peer; the Plugin shapes are structurally compatible at runtime\n // (Astro forwards them verbatim), so bridge the two type identities.\n if (format === 'tailwind') plugins.push(...(tailwindcss() as unknown as typeof plugins));\n updateConfig({ vite: { plugins } });\n const cssFile = format === 'tailwind' ? 'tailwind.css' : 'styles.css';\n const cssPath = resolve(root, out, cssFile);\n injectScript('page-ssr', `import ${JSON.stringify(cssPath)};`);\n logger.info(`design-system CSS (${format}) auto-injected from ${out}/${cssFile}`);\n }\n },\n },\n };\n}\n","import { parseFragment, serialize as s, type DefaultTreeAdapterTypes } from 'parse5';\nimport type { HTMLTag, HTMLAttributes } from 'astro/types';\nimport type { Localizable, ContentNode } from '../types.js';\n\nexport type ChildNode = DefaultTreeAdapterTypes.ChildNode;\n\nexport function parseRenderedSlots(html: string): ChildNode[] {\n const document = parseFragment(html);\n return document.childNodes;\n}\n\nexport function getAttribute(node: ChildNode, attrName: string): string | null {\n if ('attrs' in node) {\n const attr = node.attrs.find((a) => a.name === attrName);\n return attr ? attr.value : null;\n }\n return null;\n}\n\nexport function serialize(nodes: ChildNode | ChildNode[]): string[] {\n return (Array.isArray(nodes) ? nodes : [nodes]).map((node) =>\n s(node as DefaultTreeAdapterTypes.ParentNode),\n );\n}\n\n/**\n * Style value types that can be combined. StyleObject is structurally\n * compatible with astroHTML.JSX.CSSProperties (camelCase or --custom-prop keys).\n */\ntype StyleObject = Record<string, string | number | null | undefined>;\nexport type StyleValue = string | StyleObject | null | undefined;\nexport type StyleList = StyleValue | StyleValue[];\n\n/**\n * Convert camelCase to kebab-case, preserving CSS custom properties (--var-name)\n */\nfunction toKebabCase(str: string): string {\n if (str.startsWith('--')) return str;\n return str.replace(/([A-Z])/g, '-$1').toLowerCase();\n}\nfunction styleObjectToString(obj: StyleObject): string {\n return Object.entries(obj)\n .filter(([, value]) => value != null)\n .map(([key, value]) => `${toKebabCase(key)}: ${value}`)\n .join('; ');\n}\n\n/**\n * Combine multiple style formats into a single CSS string\n * Similar to Astro's class:list but for inline styles\n */\nexport function styleList(...styles: StyleList[]): string {\n const result: string[] = [];\n\n const process = (item: StyleList): void => {\n if (item == null) return;\n if (Array.isArray(item)) {\n item.forEach(process);\n } else if (typeof item === 'string') {\n const trimmed = item.trim();\n if (trimmed) result.push(trimmed);\n } else {\n const str = styleObjectToString(item as StyleObject);\n if (str) result.push(str);\n }\n };\n\n styles.forEach(process);\n return result.join('; ');\n}\n\n/**\n * Element descriptor matching the El interface from env.d.ts\n */\nexport interface El<Tag extends HTMLTag = HTMLTag> {\n text: string;\n tag: Tag;\n attributes?: HTMLAttributes<Tag>;\n}\n\n/**\n * Type guard to check if a value is an El object\n */\nfunction isEl(value: string | El): value is El {\n return typeof value === 'object' && value !== null && 'tag' in value && 'text' in value;\n}\n\n/**\n * Convert HTML attributes object to a string for use in markup\n */\nfunction attributesToString(attrs: Record<string, unknown>): string {\n const parts: string[] = [];\n\n for (const [key, value] of Object.entries(attrs)) {\n if (value == null || value === false) continue;\n if (value === true) {\n parts.push(key);\n } else if (key === 'style' && typeof value === 'object') {\n parts.push(`style=\"${styleList(value as StyleValue)}\"`);\n } else {\n // Class:list arrays join like Astro; other objects stringify as JSON\n // (never \"[object Object]\"). Escape double quotes for the attribute.\n const text = Array.isArray(value)\n ? value.filter((v): v is string => typeof v === 'string' && v !== '').join(' ')\n : typeof value === 'object'\n ? JSON.stringify(value)\n : String(value as string | number | boolean);\n const escaped = text.replace(/\"/g, '&quot;');\n parts.push(`${key}=\"${escaped}\"`);\n }\n }\n\n return parts.join(' ');\n}\n\n/**\n * Normalize a string or El object into an HTML string for use with set:html\n */\nexport function toHtml<Tag extends HTMLTag>(content: string | El<Tag>): string {\n if (!isEl(content)) {\n return content;\n }\n\n const { tag, text, attributes } = content;\n const attrStr = attributes ? attributesToString(attributes) : '';\n // HTMLTag is a key type (string | number | symbol to tsc) — stringify for templates.\n const t = String(tag);\n\n return attrStr ? `<${t} ${attrStr}>${text}</${t}>` : `<${t}>${text}</${t}>`;\n}\n\n// ---------------------------------------------------------------------------\n// Node tree renderer — { tag, attrs, children }\n// ---------------------------------------------------------------------------\n\n/** Void elements that must not have a closing tag */\nconst VOID_ELEMENTS = new Set([\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n]);\n\n/** Default text resolver — strings pass through, locale maps return first value */\nfunction defaultResolveText(value: Localizable): string {\n if (typeof value === 'string') return value;\n const values = Object.values(value);\n return values[0] ?? '';\n}\n\n/** Escape text content for safe HTML output */\nfunction escapeHtml(text: string): string {\n return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\n/**\n * Render a single ContentNode to an HTML string.\n */\nfunction nodeToHtml(node: ContentNode, resolveText: (value: Localizable) => string): string {\n // String child → text node\n if (typeof node === 'string') {\n return escapeHtml(resolveText(node));\n }\n\n const { tag, attrs, text, children } = node;\n const attrStr = attrs ? attributesToString(attrs) : '';\n const open = attrStr ? `<${tag} ${attrStr}>` : `<${tag}>`;\n\n if (VOID_ELEMENTS.has(tag)) {\n return open;\n }\n\n // text is shorthand for children: [\"string\"]\n let inner = '';\n if (text != null) {\n inner = escapeHtml(resolveText(text));\n } else if (children?.length) {\n inner = children.map((child) => nodeToHtml(child, resolveText)).join('');\n }\n\n return `${open}${inner}</${tag}>`;\n}\n\n/**\n * Render an array of ContentNodes to an HTML string.\n *\n * @param nodes - Array of element nodes and/or text strings\n * @param resolveText - Optional function to resolve Localizable values.\n * Defaults to returning strings as-is and first locale value for maps.\n */\nexport function nodesToHtml(\n nodes: ContentNode[],\n resolveText: (value: Localizable) => string = defaultResolveText,\n): string {\n return nodes.map((node) => nodeToHtml(node, resolveText)).join('');\n}\n","import type { Elmnt, Link, TextLink, ImageLink } from '../types.js';\nimport type { HTMLTag } from 'astro/types';\n\nexport function isElmnt<T, Tag extends HTMLTag = 'div'>(item: any): item is Elmnt<T, Tag> {\n return item && item.type === 'element';\n}\n\nexport function isLink(item: any): item is Link {\n return item && ('label' in item || 'image' in item) && 'href' in item;\n}\n\nexport function isTextLink(item: any): item is TextLink {\n return item && 'label' in item && 'href' in item;\n}\n\nexport function isImageLink(item: any): item is ImageLink {\n return item && 'image' in item && 'href' in item;\n}\n","import type { Attrs } from '../types.js';\n\n/**\n * Resolve per-part attrs from a presentation type config.\n * For the 'root' part, merges legacy `attrs` with `parts.root` (backward compat).\n */\nexport function partAttrs(\n present: { attrs?: Attrs; parts?: Record<string, Attrs> } | undefined,\n part: string,\n): Attrs {\n const fromParts = present?.parts?.[part];\n if (part === 'root') {\n const legacy = present?.attrs;\n if (!legacy && !fromParts) return {};\n const { class: lc, ...lr } = legacy ?? {};\n const { class: pc, ...pr } = fromParts ?? {};\n // Flatten class:list-style values and keep only real class strings.\n const classes = [lc, pc].flat().filter((c): c is string => typeof c === 'string' && c !== '');\n return {\n ...(classes.length ? { class: classes } : {}),\n ...lr,\n ...pr,\n };\n }\n return fromParts ?? {};\n}\n","import type { Localizable } from '../types.js';\n\n/**\n * Resolve a Localizable value to a string for the given locale.\n * If the value is a plain string, it is returned as-is (treated as defaultLocale).\n * If it is a locale map, the best match is returned.\n */\nexport function t(\n value: Localizable | undefined,\n locale: string,\n defaultLocale: string,\n): string {\n if (value == null) return '';\n if (typeof value === 'string') return value;\n return value[locale] ?? value[defaultLocale] ?? '';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/integration.ts"],"sourcesContent":["/**\n * @getvitops/astro — the Astro integration. One entry wires the design system's\n * `<head>` contributions into a consumer site:\n * - favicons + PWA manifest (generated into public/),\n * - the web-component runtime bundles (copied into public/vitops/),\n * - (opt-in) the design-system CSS, generated + auto-injected (no manual import).\n * The `<Head />` component (shipped alongside) renders the tags, reading resolved\n * config from the `virtual:getvitops/head` module this integration provides.\n */\nimport { cpSync, existsSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Format } from '@getvitops/generator';\nimport {\n type FaviconLink,\n faviconLinks,\n generateFavicons,\n writeFaviconManifest,\n} from '@getvitops/utils';\nimport vitops from '@getvitops/vite';\nimport tailwindcss from '@tailwindcss/vite';\nimport type { AstroIntegration } from 'astro';\n\nexport interface GetvitopsFaviconOptions {\n /** Source SVG or PNG. */\n source: string;\n lowResSource?: string;\n /** App name → enables `site.webmanifest`. */\n name?: string;\n /** PWA theme color (also `<meta name=\"theme-color\">`). */\n themeColor?: string;\n backgroundColor?: string;\n}\n\nexport interface GetvitopsCssOptions {\n /** Path to design-system.json (default 'design-system.json'). */\n input?: string;\n /** Output format (default 'tailwind'). */\n format?: Format;\n /** Directory the generated CSS is written to (default 'src/styles'). */\n out?: string;\n}\n\nexport interface GetvitopsOptions {\n favicon?: GetvitopsFaviconOptions;\n /** Copy + link the web-component bundles (default true). */\n webComponents?: boolean;\n /** Generate + auto-inject the design-system CSS. Off unless provided. */\n css?: GetvitopsCssOptions;\n}\n\ninterface HeadData {\n favicons: boolean;\n faviconLinks: FaviconLink[];\n themeColor: string | null;\n webComponents: boolean;\n wcBase: string;\n}\n\nconst VIRTUAL_ID = 'virtual:getvitops/head';\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`;\n\nfunction virtualHeadPlugin(data: HeadData) {\n return {\n name: '@getvitops/astro:virtual-head',\n resolveId(id: string) {\n return id === VIRTUAL_ID ? RESOLVED_ID : null;\n },\n load(id: string) {\n return id === RESOLVED_ID ? `export default ${JSON.stringify(data)};` : null;\n },\n };\n}\n\nexport default function getvitops(opts: GetvitopsOptions = {}): AstroIntegration {\n return {\n name: '@getvitops/astro',\n hooks: {\n 'astro:config:setup': async ({ config, updateConfig, injectScript, logger }) => {\n const root = fileURLToPath(config.root);\n const publicDir = fileURLToPath(config.publicDir);\n const webComponents = opts.webComponents !== false;\n const hasManifest = !!(opts.favicon?.name && opts.favicon?.themeColor);\n let hasSvg = false;\n\n // 1. Favicons + manifest → public/\n if (opts.favicon) {\n const source = resolve(root, opts.favicon.source);\n hasSvg = source.endsWith('.svg');\n await generateFavicons({\n source,\n outputDir: publicDir,\n ...(opts.favicon.lowResSource\n ? { lowResSource: resolve(root, opts.favicon.lowResSource) }\n : {}),\n });\n if (opts.favicon.name && opts.favicon.themeColor) {\n await writeFaviconManifest(publicDir, {\n name: opts.favicon.name,\n themeColor: opts.favicon.themeColor,\n ...(opts.favicon.backgroundColor\n ? { backgroundColor: opts.favicon.backgroundColor }\n : {}),\n });\n }\n logger.info('generated favicons + manifest → public/');\n }\n\n // 2. Web-component bundles → public/vitops/\n if (webComponents) {\n const corePkg = fileURLToPath(import.meta.resolve('@getvitops/core/package.json'));\n const coreDist = join(dirname(corePkg), 'dist');\n const dest = join(publicDir, 'vitops');\n if (existsSync(coreDist)) {\n for (const f of ['polyfills.js', 'deferred.js', 'elements.js', 'polyfills']) {\n const src = join(coreDist, f);\n if (existsSync(src)) cpSync(src, join(dest, f), { recursive: true });\n }\n logger.info('copied web-component bundles → public/vitops/');\n } else {\n logger.warn(\n `@getvitops/core dist not found (${coreDist}); run build:core. Skipping WC copy.`,\n );\n }\n }\n\n // 3. Virtual head module for <Head/>\n updateConfig({\n vite: {\n plugins: [\n virtualHeadPlugin({\n favicons: !!opts.favicon,\n faviconLinks: opts.favicon ? faviconLinks({ hasSvg, manifest: hasManifest }) : [],\n themeColor: opts.favicon?.themeColor ?? null,\n webComponents,\n wcBase: '/vitops',\n }),\n ],\n },\n });\n\n // 4. CSS — generate + compile + auto-inject (consumer imports nothing).\n if (opts.css) {\n const format: Format = opts.css.format ?? 'tailwind';\n const out = opts.css.out ?? 'src/styles';\n const plugins = [vitops({ input: opts.css.input ?? 'design-system.json', format, out })];\n // tailwindcss() is typed against a newer vite than @getvitops/vite's\n // peer; the Plugin shapes are structurally compatible at runtime\n // (Astro forwards them verbatim), so bridge the two type identities.\n if (format === 'tailwind') plugins.push(...(tailwindcss() as unknown as typeof plugins));\n updateConfig({ vite: { plugins } });\n const cssFile = format === 'tailwind' ? 'tailwind.css' : 'styles.css';\n const cssPath = resolve(root, out, cssFile);\n injectScript('page-ssr', `import ${JSON.stringify(cssPath)};`);\n logger.info(`design-system CSS (${format}) auto-injected from ${out}/${cssFile}`);\n }\n },\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA2DA,MAAM,aAAa;AACnB,MAAM,cAAc,KAAK;AAEzB,SAAS,kBAAkB,MAAgB;CACzC,OAAO;EACL,MAAM;EACN,UAAU,IAAY;GACpB,OAAO,OAAO,aAAa,cAAc;EAC3C;EACA,KAAK,IAAY;GACf,OAAO,OAAO,cAAc,kBAAkB,KAAK,UAAU,IAAI,EAAE,KAAK;EAC1E;CACF;AACF;AAEA,SAAwB,UAAU,OAAyB,CAAC,GAAqB;CAC/E,OAAO;EACL,MAAM;EACN,OAAO,EACL,sBAAsB,OAAO,EAAE,QAAQ,cAAc,cAAc,aAAa;GAC9E,MAAM,OAAO,cAAc,OAAO,IAAI;GACtC,MAAM,YAAY,cAAc,OAAO,SAAS;GAChD,MAAM,gBAAgB,KAAK,kBAAkB;GAC7C,MAAM,cAAc,CAAC,EAAE,KAAK,SAAS,QAAQ,KAAK,SAAS;GAC3D,IAAI,SAAS;GAGb,IAAI,KAAK,SAAS;IAChB,MAAM,SAAS,QAAQ,MAAM,KAAK,QAAQ,MAAM;IAChD,SAAS,OAAO,SAAS,MAAM;IAC/B,MAAM,iBAAiB;KACrB;KACA,WAAW;KACX,GAAI,KAAK,QAAQ,eACb,EAAE,cAAc,QAAQ,MAAM,KAAK,QAAQ,YAAY,EAAE,IACzD,CAAC;IACP,CAAC;IACD,IAAI,KAAK,QAAQ,QAAQ,KAAK,QAAQ,YACpC,MAAM,qBAAqB,WAAW;KACpC,MAAM,KAAK,QAAQ;KACnB,YAAY,KAAK,QAAQ;KACzB,GAAI,KAAK,QAAQ,kBACb,EAAE,iBAAiB,KAAK,QAAQ,gBAAgB,IAChD,CAAC;IACP,CAAC;IAEH,OAAO,KAAK,yCAAyC;GACvD;GAGA,IAAI,eAAe;IAEjB,MAAM,WAAW,KAAK,QADN,cAAc,OAAO,KAAK,QAAQ,8BAA8B,CAC5C,CAAC,GAAG,MAAM;IAC9C,MAAM,OAAO,KAAK,WAAW,QAAQ;IACrC,IAAI,WAAW,QAAQ,GAAG;KACxB,KAAK,MAAM,KAAK;MAAC;MAAgB;MAAe;MAAe;KAAW,GAAG;MAC3E,MAAM,MAAM,KAAK,UAAU,CAAC;MAC5B,IAAI,WAAW,GAAG,GAAG,OAAO,KAAK,KAAK,MAAM,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;KACrE;KACA,OAAO,KAAK,+CAA+C;IAC7D,OACE,OAAO,KACL,mCAAmC,SAAS,qCAC9C;GAEJ;GAGA,aAAa,EACX,MAAM,EACJ,SAAS,CACP,kBAAkB;IAChB,UAAU,CAAC,CAAC,KAAK;IACjB,cAAc,KAAK,UAAU,aAAa;KAAE;KAAQ,UAAU;IAAY,CAAC,IAAI,CAAC;IAChF,YAAY,KAAK,SAAS,cAAc;IACxC;IACA,QAAQ;GACV,CAAC,CACH,EACF,EACF,CAAC;GAGD,IAAI,KAAK,KAAK;IACZ,MAAM,SAAiB,KAAK,IAAI,UAAU;IAC1C,MAAM,MAAM,KAAK,IAAI,OAAO;IAC5B,MAAM,UAAU,CAAC,OAAO;KAAE,OAAO,KAAK,IAAI,SAAS;KAAsB;KAAQ;IAAI,CAAC,CAAC;IAIvF,IAAI,WAAW,YAAY,QAAQ,KAAK,GAAI,YAAY,CAA+B;IACvF,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAClC,MAAM,UAAU,WAAW,aAAa,iBAAiB;IACzD,MAAM,UAAU,QAAQ,MAAM,KAAK,OAAO;IAC1C,aAAa,YAAY,UAAU,KAAK,UAAU,OAAO,EAAE,EAAE;IAC7D,OAAO,KAAK,sBAAsB,OAAO,uBAAuB,IAAI,GAAG,SAAS;GAClF;EACF,EACF;CACF;AACF"}
package/package.json CHANGED
@@ -1,11 +1,18 @@
1
1
  {
2
2
  "name": "@getvitops/astro",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Astro integration for the Vitops design system — favicons/PWA, web-component scripts, and CSS, via a getvitops() integration + <Head /> component.",
5
5
  "license": "MIT",
6
6
  "files": [
7
7
  "dist",
8
8
  "src/components/Head.astro",
9
+ "src/components/Subgrid.astro",
10
+ "src/components/Cards.astro",
11
+ "src/components/NodeRenderer.astro",
12
+ "src/components/WebComponentLoader.astro",
13
+ "src/components/Popover.astro",
14
+ "src/components/Details.astro",
15
+ "src/components/Drawer.astro",
9
16
  "src/schemas",
10
17
  "virtual.d.ts"
11
18
  ],
@@ -17,6 +24,13 @@
17
24
  "import": "./dist/index.mjs"
18
25
  },
19
26
  "./Head.astro": "./src/components/Head.astro",
27
+ "./components/Subgrid.astro": "./src/components/Subgrid.astro",
28
+ "./components/Cards.astro": "./src/components/Cards.astro",
29
+ "./components/NodeRenderer.astro": "./src/components/NodeRenderer.astro",
30
+ "./components/WebComponentLoader.astro": "./src/components/WebComponentLoader.astro",
31
+ "./components/Popover.astro": "./src/components/Popover.astro",
32
+ "./components/Details.astro": "./src/components/Details.astro",
33
+ "./components/Drawer.astro": "./src/components/Drawer.astro",
20
34
  "./schemas/*": "./src/schemas/*",
21
35
  "./package.json": "./package.json"
22
36
  },
@@ -25,17 +39,22 @@
25
39
  },
26
40
  "dependencies": {
27
41
  "@tailwindcss/vite": "^4.3.3",
28
- "parse5": "^7.3.0",
29
42
  "tailwindcss": "^4.3.3",
30
- "@getvitops/core": "0.2.1",
31
- "@getvitops/generator": "0.2.1",
32
- "@getvitops/utils": "0.2.1",
33
- "@getvitops/vite": "0.2.1"
43
+ "@getvitops/generator": "0.3.0",
44
+ "@getvitops/core": "0.3.0",
45
+ "@getvitops/vite": "0.3.0",
46
+ "@getvitops/utils": "0.3.0"
34
47
  },
35
48
  "devDependencies": {
36
49
  "astro": "^7.1.2"
37
50
  },
38
51
  "peerDependencies": {
39
- "astro": ">=7"
52
+ "astro": ">=7",
53
+ "astro-icon": ">=1"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "astro-icon": {
57
+ "optional": true
58
+ }
40
59
  }
41
60
  }
@@ -0,0 +1,32 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+ import Subgrid from './Subgrid.astro';
4
+ import { parseRenderedSlots, serialize } from '@getvitops/utils';
5
+
6
+ export interface Props extends HTMLAttributes<'ul'> {}
7
+
8
+ const { class: className, ...rest } = Astro.props;
9
+
10
+ const nodes = parseRenderedSlots(await Astro.slots.render('default'));
11
+ const slots = nodes.filter(node => 'tagName' in node).map(node => {
12
+ const cls = node.attrs?.find(attr => attr.name === 'class');
13
+ const value = `card${cls ? ' ' + cls.value : ''}`;
14
+ if (cls) {
15
+ cls.value = value;
16
+ } else {
17
+ node.attrs.push({ name: 'class', value });
18
+ }
19
+
20
+ return { html: serialize(node), attrs: node.attrs };
21
+ });
22
+ ---
23
+
24
+ <Subgrid
25
+ role="list"
26
+ class:list={["no-list-markers", className]}
27
+ {...rest}
28
+ >
29
+ {slots.map((slot) => (
30
+ <li class="card-wrapper" style={slot.attrs?.find(attr => attr.name === 'style')?.value} set:html={slot.html}></li>
31
+ ))}
32
+ </Subgrid>
@@ -0,0 +1,76 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+
4
+ interface Item {
5
+ label: string;
6
+ href: string;
7
+ icon?: string;
8
+ }
9
+
10
+ interface Props {
11
+ label: string;
12
+ icon?: string;
13
+ items: Item[];
14
+ class?: string;
15
+ /** Resolve semantic icon names to fully-qualified astro-icon names.
16
+ * Defaults to pass-through (expects already-qualified names like 'fa7-solid:chevron-down'). */
17
+ iconResolver?: (name: string) => string;
18
+ }
19
+
20
+ const {
21
+ label,
22
+ items,
23
+ class: className,
24
+ iconResolver = (name: string) => name,
25
+ } = Astro.props;
26
+ ---
27
+
28
+ <details class={`details-collapse ${className || ''}`}>
29
+ <summary class="details-trigger"><slot>
30
+ {label}
31
+ <Icon name={iconResolver('expand-more')} class="details-icon" /></slot>
32
+ </summary>
33
+ <slot name="content" />
34
+ </details>
35
+
36
+ <style>
37
+ .details-collapse {
38
+ --_details-icon-rotate: 180deg;
39
+ border: none;
40
+ padding: 0;
41
+ }
42
+
43
+ .details-trigger {
44
+ gap: 0.75rem;
45
+ background: none;
46
+ border: none;
47
+ padding: 0;
48
+ width: 100%;
49
+ text-align: left;
50
+ font-size: inherit;
51
+ font-family: inherit;
52
+ color: inherit;
53
+ }
54
+
55
+ .details-icon {
56
+ width: 0.75rem;
57
+ height: 0.75rem;
58
+ }
59
+
60
+ .details-content {
61
+ list-style: none;
62
+ padding-left: 2rem;
63
+ margin-top: 0.75rem;
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: 0.75rem;
67
+ }
68
+
69
+ .details-content a {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 0.5rem;
73
+ text-decoration: none;
74
+ color: inherit;
75
+ }
76
+ </style>
@@ -0,0 +1,65 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+
4
+ type Attrs = Record<string, string | number | boolean>;
5
+
6
+ interface Props {
7
+ id: string;
8
+ position?: 'left' | 'right' | 'top' | 'bottom';
9
+ trigger?: {
10
+ label: string;
11
+ icon?: string;
12
+ class?: string;
13
+ attrs?: Attrs;
14
+ };
15
+ attrs?: Attrs;
16
+ class?: string;
17
+ maxWidth?: string;
18
+ maxHeight?: string;
19
+ /** Resolve semantic icon names to fully-qualified astro-icon names.
20
+ * Defaults to pass-through (expects already-qualified names like 'fa7-solid:bars'). */
21
+ iconResolver?: (name: string) => string;
22
+ }
23
+
24
+ const {
25
+ id,
26
+ position = 'left',
27
+ trigger,
28
+ attrs,
29
+ class: className,
30
+ maxWidth,
31
+ maxHeight,
32
+ iconResolver = (name: string) => name,
33
+ } = Astro.props;
34
+
35
+ const styleVars = [
36
+ maxWidth && `--drawer-max-inline-size: ${maxWidth}`,
37
+ maxHeight && `--drawer-max-block-size: ${maxHeight}`,
38
+ ].filter(Boolean).join('; ') || undefined;
39
+
40
+ const { class: triggerAttrClass, ...triggerRestAttrs } = trigger?.attrs ?? {};
41
+ const { class: dialogAttrClass, style: dialogAttrStyle, ...dialogRestAttrs } = attrs ?? {};
42
+ ---
43
+
44
+ <slot name="trigger">
45
+ <button
46
+ commandfor={id}
47
+ command="show-modal"
48
+ class:list={[trigger?.class || 'btn', triggerAttrClass]}
49
+ aria-label={trigger?.label || 'Open drawer'}
50
+ {...triggerRestAttrs}
51
+ >
52
+ <span class={trigger?.icon ? 'sr-only' : ''}>{trigger?.label || 'Open'}</span>
53
+ {trigger?.icon && <Icon name={iconResolver(trigger.icon)} class="not-sr-only" />}
54
+ </button>
55
+ </slot>
56
+
57
+ <dialog
58
+ id={id}
59
+ closedby="any"
60
+ class:list={['drawer', `drawer--${position}`, className, dialogAttrClass]}
61
+ style={[styleVars, dialogAttrStyle].filter(Boolean).join('; ') || undefined}
62
+ {...dialogRestAttrs}
63
+ >
64
+ <slot />
65
+ </dialog>
@@ -0,0 +1,27 @@
1
+ ---
2
+ import { nodesToHtml } from '@getvitops/utils';
3
+ import { t } from '@getvitops/utils';
4
+ import type { ContentNode, Localizable, Attrs } from '@getvitops/utils';
5
+
6
+ interface Props {
7
+ nodes: ContentNode[];
8
+ attrs?: Attrs;
9
+ locale?: string;
10
+ defaultLocale?: string;
11
+ }
12
+
13
+ const {
14
+ nodes,
15
+ attrs,
16
+ locale = 'en',
17
+ defaultLocale = 'en',
18
+ } = Astro.props;
19
+
20
+ const resolveText = (value: Localizable) => t(value, locale, defaultLocale);
21
+ const htmlString = nodesToHtml(nodes, resolveText);
22
+ ---
23
+
24
+ {attrs
25
+ ? <div {...attrs} set:html={htmlString} />
26
+ : <Fragment set:html={htmlString} />
27
+ }
@@ -0,0 +1,90 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+
4
+ interface Props {
5
+ id: string;
6
+ trigger?: {
7
+ label: string;
8
+ icon?: string;
9
+ class?: string;
10
+ attrs?: Record<string, string | number | boolean>;
11
+ };
12
+ type?: 'auto' | 'manual';
13
+ as?: 'div' | 'dialog';
14
+ class?: string;
15
+ anchorName?: string;
16
+ positionArea?: string;
17
+ /** Resolve semantic icon names to fully-qualified astro-icon names.
18
+ * Defaults to pass-through (expects already-qualified names like 'fa7-solid:bars'). */
19
+ iconResolver?: (name: string) => string;
20
+ }
21
+
22
+ const {
23
+ id,
24
+ trigger,
25
+ type = 'auto',
26
+ as: Element = 'div',
27
+ class: className,
28
+ anchorName,
29
+ positionArea = 'block-end center',
30
+ iconResolver = (name: string) => name,
31
+ } = Astro.props;
32
+
33
+ const styles = anchorName
34
+ ? `position-anchor: --${anchorName}; position-area: ${positionArea}`
35
+ : undefined;
36
+
37
+ const anchorStyles = anchorName ? `anchor-name: --${anchorName}` : undefined;
38
+
39
+ const { class: triggerAttrClass, ...triggerRestAttrs } = trigger?.attrs ?? {};
40
+ ---
41
+
42
+ {/* Trigger button - slot with default fallback */}
43
+ <slot name="trigger">
44
+ <button
45
+ popovertarget={id}
46
+ class:list={['popover-trigger', trigger?.class, triggerAttrClass]}
47
+ style={anchorStyles}
48
+ aria-label={trigger?.label || 'Open'}
49
+ {...triggerRestAttrs}
50
+ >
51
+ <span class={trigger?.icon ? 'sr-only' : ''}>{trigger?.label || 'Open'}</span>
52
+ {trigger?.icon && <Icon name={iconResolver(trigger.icon)} class="not-sr-only" />}
53
+ </button>
54
+ </slot>
55
+
56
+
57
+ <Element
58
+ id={id}
59
+ popover={type}
60
+ class:list={["popover", className]}
61
+ style={styles}
62
+ >
63
+ <slot />
64
+ </Element>
65
+
66
+ <style>
67
+ .popover {
68
+ margin: 0;
69
+ inset: auto;
70
+ border: none;
71
+ padding: 0;
72
+ }
73
+
74
+ .popover[popover]::backdrop {
75
+ background: rgba(0, 0, 0, 0.5);
76
+ }
77
+
78
+ .popover-trigger {
79
+ display: inline-flex;
80
+ align-items: center;
81
+ gap: 0.5rem;
82
+ background: none;
83
+ border: none;
84
+ cursor: pointer;
85
+ padding: 0;
86
+ font-size: inherit;
87
+ font-family: inherit;
88
+ color: inherit;
89
+ }
90
+ </style>
@@ -0,0 +1,32 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+ import { parseRenderedSlots, getAttribute, serialize, styleList } from '@getvitops/utils';
4
+
5
+ export interface Props extends HTMLAttributes<'ul'> {};
6
+
7
+ const { class: className, style } = Astro.props;
8
+
9
+ const nodes = parseRenderedSlots(await Astro.slots.render('default'));
10
+ const slots = nodes.filter(node => 'tagName' in node).map(node => ({ html: serialize(node), attrs: node.attrs }));
11
+ ---
12
+
13
+ <style>
14
+ ul {
15
+ grid-template-columns: repeat(var(--cols, var(--_cols, 1)), minmax(0, 1fr));
16
+ }
17
+
18
+ li {
19
+ --current-row: round(down, calc((sibling-index() - 1) / var(--cols)), 1);
20
+
21
+ /* Apply the extra gap ONLY if the row index is > 0.
22
+ We use clamp to ensure the multiplier is either 0 or 1.
23
+ */
24
+ --row-gap-multiplier: clamp(0, var(--current-row), 1);
25
+ margin-block-start: calc(var(--row-gap-multiplier) * var(--row-margin));
26
+ }
27
+ </style>
28
+ <ul class:list={["grid", className]} style={styleList({'--num-items': slots.length}, style)}>
29
+ {slots.map((slot) => (
30
+ <li class:list={["grid grid-rows-subgrid row-span-(--row-span)", getAttribute(slot, 'class')]} style={styleList(getAttribute(slot, 'style'))} set:html={slot.html}></li>
31
+ ))}
32
+ </ul>
@@ -0,0 +1,48 @@
1
+ ---
2
+ /**
3
+ * Generic loader for web components.
4
+ * Uses import.meta.glob to include all WC*.ts files in the dependency graph,
5
+ * then dynamically loads the requested one on the client.
6
+ *
7
+ * @example
8
+ * <WebComponentLoader src="WCColorSchemeToggle" />
9
+ * <color-scheme-toggle></color-scheme-toggle>
10
+ */
11
+ interface Props {
12
+ src: string;
13
+ }
14
+
15
+ const { src } = Astro.props;
16
+ ---
17
+
18
+ <wc-loader data-src={src}></wc-loader>
19
+
20
+ <script>
21
+ // All web components matching this pattern are included in the build
22
+ const modules = import.meta.glob<{ default?: unknown }>(
23
+ '/src/components/web-components/WC*.ts'
24
+ );
25
+
26
+ // Load requested modules
27
+ document.querySelectorAll<HTMLElement>('wc-loader[data-src]').forEach(async (el) => {
28
+ const name = el.dataset.src;
29
+ if (!name) return;
30
+
31
+ // Build the full path from the component name
32
+ const path = `/src/components/web-components/${name}.ts`;
33
+ const loader = modules[path];
34
+
35
+ if (loader) {
36
+ await loader();
37
+ el.remove(); // Clean up the loader element
38
+ } else {
39
+ console.warn(`[WebComponentLoader] Module not found: ${path}`);
40
+ }
41
+ });
42
+ </script>
43
+
44
+ <style>
45
+ wc-loader {
46
+ display: none;
47
+ }
48
+ </style>