comark-cards 0.0.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,69 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 onmax
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ The icons are Lucide's (https://lucide.dev), under its license:
26
+
27
+ ISC License
28
+
29
+ Copyright (c) 2026 Lucide Icons and Contributors
30
+
31
+ Permission to use, copy, modify, and/or distribute this software for any
32
+ purpose with or without fee is hereby granted, provided that the above
33
+ copyright notice and this permission notice appear in all copies.
34
+
35
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
36
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
37
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
38
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
39
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
40
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
41
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
42
+
43
+ ---
44
+
45
+ The following Lucide icons are derived from the Feather project:
46
+
47
+ airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
48
+
49
+ The MIT License (MIT) (for the icons listed above)
50
+
51
+ Copyright (c) 2013-present Cole Bemis
52
+
53
+ Permission is hereby granted, free of charge, to any person obtaining a copy
54
+ of this software and associated documentation files (the "Software"), to deal
55
+ in the Software without restriction, including without limitation the rights
56
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
57
+ copies of the Software, and to permit persons to whom the Software is
58
+ furnished to do so, subject to the following conditions:
59
+
60
+ The above copyright notice and this permission notice shall be included in all
61
+ copies or substantial portions of the Software.
62
+
63
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
64
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
65
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
66
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
67
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
68
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
69
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,54 @@
1
1
  # comark-cards
2
2
 
3
- Placeholder. The first release is published from https://github.com/onmax/comark-plugins.
3
+ [![npm version](https://comark-ui.onmax.me/badge.svg?source=npm-version&package=comark-cards&icon=npm)](https://npmx.dev/package/comark-cards)
4
+ [![npm downloads](https://comark-ui.onmax.me/badge.svg?source=npm-downloads&package=comark-cards&period=month&icon=npm)](https://npmx.dev/package/comark-cards)
5
+ [![license](https://comark-ui.onmax.me/badge.svg?label=license&value=MIT)](https://npmx.dev/package/comark-cards)
6
+ [![docs](https://comark-ui.onmax.me/badge.svg?label=docs&value=comark-ui.onmax.me)](https://comark-ui.onmax.me/cards/)
7
+
8
+ Cards, link cards, and columns in Nuxt UI's syntax, with no script. A [Comark](https://comark.dev) plugin for HTML, Vue, React, and Svelte.
9
+
10
+ ```sh
11
+ pnpm add comark comark-cards
12
+ ```
13
+
14
+ ```ts
15
+ import { createHtmlRenderer } from "@comark/html";
16
+ import cards, { components } from "comark-cards/html";
17
+
18
+ const render = createHtmlRenderer({ plugins: [cards()], components });
19
+ ```
20
+
21
+ ```md
22
+ ::card-group
23
+ :::card{title="Guides" icon="lucide:book-open" to="/guides/"}
24
+ Start here.
25
+ :::
26
+
27
+ :::card{title="GitHub" to="https://github.com/onmax/comark-ui" color="neutral"}
28
+ Read the code.
29
+ :::
30
+ ::
31
+
32
+ ::columns{cols="2"}
33
+ Left.
34
+
35
+ Right.
36
+ ::
37
+ ```
38
+
39
+ `::card` takes `title` (or a `#title` slot), `description`, `icon`, `to`, `target`, `color`, and `variant`: `soft` washes the card in its color, and `subtle` draws its border in it too. A card with `to` is covered by one link named after its title; a web address opens in a new tab. `::columns{cols}` makes a cell of each block inside, and `:::column` groups several. Icons resolve through comark-icon; pass `iconResolver` to share the page's.
40
+
41
+ The plugin adds every problem to `meta.issues`, so a host can reject a document before publishing it: `href` written instead of `to`, a script link, an unknown color or variant, a `cols` outside 1 to 6, and an icon it can't draw. Each issue names its block and says how to fix it.
42
+
43
+ ## Renderers
44
+
45
+ | Renderer | Import |
46
+ | --- | --- |
47
+ | `@comark/html` | `comark-cards/html`: the styles are inlined once per document |
48
+ | `@comark/vue` | `comark-cards/vue` |
49
+ | `@comark/react` | `comark-cards/react` |
50
+ | `@comark/svelte` | `comark-cards/svelte` |
51
+
52
+ Each entry exports the parse plugin as `default` and the renderer's `components`. The Vue, React, and Svelte components add the styles when they mount; import `comark-cards/style.css` with your styles so a server-rendered page doesn't shift.
53
+
54
+ The styles follow shadcn/ui: they read its CSS variables, such as `--accent` and `--border`, when the page has them, and use its neutral theme otherwise, while colors read Nuxt UI's `--ui-*` variables. Set the shared `--comark-*` tokens to theme every plugin at once; the rules sit in a `comark` CSS layer, so any plain rule of your own wins.
@@ -0,0 +1,130 @@
1
+ //#region ../zoom/dist/theme.mjs
2
+ const tokens = [
3
+ [
4
+ "background",
5
+ "ui-bg",
6
+ "background",
7
+ "#fff",
8
+ "#0a0a0a"
9
+ ],
10
+ [
11
+ "foreground",
12
+ "ui-text-highlighted",
13
+ "foreground",
14
+ "#0a0a0a",
15
+ "#fafafa"
16
+ ],
17
+ [
18
+ "muted",
19
+ "ui-text-muted",
20
+ "muted-foreground",
21
+ "#737373",
22
+ "#a1a1a1"
23
+ ],
24
+ [
25
+ "border",
26
+ "ui-border",
27
+ "border",
28
+ "#e5e5e5",
29
+ "rgb(255 255 255/.1)"
30
+ ],
31
+ [
32
+ "accent",
33
+ "ui-bg-elevated",
34
+ "accent",
35
+ "#f5f5f5",
36
+ "#262626"
37
+ ],
38
+ [
39
+ "ring",
40
+ "ui-primary",
41
+ "ring",
42
+ "#a1a1a1",
43
+ "#737373"
44
+ ]
45
+ ];
46
+ const resolve = (scheme) => tokens.map((token) => `--_comark-${token[0]}:var(--comark-${token[0]},var(--${token[1]},var(--${token[2]},${token[scheme]})))`).join(";");
47
+ /**
48
+ * Resolves the `--comark-*` tokens on `selector`, falling back to Nuxt UI's variables, then shadcn/ui's, then shadcn's
49
+ * neutral theme. Nuxt UI's corners are twice its `--ui-radius`, so the radius halves shadcn's to double it back.
50
+ * They resolve on the component rather than `:root`, so a `.dark` class anywhere above it switches the theme, and a
51
+ * `--comark-*` set on any ancestor still inherits down. `:where()` keeps them at zero specificity.
52
+ */
53
+ function theme(selector) {
54
+ const where = `:where(${selector})`;
55
+ return `${where}{${resolve(3)};--_comark-radius:var(--comark-radius,calc(var(--ui-radius,calc(var(--radius,.625rem)/2))*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media (prefers-color-scheme:dark){${where}{${resolve(4)}}}`;
56
+ }
57
+ //#endregion
58
+ //#region src/styles.ts
59
+ /**
60
+ * Cards in the style of shadcn/ui's Card, two to a row in a group and one when the group is narrower than 36rem, and
61
+ * columns from `::columns`, on the shared `--comark-*` tokens. The group is a size container, so a group in a narrow
62
+ * column stacks whatever the screen's width. A card's color tints its icon and, on a link card, its hovered border; the
63
+ * `soft` variant washes the card in it, and `subtle` its border too. Each color reads Nuxt UI's `--ui-*` variable when
64
+ * the page has it, then `--ccr-*`. Primary and error fall back to shadcn/ui's `--primary` and `--destructive`, and
65
+ * primary then to the foreground, so a card is neutral like the other plugins until the page has a brand color. The rules sit in the `comark` layer, so any unlayered rule of the host's wins.
66
+ */
67
+ const css = `@layer comark{${[
68
+ theme(".ccr,.ccr-group,.ccr-columns"),
69
+ ":where(.ccr){--ccr-primary:var(--ui-primary,var(--primary,var(--_comark-foreground)));--ccr-secondary:var(--ui-secondary,#2563eb);--ccr-success:var(--ui-success,#16a34a);--ccr-info:var(--ui-info,#2563eb);--ccr-warning:var(--ui-warning,#ca8a04);--ccr-error:var(--ui-error,var(--destructive,#dc2626));--ccr-neutral:var(--_comark-foreground)}",
70
+ "@media (prefers-color-scheme:dark){:where(.ccr){--ccr-secondary:var(--ui-secondary,#60a5fa);--ccr-success:var(--ui-success,#4ade80);--ccr-info:var(--ui-info,#60a5fa);--ccr-warning:var(--ui-warning,#facc15);--ccr-error:var(--ui-error,var(--destructive,#f87171))}}",
71
+ ".ccr-group,.ccr-columns{display:grid;gap:1rem;margin:1.5rem 0;container-type:inline-size}",
72
+ ".ccr-group{grid-template-columns:repeat(2,minmax(0,1fr))}",
73
+ ...[
74
+ 2,
75
+ 3,
76
+ 4,
77
+ 5,
78
+ 6
79
+ ].map((cols) => `.ccr-columns[data-cols='${cols}']{grid-template-columns:repeat(${cols},minmax(0,1fr))}`),
80
+ "@container (width<36rem){.ccr-group>*,.ccr-columns>*{grid-column:1/-1}}",
81
+ ".ccr-group>.ccr{margin:0}",
82
+ ".ccr-column>:first-child{margin-top:0}",
83
+ ".ccr-column>:last-child{margin-bottom:0}",
84
+ ".ccr{--ccr:var(--ccr-primary);position:relative;margin:1.5rem 0;padding:1.25rem;border:1px solid var(--_comark-border);border-radius:calc(var(--_comark-radius) + 2px);background:var(--_comark-background);box-shadow:0 1px 2px rgb(0 0 0/.05);color:var(--_comark-muted);font:400 .875rem/1.375rem var(--_comark-font);transition:background-color .15s,border-color .15s}",
85
+ ...[
86
+ "secondary",
87
+ "success",
88
+ "info",
89
+ "warning",
90
+ "error",
91
+ "neutral"
92
+ ].map((color) => `.ccr[data-color='${color}']{--ccr:var(--ccr-${color})}`),
93
+ ".ccr[data-variant='soft'],.ccr[data-variant='subtle']{background:color-mix(in srgb,var(--ccr) 10%,var(--_comark-background))}",
94
+ ".ccr[data-variant='soft']{border-color:transparent}",
95
+ ".ccr[data-variant='subtle']{border-color:color-mix(in srgb,var(--ccr) 30%,var(--_comark-background))}",
96
+ ".ccr>:last-child{margin-bottom:0}",
97
+ ".ccr>.ci{--comark-icon-size:1.25rem;display:block;margin-bottom:.75rem;color:var(--ccr)}",
98
+ ".ccr-title{margin:0 0 .25rem;color:var(--_comark-foreground);font-size:.9375rem;font-weight:600;line-height:1.375rem;text-wrap:balance}",
99
+ ".ccr-title+*{margin-top:0}",
100
+ ".ccr-link{position:absolute;inset:0;z-index:0;border-radius:inherit}",
101
+ ".ccr-link:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--_comark-ring) 50%,transparent)}",
102
+ ".ccr :is(a,button):not(.ccr-link){position:relative;z-index:1}",
103
+ ".ccr:has(>.ccr-link):hover{border-color:color-mix(in srgb,var(--ccr) 50%,var(--_comark-border));background:color-mix(in srgb,var(--_comark-accent) 50%,var(--_comark-background))}",
104
+ ".ccr[data-variant='soft']:has(>.ccr-link):hover{border-color:transparent}",
105
+ ".ccr[data-variant='subtle']:has(>.ccr-link):hover{border-color:color-mix(in srgb,var(--ccr) 30%,var(--_comark-background))}",
106
+ ".ccr[data-variant='soft']:has(>.ccr-link):hover,.ccr[data-variant='subtle']:has(>.ccr-link):hover{background:color-mix(in srgb,var(--ccr) 16%,var(--_comark-background))}",
107
+ ".ccr-external{position:absolute;top:1rem;inset-inline-end:1rem;width:1rem;height:1rem;color:var(--_comark-muted)}",
108
+ ".ccr:dir(rtl) .ccr-external{scale:-1 1}",
109
+ "@media print{.ccr{break-inside:avoid}.ccr-external{display:none}}"
110
+ ].join("")}}`;
111
+ //#endregion
112
+ //#region src/client.ts
113
+ /** Adds the card styles to the page once. The Vue, React, and Svelte components call it when they mount. */
114
+ function addStyles() {
115
+ if (document.querySelector("style[data-comark-cards]")) return;
116
+ const style = document.createElement("style");
117
+ style.dataset.comarkCards = "";
118
+ style.textContent = css;
119
+ document.head.append(style);
120
+ }
121
+ /** Lucide's arrow-up-right (ISC), drawn in the corner of a card that opens in a new tab. */
122
+ const external = "M7 7h10v10M7 17 17 7";
123
+ /** Returns whether a card URL is safe to place in an href. */
124
+ const isSafeHref = (value) => !!value && !/^\s*(?:javascript|vbscript|data):/i.test(value);
125
+ /** Web URLs open in a new tab, including protocol-relative URLs. */
126
+ const isExternalHref = (value) => /^(?:https?:\/\/|\/\/)/i.test(value ?? "");
127
+ /** The `rel` of a link that opens in a new tab, so the page it opens can't reach back into this one. */
128
+ const rel = (target) => target === "_blank" ? "noopener noreferrer" : void 0;
129
+ //#endregion
130
+ export { rel as a, isSafeHref as i, external as n, css as o, isExternalHref as r, addStyles as t };
@@ -0,0 +1,23 @@
1
+ //#region src/styles.d.ts
2
+ /**
3
+ * Cards in the style of shadcn/ui's Card, two to a row in a group and one when the group is narrower than 36rem, and
4
+ * columns from `::columns`, on the shared `--comark-*` tokens. The group is a size container, so a group in a narrow
5
+ * column stacks whatever the screen's width. A card's color tints its icon and, on a link card, its hovered border; the
6
+ * `soft` variant washes the card in it, and `subtle` its border too. Each color reads Nuxt UI's `--ui-*` variable when
7
+ * the page has it, then `--ccr-*`. Primary and error fall back to shadcn/ui's `--primary` and `--destructive`, and
8
+ * primary then to the foreground, so a card is neutral like the other plugins until the page has a brand color. The rules sit in the `comark` layer, so any unlayered rule of the host's wins.
9
+ */
10
+ export declare const css: string;
11
+ //#endregion
12
+ //#region src/client.d.ts
13
+ /** Adds the card styles to the page once. The Vue, React, and Svelte components call it when they mount. */
14
+ export declare function addStyles(): void;
15
+ /** Lucide's arrow-up-right (ISC), drawn in the corner of a card that opens in a new tab. */
16
+ export declare const external = "M7 7h10v10M7 17 17 7";
17
+ /** Returns whether a card URL is safe to place in an href. */
18
+ export declare const isSafeHref: (value?: string) => boolean;
19
+ /** Web URLs open in a new tab, including protocol-relative URLs. */
20
+ export declare const isExternalHref: (value?: string) => boolean;
21
+ /** The `rel` of a link that opens in a new tab, so the page it opens can't reach back into this one. */
22
+ export declare const rel: (target?: string) => string | undefined;
23
+ //#endregion
@@ -0,0 +1 @@
1
+ export { t as addStyles, o as css, n as external, r as isExternalHref, i as isSafeHref, a as rel } from "./client-C5acm8_C.mjs";
@@ -0,0 +1,7 @@
1
+ import _default, { CardAttributes, CardColor, CardVariant, CardsMeta, CardsOptions, ColumnsAttributes, colors, variants } from "./index.mjs";
2
+ import { NodeHandler } from "comark";
3
+ //#region src/html.d.ts
4
+ /** The HTML renderer for `::card-group`, `::card`, and `::columns`. Pass it as `components`. */
5
+ export declare const components: Record<"card" | "card-title" | "card-group" | "columns" | "column", NodeHandler>;
6
+ //#endregion
7
+ export { type CardAttributes, type CardColor, type CardVariant, type CardsMeta, type CardsOptions, type ColumnsAttributes, colors, _default as default, variants };
package/dist/html.mjs ADDED
@@ -0,0 +1,34 @@
1
+ import { a as rel, n as external, o as css } from "./client-C5acm8_C.mjs";
2
+ import src_default, { colors, variants } from "./index.mjs";
3
+ import { escapeHtml } from "comark/utils";
4
+ import { iconHtml, iconStyles } from "comark-icon/html";
5
+ //#region src/html.ts
6
+ const styled = /* @__PURE__ */ new WeakSet();
7
+ const attr = (value) => escapeHtml(String(value), { "\"": "&quot;" });
8
+ const styles = (state) => {
9
+ if (styled.has(state.context)) return "";
10
+ styled.add(state.context);
11
+ return `<style data-comark-cards>${css}</style>`;
12
+ };
13
+ const card = async (node, state) => {
14
+ const [, attrs] = node;
15
+ const target = attrs.target ? attrs.target : void 0;
16
+ const link = attrs.to ? `<a class="ccr-link" href="${attr(attrs.to)}"${target ? ` target="${attr(target)}"` : ""}${rel(target) ? ` rel="${rel(target)}"` : ""} aria-label="${attr(attrs.label)}"></a>` : "";
17
+ const icon = attrs.icon ? iconHtml(attrs.icon) : "";
18
+ const arrow = attrs.external ? `<svg class="ccr-external" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="${external}"></path></svg>` : "";
19
+ return `${styles(state)}${attrs.icon ? iconStyles(state) : ""}<div class="ccr" data-color="${attr(attrs.color ?? "primary")}"${attrs.variant ? ` data-variant="${attr(attrs.variant)}"` : ""}>${link}${icon}${arrow}${await state.flow(node, state)}</div>`;
20
+ };
21
+ const title = async (node, state) => `<p class="ccr-title">${await state.flow(node, state)}</p>`;
22
+ const group = async (node, state) => `${styles(state)}<div class="ccr-group">${await state.flow(node, state)}</div>`;
23
+ const columns = async (node, state) => `${styles(state)}<div class="ccr-columns" data-cols="${attr(node[1].cols ?? "2")}">${await state.flow(node, state)}</div>`;
24
+ const column = async (node, state) => `<div class="ccr-column">${await state.flow(node, state)}</div>`;
25
+ /** The HTML renderer for `::card-group`, `::card`, and `::columns`. Pass it as `components`. */
26
+ const components = {
27
+ card,
28
+ "card-title": title,
29
+ "card-group": group,
30
+ columns,
31
+ column
32
+ };
33
+ //#endregion
34
+ export { colors, components, src_default as default, variants };
@@ -0,0 +1,49 @@
1
+ import { IconData, Icons } from "comark-icon";
2
+ //#region src/index.d.ts
3
+ export type CardsOptions = {
4
+ /**
5
+ * Resolves the cards' `icon`, from `createIcons()` in comark-icon. Pass the one the page's other plugins use, so they
6
+ * share its `collections` and `fetch`. By default icons come from the Iconify API.
7
+ */
8
+ iconResolver?: Icons;
9
+ };
10
+ export type CardsMeta = {
11
+ /** One readable sentence per problem with a `::card` or `::columns`. Empty when there are none. Other plugins add theirs too. */
12
+ issues: string[];
13
+ /** The comark-linter rule of each issue, by its sentence, so the linter reports it under its own code. */
14
+ issueRules: Record<string, string>;
15
+ };
16
+ /** Nuxt UI's colors, which tint a card's icon and border, and its background with a `variant`. */
17
+ export declare const colors: readonly ["primary", "secondary", "success", "info", "warning", "error", "neutral"];
18
+ export type CardColor = (typeof colors)[number];
19
+ /**
20
+ * Nuxt UI's card variants that a document block needs: `outline`, the default, keeps the plain card, `soft` washes it
21
+ * in its color, and `subtle` washes it and draws its border in the color too.
22
+ */
23
+ export declare const variants: readonly ["outline", "soft", "subtle"];
24
+ export type CardVariant = (typeof variants)[number];
25
+ /**
26
+ * What a `::card` becomes. A link card has `to`, its `target`, `external` when it opens in a new tab, and `label`, the
27
+ * link's name. `variant` is only written when it isn't `outline`. Its title, when it has one, is a `card-title` node
28
+ * before its content.
29
+ */
30
+ export type CardAttributes = {
31
+ color?: CardColor;
32
+ variant?: Exclude<CardVariant, "outline">;
33
+ to?: string;
34
+ target?: string;
35
+ external?: string;
36
+ label?: string;
37
+ icon?: IconData;
38
+ };
39
+ /** What a `::columns` becomes: how many columns, with each cell a `column` node. */
40
+ export type ColumnsAttributes = {
41
+ cols?: string;
42
+ };
43
+ /**
44
+ * Turns `::card-group` and `::card{title description icon to target color variant}` into cards, link cards when they
45
+ * have `to`, and `::columns{cols}` into a grid with a cell per block. Problems are added to `meta.issues`.
46
+ */
47
+ declare const _default: import("comark").ComarkPluginFactory<CardsOptions, CardsMeta, {}>;
48
+ //#endregion
49
+ export { _default as default };
package/dist/index.mjs ADDED
@@ -0,0 +1,211 @@
1
+ import { i as isSafeHref, r as isExternalHref } from "./client-C5acm8_C.mjs";
2
+ import { defineComarkPlugin } from "comark";
3
+ import { resolveAttributes } from "comark/render";
4
+ import { textContent } from "comark/utils";
5
+ import { createIcons, describeIconProblem, iconProblemRule } from "comark-icon";
6
+ //#region src/index.ts
7
+ /** Nuxt UI's colors, which tint a card's icon and border, and its background with a `variant`. */
8
+ const colors = [
9
+ "primary",
10
+ "secondary",
11
+ "success",
12
+ "info",
13
+ "warning",
14
+ "error",
15
+ "neutral"
16
+ ];
17
+ /**
18
+ * Nuxt UI's card variants that a document block needs: `outline`, the default, keeps the plain card, `soft` washes it
19
+ * in its color, and `subtle` washes it and draws its border in the color too.
20
+ */
21
+ const variants = [
22
+ "outline",
23
+ "soft",
24
+ "subtle"
25
+ ];
26
+ const isElement = (node) => Array.isArray(node) && node[0] !== null;
27
+ const isContent = (node) => Array.isArray(node) ? node[0] !== null : node.trim() !== "";
28
+ const childrenOf = (node) => node.slice(2);
29
+ const text = (value) => value === void 0 || value === null || value instanceof Object ? "" : String(value).trim();
30
+ const clip = (value) => {
31
+ const words = value.trim().replace(/\s+/g, " ");
32
+ return words.length > 40 ? `${words.slice(0, 39)}…` : words;
33
+ };
34
+ const isColor = (value) => colors.some((color) => color === value);
35
+ const isVariant = (value) => variants.some((variant) => variant === value);
36
+ const isTemplate = (node, name) => isElement(node) && node[0] === "template" && node[1].name === name;
37
+ const unwrap = (nodes) => nodes.filter(isContent).flatMap((node) => isElement(node) && node[0] === "p" ? childrenOf(node) : [node]);
38
+ const resolve = (node, walk) => resolveAttributes(node[1], walk.data, { parseJson: true });
39
+ const CARD_PROPS = [
40
+ "title",
41
+ "description",
42
+ "icon",
43
+ "to",
44
+ "href",
45
+ "target",
46
+ "color",
47
+ "variant"
48
+ ];
49
+ function unknownProps(node, name, block, props, walk) {
50
+ const known = props.flatMap((prop) => prop === "href" ? [] : [`\`${prop}\``]);
51
+ const list = known.length > 1 ? `${known.slice(0, -1).join(", ")}, or ${known.at(-1)}` : known[0];
52
+ for (const key of Object.keys(node[1])) {
53
+ const prop = key.replace(/^:/, "");
54
+ if (!props.includes(prop) && prop !== "id" && prop !== "class") walk.issues.push({
55
+ rule: "comark/cards/unknown-prop",
56
+ message: `${name}: \`${prop}\` isn't a prop of \`${block}\`, so it's ignored. Use ${list}.`
57
+ });
58
+ }
59
+ }
60
+ function card(node, walk) {
61
+ const attrs = resolve(node, walk);
62
+ const children = childrenOf(node);
63
+ const slot = children.find((child) => isTemplate(child, "title"));
64
+ const content = children.flatMap((child) => child === slot ? [] : isTemplate(child, "default") ? childrenOf(child) : [child]);
65
+ const title = slot ? unwrap(childrenOf(slot)) : text(attrs.title) ? [text(attrs.title)] : [];
66
+ const words = clip(textContent([
67
+ "p",
68
+ {},
69
+ ...title
70
+ ]));
71
+ const name = `\`::card\` ${++walk.cards}${words ? ` ("${words}")` : ""}`;
72
+ unknownProps(node, name, "::card", CARD_PROPS, walk);
73
+ const description = text(attrs.description);
74
+ if (!content.some(isContent) && description) content.push([
75
+ "p",
76
+ {},
77
+ description
78
+ ]);
79
+ const written = text(attrs.color);
80
+ const result = {};
81
+ result.color = isColor(written) ? written : "primary";
82
+ if (written && !isColor(written)) walk.issues.push({
83
+ rule: "comark/cards/invalid-color",
84
+ message: `${name}: \`color\` is "${clip(written)}", so it shows as primary. Write one of ${colors.join(", ")}.`
85
+ });
86
+ const variant = text(attrs.variant);
87
+ if (isVariant(variant)) {
88
+ if (variant !== "outline") result.variant = variant;
89
+ } else if (variant) walk.issues.push({
90
+ rule: "comark/cards/invalid-variant",
91
+ message: `${name}: \`variant\` is "${clip(variant)}", so it shows as outline. Write one of ${variants.join(", ")}.`
92
+ });
93
+ let to = text(attrs.to);
94
+ const href = text(attrs.href);
95
+ if (href && !to) {
96
+ walk.issues.push({
97
+ rule: "comark/cards/href-instead-of-to",
98
+ message: `${name}: Nuxt UI links a card with \`to\`, so it ignores \`href\`. Write \`to="${clip(href)}"\` instead.`
99
+ });
100
+ to = href;
101
+ }
102
+ if (to && !isSafeHref(to)) {
103
+ walk.issues.push({
104
+ rule: "comark/cards/unsafe-link",
105
+ message: `${name}: \`to\` must be a page or a web address, so it isn't a link.`
106
+ });
107
+ to = "";
108
+ }
109
+ if (to) {
110
+ result.to = to;
111
+ const target = text(attrs.target) || (isExternalHref(to) ? "_blank" : "");
112
+ if (target) result.target = target;
113
+ if (target === "_blank") result.external = "true";
114
+ result.label = words || "Card link";
115
+ }
116
+ const icon = text(attrs.icon);
117
+ if (icon) walk.icons.push({
118
+ card: node,
119
+ value: icon,
120
+ name
121
+ });
122
+ node[1] = result;
123
+ const head = title.length ? [[
124
+ "card-title",
125
+ {},
126
+ ...title
127
+ ]] : [];
128
+ node.splice(2, node.length - 2, ...head, ...content);
129
+ }
130
+ function columns(node, walk) {
131
+ const name = `\`::columns\` ${++walk.columns}`;
132
+ unknownProps(node, name, "::columns", ["cols"], walk);
133
+ const written = text(resolve(node, walk).cols);
134
+ let cols = "2";
135
+ if (/^[1-6]$/.test(written)) cols = written;
136
+ else if (written) walk.issues.push({
137
+ rule: "comark/cards/invalid-cols",
138
+ message: `${name}: \`cols\` is "${clip(written)}", so it shows 2 columns. Write a number from 1 to 6.`
139
+ });
140
+ const cells = childrenOf(node).filter(isContent).map((child) => isElement(child) && child[0] === "column" ? [
141
+ "column",
142
+ {},
143
+ ...childrenOf(child)
144
+ ] : [
145
+ "column",
146
+ {},
147
+ child
148
+ ]);
149
+ if (!cells.length) walk.issues.push({
150
+ rule: "comark/cards/empty-columns",
151
+ message: `${name} is empty. Put a block inside for each column, or remove it.`
152
+ });
153
+ node[1] = { cols };
154
+ node.splice(2, node.length - 2, ...cells);
155
+ }
156
+ function transform(nodes, walk) {
157
+ for (const node of nodes) {
158
+ if (!isElement(node)) continue;
159
+ if (node[0] === "card") card(node, walk);
160
+ else if (node[0] === "columns") columns(node, walk);
161
+ else if (node[0] === "card-group") node[1] = {};
162
+ transform(childrenOf(node), walk);
163
+ }
164
+ }
165
+ async function addIcons(walk, resolver) {
166
+ if (!walk.icons.length) return;
167
+ const resolved = await resolver.resolve(walk.icons.map(({ value }) => value));
168
+ for (const { card: node, value, name } of walk.icons) {
169
+ const icon = resolved.get(value);
170
+ if (!icon) continue;
171
+ if ("problem" in icon) walk.issues.push({
172
+ rule: iconProblemRule(icon),
173
+ message: `${name}, icon "${clip(value)}": ${describeIconProblem(value, icon)}`
174
+ });
175
+ else node[1].icon = icon;
176
+ }
177
+ }
178
+ /**
179
+ * Turns `::card-group` and `::card{title description icon to target color variant}` into cards, link cards when they
180
+ * have `to`, and `::columns{cols}` into a grid with a cell per block. Problems are added to `meta.issues`.
181
+ */
182
+ var src_default = defineComarkPlugin((options = {}) => {
183
+ const resolver = options.iconResolver ?? createIcons();
184
+ return {
185
+ name: "comark-cards",
186
+ async post(state) {
187
+ const { tree } = state;
188
+ const walk = {
189
+ data: {
190
+ frontmatter: tree.frontmatter,
191
+ meta: tree.meta,
192
+ data: {},
193
+ props: {}
194
+ },
195
+ issues: [],
196
+ icons: [],
197
+ cards: 0,
198
+ columns: 0
199
+ };
200
+ transform(tree.nodes, walk);
201
+ await addIcons(walk, resolver);
202
+ tree.meta.issues = [...tree.meta.issues ?? [], ...walk.issues.map((issue) => issue.message)];
203
+ tree.meta.issueRules = {
204
+ ...tree.meta.issueRules,
205
+ ...Object.fromEntries(walk.issues.map((issue) => [issue.message, issue.rule]))
206
+ };
207
+ }
208
+ };
209
+ });
210
+ //#endregion
211
+ export { colors, src_default as default, variants };
@@ -0,0 +1,51 @@
1
+ import _default, { CardAttributes, CardColor, CardVariant, CardsMeta, CardsOptions, ColumnsAttributes, colors, variants } from "./index.mjs";
2
+ import { IconData } from "comark-icon/react";
3
+ import { ReactNode } from "react";
4
+ //#region src/react.d.ts
5
+ type Children = {
6
+ children?: ReactNode;
7
+ };
8
+ type CardProps = Children & {
9
+ color?: string;
10
+ variant?: string;
11
+ to?: string;
12
+ target?: string;
13
+ external?: string | boolean;
14
+ label?: string;
15
+ icon?: IconData;
16
+ };
17
+ /** `::card` for `@comark/react`. */
18
+ export declare function Card({ color, variant, to, target, external: opens, label, icon, children }: CardProps): import("react").DetailedReactHTMLElement<{
19
+ className: string;
20
+ "data-color": string;
21
+ "data-variant": string | undefined;
22
+ }, HTMLElement>;
23
+ /** A card's title for `@comark/react`. */
24
+ export declare function CardTitle({ children }: Children): import("react").DetailedReactHTMLElement<{
25
+ className: string;
26
+ }, HTMLElement>;
27
+ /** `::card-group` for `@comark/react`. */
28
+ export declare function CardGroup({ children }: Children): import("react").DetailedReactHTMLElement<{
29
+ className: string;
30
+ }, HTMLElement>;
31
+ /** `::columns` for `@comark/react`. */
32
+ export declare function Columns({ cols, children }: Children & {
33
+ cols?: string | number;
34
+ }): import("react").DetailedReactHTMLElement<{
35
+ className: string;
36
+ "data-cols": string;
37
+ }, HTMLElement>;
38
+ /** Each cell of a `::columns` for `@comark/react`. */
39
+ export declare function Column({ children }: Children): import("react").DetailedReactHTMLElement<{
40
+ className: string;
41
+ }, HTMLElement>;
42
+ /** Pass these as `components` to `@comark/react`. */
43
+ export declare const components: {
44
+ Card: typeof Card;
45
+ CardTitle: typeof CardTitle;
46
+ CardGroup: typeof CardGroup;
47
+ Columns: typeof Columns;
48
+ Column: typeof Column;
49
+ };
50
+ //#endregion
51
+ export { type CardAttributes, type CardColor, type CardVariant, type CardsMeta, type CardsOptions, type ColumnsAttributes, colors, _default as default, variants };
package/dist/react.mjs ADDED
@@ -0,0 +1,65 @@
1
+ import { a as rel, i as isSafeHref, n as external, r as isExternalHref, t as addStyles } from "./client-C5acm8_C.mjs";
2
+ import src_default, { colors, variants } from "./index.mjs";
3
+ import { Icon } from "comark-icon/react";
4
+ import { createElement, useEffect } from "react";
5
+ //#region src/react.ts
6
+ const arrow = () => createElement("svg", {
7
+ className: "ccr-external",
8
+ viewBox: "0 0 24 24",
9
+ width: 16,
10
+ height: 16,
11
+ fill: "none",
12
+ stroke: "currentColor",
13
+ strokeWidth: 2,
14
+ strokeLinecap: "round",
15
+ strokeLinejoin: "round",
16
+ "aria-hidden": true
17
+ }, createElement("path", { d: external }));
18
+ /** `::card` for `@comark/react`. */
19
+ function Card({ color = "primary", variant, to, target, external: opens, label, icon, children }) {
20
+ useEffect(addStyles, []);
21
+ const href = isSafeHref(to) ? to : void 0;
22
+ const linkTarget = target || (isExternalHref(href) ? "_blank" : void 0);
23
+ return createElement("div", {
24
+ className: "ccr",
25
+ "data-color": color,
26
+ "data-variant": variant
27
+ }, href ? createElement("a", {
28
+ className: "ccr-link",
29
+ href,
30
+ target: linkTarget,
31
+ rel: rel(linkTarget),
32
+ "aria-label": label
33
+ }) : null, icon?.src ? createElement(Icon, icon) : null, opens ? arrow() : null, children);
34
+ }
35
+ /** A card's title for `@comark/react`. */
36
+ function CardTitle({ children }) {
37
+ return createElement("p", { className: "ccr-title" }, children);
38
+ }
39
+ /** `::card-group` for `@comark/react`. */
40
+ function CardGroup({ children }) {
41
+ useEffect(addStyles, []);
42
+ return createElement("div", { className: "ccr-group" }, children);
43
+ }
44
+ /** `::columns` for `@comark/react`. */
45
+ function Columns({ cols = "2", children }) {
46
+ useEffect(addStyles, []);
47
+ return createElement("div", {
48
+ className: "ccr-columns",
49
+ "data-cols": String(cols)
50
+ }, children);
51
+ }
52
+ /** Each cell of a `::columns` for `@comark/react`. */
53
+ function Column({ children }) {
54
+ return createElement("div", { className: "ccr-column" }, children);
55
+ }
56
+ /** Pass these as `components` to `@comark/react`. */
57
+ const components = {
58
+ Card,
59
+ CardTitle,
60
+ CardGroup,
61
+ Columns,
62
+ Column
63
+ };
64
+ //#endregion
65
+ export { Card, CardGroup, CardTitle, Column, Columns, colors, components, src_default as default, variants };
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ @layer comark{:where(.ccr,.ccr-group,.ccr-columns){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#fff)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#0a0a0a)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#737373)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,#e5e5e5)));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#f5f5f5)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#a1a1a1)));--_comark-radius:var(--comark-radius,calc(var(--ui-radius,calc(var(--radius,.625rem)/2))*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media (prefers-color-scheme:dark){:where(.ccr,.ccr-group,.ccr-columns){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#0a0a0a)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#fafafa)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#a1a1a1)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,rgb(255 255 255/.1))));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#262626)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#737373)))}}:where(.ccr){--ccr-primary:var(--ui-primary,var(--primary,var(--_comark-foreground)));--ccr-secondary:var(--ui-secondary,#2563eb);--ccr-success:var(--ui-success,#16a34a);--ccr-info:var(--ui-info,#2563eb);--ccr-warning:var(--ui-warning,#ca8a04);--ccr-error:var(--ui-error,var(--destructive,#dc2626));--ccr-neutral:var(--_comark-foreground)}@media (prefers-color-scheme:dark){:where(.ccr){--ccr-secondary:var(--ui-secondary,#60a5fa);--ccr-success:var(--ui-success,#4ade80);--ccr-info:var(--ui-info,#60a5fa);--ccr-warning:var(--ui-warning,#facc15);--ccr-error:var(--ui-error,var(--destructive,#f87171))}}.ccr-group,.ccr-columns{display:grid;gap:1rem;margin:1.5rem 0;container-type:inline-size}.ccr-group{grid-template-columns:repeat(2,minmax(0,1fr))}.ccr-columns[data-cols='2']{grid-template-columns:repeat(2,minmax(0,1fr))}.ccr-columns[data-cols='3']{grid-template-columns:repeat(3,minmax(0,1fr))}.ccr-columns[data-cols='4']{grid-template-columns:repeat(4,minmax(0,1fr))}.ccr-columns[data-cols='5']{grid-template-columns:repeat(5,minmax(0,1fr))}.ccr-columns[data-cols='6']{grid-template-columns:repeat(6,minmax(0,1fr))}@container (width<36rem){.ccr-group>*,.ccr-columns>*{grid-column:1/-1}}.ccr-group>.ccr{margin:0}.ccr-column>:first-child{margin-top:0}.ccr-column>:last-child{margin-bottom:0}.ccr{--ccr:var(--ccr-primary);position:relative;margin:1.5rem 0;padding:1.25rem;border:1px solid var(--_comark-border);border-radius:calc(var(--_comark-radius) + 2px);background:var(--_comark-background);box-shadow:0 1px 2px rgb(0 0 0/.05);color:var(--_comark-muted);font:400 .875rem/1.375rem var(--_comark-font);transition:background-color .15s,border-color .15s}.ccr[data-color='secondary']{--ccr:var(--ccr-secondary)}.ccr[data-color='success']{--ccr:var(--ccr-success)}.ccr[data-color='info']{--ccr:var(--ccr-info)}.ccr[data-color='warning']{--ccr:var(--ccr-warning)}.ccr[data-color='error']{--ccr:var(--ccr-error)}.ccr[data-color='neutral']{--ccr:var(--ccr-neutral)}.ccr[data-variant='soft'],.ccr[data-variant='subtle']{background:color-mix(in srgb,var(--ccr) 10%,var(--_comark-background))}.ccr[data-variant='soft']{border-color:transparent}.ccr[data-variant='subtle']{border-color:color-mix(in srgb,var(--ccr) 30%,var(--_comark-background))}.ccr>:last-child{margin-bottom:0}.ccr>.ci{--comark-icon-size:1.25rem;display:block;margin-bottom:.75rem;color:var(--ccr)}.ccr-title{margin:0 0 .25rem;color:var(--_comark-foreground);font-size:.9375rem;font-weight:600;line-height:1.375rem;text-wrap:balance}.ccr-title+*{margin-top:0}.ccr-link{position:absolute;inset:0;z-index:0;border-radius:inherit}.ccr-link:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--_comark-ring) 50%,transparent)}.ccr :is(a,button):not(.ccr-link){position:relative;z-index:1}.ccr:has(>.ccr-link):hover{border-color:color-mix(in srgb,var(--ccr) 50%,var(--_comark-border));background:color-mix(in srgb,var(--_comark-accent) 50%,var(--_comark-background))}.ccr[data-variant='soft']:has(>.ccr-link):hover{border-color:transparent}.ccr[data-variant='subtle']:has(>.ccr-link):hover{border-color:color-mix(in srgb,var(--ccr) 30%,var(--_comark-background))}.ccr[data-variant='soft']:has(>.ccr-link):hover,.ccr[data-variant='subtle']:has(>.ccr-link):hover{background:color-mix(in srgb,var(--ccr) 16%,var(--_comark-background))}.ccr-external{position:absolute;top:1rem;inset-inline-end:1rem;width:1rem;height:1rem;color:var(--_comark-muted)}.ccr:dir(rtl) .ccr-external{scale:-1 1}@media print{.ccr{break-inside:avoid}.ccr-external{display:none}}}
@@ -0,0 +1,22 @@
1
+ <!-- `::card` for `@comark/svelte`. -->
2
+ <script>
3
+ import { onMount } from "svelte";
4
+ import { addStyles, external as arrow, isExternalHref, isSafeHref, rel } from "comark-cards/client";
5
+ import { Icon } from "comark-icon/svelte";
6
+
7
+ let {
8
+ color = "primary",
9
+ variant = undefined,
10
+ to = undefined,
11
+ target = undefined,
12
+ external = undefined,
13
+ label = undefined,
14
+ icon = undefined,
15
+ children,
16
+ } = $props();
17
+ onMount(addStyles);
18
+ const href = $derived(isSafeHref(to) ? to : undefined);
19
+ const linkTarget = $derived(target || (isExternalHref(href) ? "_blank" : undefined));
20
+ </script>
21
+
22
+ <div class="ccr" data-color={color} data-variant={variant}>{#if href}<a class="ccr-link" href={href} target={linkTarget} rel={rel(linkTarget)} aria-label={label}></a>{/if}{#if icon?.src}<Icon {...icon} />{/if}{#if external}<svg class="ccr-external" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d={arrow} /></svg>{/if}{@render children?.()}</div>
@@ -0,0 +1,10 @@
1
+ <!-- `::card-group` for `@comark/svelte`. -->
2
+ <script>
3
+ import { onMount } from "svelte";
4
+ import { addStyles } from "comark-cards/client";
5
+
6
+ let { children } = $props();
7
+ onMount(addStyles);
8
+ </script>
9
+
10
+ <div class="ccr-group">{@render children?.()}</div>
@@ -0,0 +1,6 @@
1
+ <!-- A card's title for `@comark/svelte`. -->
2
+ <script>
3
+ let { children } = $props();
4
+ </script>
5
+
6
+ <p class="ccr-title">{@render children?.()}</p>
@@ -0,0 +1,6 @@
1
+ <!-- Each cell of a `::columns` for `@comark/svelte`. -->
2
+ <script>
3
+ let { children } = $props();
4
+ </script>
5
+
6
+ <div class="ccr-column">{@render children?.()}</div>
@@ -0,0 +1,10 @@
1
+ <!-- `::columns` for `@comark/svelte`. -->
2
+ <script>
3
+ import { onMount } from "svelte";
4
+ import { addStyles } from "comark-cards/client";
5
+
6
+ let { cols = "2", children } = $props();
7
+ onMount(addStyles);
8
+ </script>
9
+
10
+ <div class="ccr-columns" data-cols={String(cols)}>{@render children?.()}</div>
@@ -0,0 +1,34 @@
1
+ import type { Component, Snippet } from "svelte";
2
+ import type { IconData } from "comark-icon";
3
+
4
+ export { colors, default, variants } from "comark-cards";
5
+ export type {
6
+ CardAttributes,
7
+ CardColor,
8
+ CardVariant,
9
+ CardsMeta,
10
+ CardsOptions,
11
+ ColumnsAttributes,
12
+ } from "comark-cards";
13
+
14
+ export declare const Card: Component<{
15
+ color?: string;
16
+ variant?: string;
17
+ to?: string;
18
+ target?: string;
19
+ external?: string | boolean;
20
+ label?: string;
21
+ icon?: IconData;
22
+ children?: Snippet;
23
+ }>;
24
+ export declare const CardTitle: Component<{ children?: Snippet }>;
25
+ export declare const CardGroup: Component<{ children?: Snippet }>;
26
+ export declare const Columns: Component<{ cols?: string | number; children?: Snippet }>;
27
+ export declare const Column: Component<{ children?: Snippet }>;
28
+ export declare const components: {
29
+ Card: typeof Card;
30
+ CardTitle: typeof CardTitle;
31
+ CardGroup: typeof CardGroup;
32
+ Columns: typeof Columns;
33
+ Column: typeof Column;
34
+ };
@@ -0,0 +1,11 @@
1
+ // `::card-group`, `::card`, and `::columns` for `@comark/svelte`. Pass `components` to it; the parse plugin comes from
2
+ // `comark-cards`.
3
+ import Card from "./Card.svelte";
4
+ import CardGroup from "./CardGroup.svelte";
5
+ import CardTitle from "./CardTitle.svelte";
6
+ import Column from "./Column.svelte";
7
+ import Columns from "./Columns.svelte";
8
+
9
+ export { colors, default, variants } from "comark-cards";
10
+ export { Card, CardGroup, CardTitle, Column, Columns };
11
+ export const components = { Card, CardTitle, CardGroup, Columns, Column };
package/dist/vue.d.mts ADDED
@@ -0,0 +1,116 @@
1
+ import _default, { CardAttributes, CardColor, CardVariant, CardsMeta, CardsOptions, ColumnsAttributes, colors, variants } from "./index.mjs";
2
+ import { PropType } from "vue";
3
+ import { IconData } from "comark-icon/vue";
4
+ //#region src/vue.d.ts
5
+ /** `::card` for `@comark/vue`. */
6
+ export declare const Card: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
7
+ color: {
8
+ type: StringConstructor;
9
+ default: string;
10
+ };
11
+ variant: StringConstructor;
12
+ to: StringConstructor;
13
+ target: StringConstructor;
14
+ external: (StringConstructor | BooleanConstructor)[];
15
+ label: StringConstructor;
16
+ icon: PropType<IconData>;
17
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
18
+ [key: string]: any;
19
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
20
+ color: {
21
+ type: StringConstructor;
22
+ default: string;
23
+ };
24
+ variant: StringConstructor;
25
+ to: StringConstructor;
26
+ target: StringConstructor;
27
+ external: (StringConstructor | BooleanConstructor)[];
28
+ label: StringConstructor;
29
+ icon: PropType<IconData>;
30
+ }>> & Readonly<{}>, {
31
+ color: string;
32
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
33
+ /** A card's title for `@comark/vue`. */
34
+ export declare const CardTitle: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
35
+ [key: string]: any;
36
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
37
+ /** `::card-group` for `@comark/vue`. */
38
+ export declare const CardGroup: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
39
+ [key: string]: any;
40
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
41
+ /** `::columns` for `@comark/vue`. */
42
+ export declare const Columns: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
43
+ cols: {
44
+ type: (StringConstructor | NumberConstructor)[];
45
+ default: string;
46
+ };
47
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
48
+ [key: string]: any;
49
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
50
+ cols: {
51
+ type: (StringConstructor | NumberConstructor)[];
52
+ default: string;
53
+ };
54
+ }>> & Readonly<{}>, {
55
+ cols: string | number;
56
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
57
+ /** Each cell of a `::columns` for `@comark/vue`. */
58
+ export declare const Column: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
59
+ [key: string]: any;
60
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
61
+ /** Pass these as `components` to `@comark/vue`. */
62
+ export declare const components: {
63
+ Card: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
64
+ color: {
65
+ type: StringConstructor;
66
+ default: string;
67
+ };
68
+ variant: StringConstructor;
69
+ to: StringConstructor;
70
+ target: StringConstructor;
71
+ external: (StringConstructor | BooleanConstructor)[];
72
+ label: StringConstructor;
73
+ icon: PropType<IconData>;
74
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
75
+ [key: string]: any;
76
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
77
+ color: {
78
+ type: StringConstructor;
79
+ default: string;
80
+ };
81
+ variant: StringConstructor;
82
+ to: StringConstructor;
83
+ target: StringConstructor;
84
+ external: (StringConstructor | BooleanConstructor)[];
85
+ label: StringConstructor;
86
+ icon: PropType<IconData>;
87
+ }>> & Readonly<{}>, {
88
+ color: string;
89
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
90
+ CardTitle: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
91
+ [key: string]: any;
92
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
93
+ CardGroup: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
94
+ [key: string]: any;
95
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
96
+ Columns: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
97
+ cols: {
98
+ type: (StringConstructor | NumberConstructor)[];
99
+ default: string;
100
+ };
101
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
102
+ [key: string]: any;
103
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
104
+ cols: {
105
+ type: (StringConstructor | NumberConstructor)[];
106
+ default: string;
107
+ };
108
+ }>> & Readonly<{}>, {
109
+ cols: string | number;
110
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
111
+ Column: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
112
+ [key: string]: any;
113
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
114
+ };
115
+ //#endregion
116
+ export { type CardAttributes, type CardColor, type CardVariant, type CardsMeta, type CardsOptions, type ColumnsAttributes, colors, _default as default, variants };
package/dist/vue.mjs ADDED
@@ -0,0 +1,104 @@
1
+ import { a as rel, i as isSafeHref, n as external, r as isExternalHref, t as addStyles } from "./client-C5acm8_C.mjs";
2
+ import src_default, { colors, variants } from "./index.mjs";
3
+ import { defineComponent, h, onMounted } from "vue";
4
+ import { Icon } from "comark-icon/vue";
5
+ //#region src/vue.ts
6
+ const arrow = () => h("svg", {
7
+ class: "ccr-external",
8
+ viewBox: "0 0 24 24",
9
+ width: "16",
10
+ height: "16",
11
+ fill: "none",
12
+ stroke: "currentColor",
13
+ "stroke-width": "2",
14
+ "stroke-linecap": "round",
15
+ "stroke-linejoin": "round",
16
+ "aria-hidden": "true"
17
+ }, [h("path", { d: external })]);
18
+ /** `::card` for `@comark/vue`. */
19
+ const Card = defineComponent({
20
+ name: "Card",
21
+ inheritAttrs: false,
22
+ props: {
23
+ color: {
24
+ type: String,
25
+ default: "primary"
26
+ },
27
+ variant: String,
28
+ to: String,
29
+ target: String,
30
+ external: [String, Boolean],
31
+ label: String,
32
+ icon: Object
33
+ },
34
+ setup(props, { slots }) {
35
+ onMounted(addStyles);
36
+ return () => h("div", {
37
+ class: "ccr",
38
+ "data-color": props.color,
39
+ "data-variant": props.variant
40
+ }, [
41
+ isSafeHref(props.to) ? h("a", {
42
+ class: "ccr-link",
43
+ href: props.to,
44
+ target: props.target || (isExternalHref(props.to) ? "_blank" : void 0),
45
+ rel: rel(props.target || (isExternalHref(props.to) ? "_blank" : void 0)),
46
+ "aria-label": props.label
47
+ }) : null,
48
+ props.icon?.src ? h(Icon, { ...props.icon }) : null,
49
+ props.external ? arrow() : null,
50
+ slots.default?.()
51
+ ]);
52
+ }
53
+ });
54
+ /** A card's title for `@comark/vue`. */
55
+ const CardTitle = defineComponent({
56
+ name: "CardTitle",
57
+ inheritAttrs: false,
58
+ setup(_, { slots }) {
59
+ return () => h("p", { class: "ccr-title" }, slots.default?.());
60
+ }
61
+ });
62
+ /** `::card-group` for `@comark/vue`. */
63
+ const CardGroup = defineComponent({
64
+ name: "CardGroup",
65
+ inheritAttrs: false,
66
+ setup(_, { slots }) {
67
+ onMounted(addStyles);
68
+ return () => h("div", { class: "ccr-group" }, slots.default?.());
69
+ }
70
+ });
71
+ /** `::columns` for `@comark/vue`. */
72
+ const Columns = defineComponent({
73
+ name: "Columns",
74
+ inheritAttrs: false,
75
+ props: { cols: {
76
+ type: [String, Number],
77
+ default: "2"
78
+ } },
79
+ setup(props, { slots }) {
80
+ onMounted(addStyles);
81
+ return () => h("div", {
82
+ class: "ccr-columns",
83
+ "data-cols": String(props.cols)
84
+ }, slots.default?.());
85
+ }
86
+ });
87
+ /** Each cell of a `::columns` for `@comark/vue`. */
88
+ const Column = defineComponent({
89
+ name: "Column",
90
+ inheritAttrs: false,
91
+ setup(_, { slots }) {
92
+ return () => h("div", { class: "ccr-column" }, slots.default?.());
93
+ }
94
+ });
95
+ /** Pass these as `components` to `@comark/vue`. */
96
+ const components = {
97
+ Card,
98
+ CardTitle,
99
+ CardGroup,
100
+ Columns,
101
+ Column
102
+ };
103
+ //#endregion
104
+ export { Card, CardGroup, CardTitle, Column, Columns, colors, components, src_default as default, variants };
package/package.json CHANGED
@@ -1,12 +1,93 @@
1
1
  {
2
2
  "name": "comark-cards",
3
- "version": "0.0.0",
4
- "description": "Placeholder. The first release is published from https://github.com/onmax/comark-plugins.",
3
+ "version": "0.7.1",
4
+ "description": "Cards, link cards, and columns in Nuxt UI's syntax, with no script. A Comark plugin.",
5
+ "keywords": [
6
+ "card",
7
+ "cards",
8
+ "columns",
9
+ "comark",
10
+ "markdown",
11
+ "mdc",
12
+ "plugin"
13
+ ],
14
+ "homepage": "https://comark-ui.onmax.me/cards",
15
+ "bugs": "https://github.com/onmax/comark-ui/issues",
5
16
  "license": "MIT",
6
17
  "author": "onmax",
7
18
  "repository": {
8
19
  "type": "git",
9
- "url": "git+https://github.com/onmax/comark-plugins.git",
20
+ "url": "git+https://github.com/onmax/comark-ui.git",
10
21
  "directory": "packages/cards"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "type": "module",
27
+ "sideEffects": [
28
+ "*.css"
29
+ ],
30
+ "exports": {
31
+ ".": "./dist/index.mjs",
32
+ "./client": "./dist/client.mjs",
33
+ "./html": "./dist/html.mjs",
34
+ "./react": "./dist/react.mjs",
35
+ "./style.css": "./dist/style.css",
36
+ "./svelte": {
37
+ "types": "./dist/svelte/index.d.ts",
38
+ "svelte": "./dist/svelte/index.js",
39
+ "default": "./dist/svelte/index.js"
40
+ },
41
+ "./vue": "./dist/vue.mjs",
42
+ "./package.json": "./package.json"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "provenance": true
47
+ },
48
+ "dependencies": {
49
+ "comark-icon": "^0.7.1"
50
+ },
51
+ "devDependencies": {
52
+ "@comark/html": "^0.8.1",
53
+ "@comark/react": "^0.8.1",
54
+ "@comark/vue": "^0.8.1",
55
+ "@types/react": "^19.3.0",
56
+ "@types/react-dom": "^19.3.0",
57
+ "comark": "^0.8.1",
58
+ "react": "^19.3.1",
59
+ "react-dom": "^19.3.1",
60
+ "svelte": "^5.57.2",
61
+ "vue": "^3.5.44",
62
+ "comark-zoom": "0.7.1"
63
+ },
64
+ "peerDependencies": {
65
+ "@comark/html": "^0.8.0",
66
+ "comark": "^0.8.0",
67
+ "react": ">=18",
68
+ "svelte": ">=5",
69
+ "vue": ">=3.5"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "@comark/html": {
73
+ "optional": true
74
+ },
75
+ "react": {
76
+ "optional": true
77
+ },
78
+ "svelte": {
79
+ "optional": true
80
+ },
81
+ "vue": {
82
+ "optional": true
83
+ }
84
+ },
85
+ "engines": {
86
+ "node": ">=22.12.0"
87
+ },
88
+ "scripts": {
89
+ "build": "tsdown",
90
+ "dev": "tsdown --watch",
91
+ "doctor": "vite-doctor . --no-cache --max-warnings 0 --format text"
11
92
  }
12
- }
93
+ }