@mirrordown/sat-tab-icons 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +237 -4
- package/dist/index.d.mts +16 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +59 -0
- package/dist/index.mjs.map +1 -0
- package/dist/tab-icons.css +46 -0
- package/package.json +34 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
<sub>2026-10-28</sub>
|
|
5
|
+
|
|
6
|
+
- *(minor)*
|
|
7
|
+
Add the tab-icons plugin set, a port of vitepress-plugin-group-icons for Mirrordown's tabs: each tab label gets the icon its text matches (a keyword such as `npm`, `pnpm` or `vite` as a whole word, the longest winning, else a file name or extension such as `package.json` or `.ts`), or the one a `[!keyword]` marker at its start names; `[!none]` turns matching off for one label. The icon is an inline `<svg class="markdown-tab-icon" aria-hidden="true">` before the label's text, and the marker comes out of the label and its panel's `aria-label`. It ships group-icons' built-in icons (from VSCode Icons and SVG Logos); the `icons` option adds or replaces them, with light and dark pairs, and `builtin` and `match` turn the built-in icons and matching off. The markdown-it, remark and Sätteri plugins emit identical HTML.
|
package/README.md
CHANGED
|
@@ -1,9 +1,242 @@
|
|
|
1
1
|
# @mirrordown/sat-tab-icons
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Part of [Mirrordown](https://github.com/mirrordown/mirrordown) — a suite of markdown syntax extensions for the unified, markdown-it and Sätteri ecosystems.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A Sätteri plugin for the `tab-icons` syntax extension.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Overview
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
The `tab-icons` plugin adds an icon to the labels of [tab groups](/guide/plugins/tabs). A label gets the icon its text matches (`npm`, `pnpm`, `vite.config.ts`), or the one a `[!keyword]` marker at its start names. The icon is an inline `<svg>` before the label's text:
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<label class="markdown-tabs-label">
|
|
13
|
+
<input type="radio" name="..." checked>
|
|
14
|
+
<svg class="markdown-tab-icon" aria-hidden="true" viewBox="0 0 32 32">...</svg>
|
|
15
|
+
npm
|
|
16
|
+
</label>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
It's a port of [vitepress-plugin-group-icons](https://github.com/yuyinws/vitepress-plugin-group-icons), which does the same for VitePress code groups, with its matching rules and its built-in icons. The icons come from [VSCode Icons](https://github.com/vscode-icons/vscode-icons) (MIT) and [SVG Logos](https://github.com/gilbarbara/logos) (CC0). In renderers without the plugin, a label's text matches nothing, and a marker stays a line of text.
|
|
20
|
+
|
|
21
|
+
## Syntax
|
|
22
|
+
|
|
23
|
+
### Matching a label
|
|
24
|
+
|
|
25
|
+
Write the tool or file the tab is about as its label. Package managers, runtimes, frameworks and build tools match by name:
|
|
26
|
+
|
|
27
|
+
````markdown
|
|
28
|
+
% npm
|
|
29
|
+
> ```sh
|
|
30
|
+
> npm install @mirrordown/remd-tabs
|
|
31
|
+
> ```
|
|
32
|
+
% pnpm
|
|
33
|
+
> ```sh
|
|
34
|
+
> pnpm add @mirrordown/remd-tabs
|
|
35
|
+
> ```
|
|
36
|
+
% yarn
|
|
37
|
+
> ```sh
|
|
38
|
+
> yarn add @mirrordown/remd-tabs
|
|
39
|
+
> ```
|
|
40
|
+
% bun
|
|
41
|
+
> ```sh
|
|
42
|
+
> bun add @mirrordown/remd-tabs
|
|
43
|
+
> ```
|
|
44
|
+
````
|
|
45
|
+
|
|
46
|
+
File names match by name, then by extension:
|
|
47
|
+
|
|
48
|
+
````markdown
|
|
49
|
+
% vite.config.ts
|
|
50
|
+
> ```ts
|
|
51
|
+
> export default defineConfig({});
|
|
52
|
+
> ```
|
|
53
|
+
% package.json
|
|
54
|
+
> ```json
|
|
55
|
+
> { "type": "module" }
|
|
56
|
+
> ```
|
|
57
|
+
% .env.local
|
|
58
|
+
> ```sh
|
|
59
|
+
> API_URL=http://localhost:3000
|
|
60
|
+
> ```
|
|
61
|
+
% main.rs
|
|
62
|
+
> ```rs
|
|
63
|
+
> fn main() {}
|
|
64
|
+
> ```
|
|
65
|
+
````
|
|
66
|
+
|
|
67
|
+
### Naming the icon
|
|
68
|
+
|
|
69
|
+
Start a label with `[!keyword]` to choose its icon, the way a [GitHub alert](/guide/plugins/github-alerts) names its type. The marker comes out of the label; the keyword is any built-in or configured icon's key, in any case, including extensions such as `[!.ts]`. A label that is only a marker reads as its keyword.
|
|
70
|
+
|
|
71
|
+
````markdown
|
|
72
|
+
% [!npm] Install
|
|
73
|
+
> ```sh
|
|
74
|
+
> npm install @mirrordown/remd-tabs
|
|
75
|
+
> ```
|
|
76
|
+
% [!.ts] Configure
|
|
77
|
+
> ```ts
|
|
78
|
+
> export default { tabs: true };
|
|
79
|
+
> ```
|
|
80
|
+
% [!vite]
|
|
81
|
+
> ```sh
|
|
82
|
+
> vite build
|
|
83
|
+
> ```
|
|
84
|
+
````
|
|
85
|
+
|
|
86
|
+
`[!none]` turns matching off for one label. A keyword that names no icon isn't a marker: `[!foo] Install` stays as written.
|
|
87
|
+
|
|
88
|
+
## How a label is matched
|
|
89
|
+
|
|
90
|
+
Without a marker, the label's text is matched in this order, ignoring case:
|
|
91
|
+
|
|
92
|
+
1. **A keyword**, as a whole word: `vite` matches `Vite 8` and `vite.config.ts`, but not `invite`. A keyword may run into a `js`, `rc` or `ignore` suffix (`eslintrc`), and a camelCase label is split into words first, so `VitePress` matches `vite`.
|
|
93
|
+
2. **A file name or extension** the label ends with, `.ts` in `main.ts`; else one inside it, `.env` in `.env.local`.
|
|
94
|
+
|
|
95
|
+
When several keywords match, the **longest** wins, wherever it is in the label: `npm or pnpm` and `pnpm or npm` both get pnpm's icon. A keyword beats an extension, so `deno.json` gets Deno's icon and `tsconfig.json` gets TypeScript's config icon rather than JSON's. A label that matches nothing gets no icon and stays as it is.
|
|
96
|
+
|
|
97
|
+
Matching works on words, not meaning, so a label can match by accident: `Next steps` gets Next.js's icon. Write `[!none]` before such a label, or turn matching off with the `match` option and name every icon with a marker.
|
|
98
|
+
|
|
99
|
+
````markdown
|
|
100
|
+
% npm or pnpm
|
|
101
|
+
> Two keywords: the longer one, `pnpm`, wins.
|
|
102
|
+
% Vue + React
|
|
103
|
+
> The longer keyword wins even when it comes second.
|
|
104
|
+
% VitePress
|
|
105
|
+
> A camelCase label: `Vite` is a word of it.
|
|
106
|
+
% Next steps
|
|
107
|
+
> A false match: `next` is a whole word.
|
|
108
|
+
% [!none] Next steps
|
|
109
|
+
> Matching turned off for one label.
|
|
110
|
+
% Invitations
|
|
111
|
+
> No match: `vite` isn't a whole word here.
|
|
112
|
+
% [!foo] Install
|
|
113
|
+
> No icon is named `foo`, so this isn't a marker.
|
|
114
|
+
````
|
|
115
|
+
|
|
116
|
+
### Built-in icons
|
|
117
|
+
|
|
118
|
+
| Kind | Keys |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| Package managers and runtimes | `npm` `pnpm` `yarn` `bun` `bunx` `deno` |
|
|
121
|
+
| Frameworks | `angular` `astro` `ember` `marko` `next` `nuxt` `qwik` `react` `solid` `svelte` `vue` |
|
|
122
|
+
| Tools | `esbuild` `eslint` `gradle` `oxc` `oxfmt` `oxlint` `prisma` `rolldown` `rollup` `tailwind` `tailwindcss` `unocss` `vite` `vitest` `vscode` `webpack` |
|
|
123
|
+
| Files | `editorconfig` `gitattributes` `gitignore` `tsconfig` `package.json` `uno.config.ts` `.env` |
|
|
124
|
+
| Extensions | `.c` `.cjs` `.cpp` `.cs` `.css` `.cts` `.dart` `.gjs` `.go` `.groovy` `.gts` `.html` `.ico` `.ini` `.java` `.js` `.json` `.jsp` `.jsx` `.kt` `.less` `.lua` `.md` `.mjs` `.mts` `.php` `.py` `.r` `.rb` `.rs` `.sass` `.scala` `.scss` `.sln` `.swift` `.toml` `.ts` `.tsx` `.wasm` `.yaml` `.yml` |
|
|
125
|
+
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
All options are passed when registering the plugin.
|
|
129
|
+
|
|
130
|
+
### Add or replace icons
|
|
131
|
+
|
|
132
|
+
`icons` maps keys to SVG markup. A key with a `.` is matched as a file name or extension, any other key as a keyword, and a key that is also built in replaces that icon. Your file names and extensions are tried before the built-in ones, so `.vitepress` matches `.vitepress/config.ts` before `.ts` does. Icons from [Iconify](https://icon-sets.iconify.design/) work well: copy an icon's SVG.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
satteriTabIcons({
|
|
136
|
+
icons: {
|
|
137
|
+
docker: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">...</svg>`,
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
An icon drawn for one background can take a `{ light, dark }` pair instead: both render, and the stylesheet shows the one for the color scheme.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
icons: {
|
|
146
|
+
".vitepress": {
|
|
147
|
+
light: `<svg ...>...</svg>`,
|
|
148
|
+
dark: `<svg ...>...</svg>`,
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Other options
|
|
154
|
+
|
|
155
|
+
| Option | Default | |
|
|
156
|
+
| --- | --- | --- |
|
|
157
|
+
| `builtin` | `true` | `false` leaves out the built-in icons, so only yours match. |
|
|
158
|
+
| `match` | `true` | `false` turns matching off: only labels with a marker get an icon. |
|
|
159
|
+
| `containerClass` | `"markdown-tabs"` | The tab groups' class, if you changed the tabs plugin's. |
|
|
160
|
+
|
|
161
|
+
## Accessibility
|
|
162
|
+
|
|
163
|
+
The icon is `aria-hidden`: the label's text names the tab, and the icon only decorates it. The marker comes out of the label and out of its panel's `aria-label`, so a screen reader reads `Install`, not `[!npm] Install`.
|
|
164
|
+
|
|
165
|
+
## Customization
|
|
166
|
+
|
|
167
|
+
The stylesheet keeps its rules in `@layer markdown-tab-icons`, so any unlayered CSS you write overrides them. An icon fills a square as tall as the label's capital letters (`1cap`), with its drawing fitted inside, so it scales with the label's font.
|
|
168
|
+
|
|
169
|
+
```css
|
|
170
|
+
.markdown-tab-icon {
|
|
171
|
+
inline-size: 1.2cap;
|
|
172
|
+
block-size: 1.2cap;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Icons with light and dark variants follow the reader's preferred color scheme. A site with its own theme switch can show them by it instead, with unlayered rules:
|
|
177
|
+
|
|
178
|
+
```css
|
|
179
|
+
:root[data-theme="light"] .markdown-tab-icon[data-scheme="light"],
|
|
180
|
+
:root[data-theme="dark"] .markdown-tab-icon[data-scheme="dark"] {
|
|
181
|
+
display: revert;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
:root[data-theme="light"] .markdown-tab-icon[data-scheme="dark"],
|
|
185
|
+
:root[data-theme="dark"] .markdown-tab-icon[data-scheme="light"] {
|
|
186
|
+
display: none;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The plugin decorates the tab groups that the [tabs](/guide/plugins/tabs) plugin builds, so install that too.
|
|
191
|
+
|
|
192
|
+
## Install
|
|
193
|
+
|
|
194
|
+
```sh
|
|
195
|
+
npm install @mirrordown/sat-tab-icons
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Standalone
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { markdownToHtml } from "satteri";
|
|
202
|
+
import { satteriTabs } from "@mirrordown/sat-tabs";
|
|
203
|
+
import { satteriTabIcons } from "@mirrordown/sat-tab-icons";
|
|
204
|
+
import "@mirrordown/sat-tabs/tabs.css";
|
|
205
|
+
import "@mirrordown/sat-tab-icons/tab-icons.css";
|
|
206
|
+
|
|
207
|
+
const { html } = markdownToHtml(source, {
|
|
208
|
+
mdastPlugins: [satteriTabs()],
|
|
209
|
+
hastPlugins: [satteriTabIcons()],
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Astro
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
// astro.config.ts
|
|
217
|
+
import { defineConfig } from "astro/config";
|
|
218
|
+
import { satteri } from "@astrojs/markdown-satteri";
|
|
219
|
+
import { satteriTabs } from "@mirrordown/sat-tabs";
|
|
220
|
+
import { satteriTabIcons } from "@mirrordown/sat-tab-icons";
|
|
221
|
+
|
|
222
|
+
export default defineConfig({
|
|
223
|
+
markdown: {
|
|
224
|
+
processor: satteri({
|
|
225
|
+
mdastPlugins: [satteriTabs()],
|
|
226
|
+
hastPlugins: [satteriTabIcons()],
|
|
227
|
+
}),
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// In your layout:
|
|
232
|
+
import "@mirrordown/sat-tabs/tabs.css";
|
|
233
|
+
import "@mirrordown/sat-tab-icons/tab-icons.css";
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Documentation
|
|
237
|
+
|
|
238
|
+
Full documentation, more examples, and configuration options: [github.com/mirrordown/mirrordown](https://github.com/mirrordown/mirrordown) (dedicated docs site coming soon).
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT © [Drake Costa](https://saeris.gg)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TabIcon, TabIconsOptions, TabIconsOptions as TabIconsOptions$1 } from "@mirrordown/core/tab-icons";
|
|
2
|
+
import { HastVisitorInstance, PluginFactoryContext } from "satteri";
|
|
3
|
+
type AfterHook = NonNullable<HastVisitorInstance["after"]>;
|
|
4
|
+
/**
|
|
5
|
+
* The plugin's definition: only the keys it defines, with a synchronous hook, so `markdownToHtml` returns its result directly.
|
|
6
|
+
*/
|
|
7
|
+
export interface TabIconsPlugin {
|
|
8
|
+
name: string;
|
|
9
|
+
after: (...args: Parameters<AfterHook>) => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Sätteri plugin that adds an icon to each tab label of `@mirrordown/sat-tabs`: the one a `[!keyword]` marker at the start of the label names (`% [!npm] Install`), or, without a marker, the one the label's text matches (`% npm`, `% vite.config.ts`), as vitepress-plugin-group-icons matches code-group labels. The icon is an inline `<svg class="markdown-tab-icon" aria-hidden="true">` before the label's text, as a `raw` node, so it reaches the HTML exactly as the markdown-it and rehype plugins write it; the marker comes out of the label and the panel's `aria-label`. The matching and the icons are shared through `@mirrordown/core`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const satteriTabIcons: (options?: TabIconsOptions$1) => ((ctx: PluginFactoryContext) => TabIconsPlugin);
|
|
15
|
+
export type { TabIcon, TabIconsOptions };
|
|
16
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":["TabIconsOptions"],"sources":["../src/index.ts"],"mappings":";;KAaK,YAAY,YAAY;;;;iBAOZ;EACf;EACA,WAAW,MAAM,WAAW;;;;;qBAqBjB,kBACX,UAASA,wBACN,KAAK,yBAAyB"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createTabIconResolver, tabIconMarkup } from "@mirrordown/core/tab-icons";
|
|
2
|
+
import { defineHastPlugin } from "satteri";
|
|
3
|
+
// src/index.ts
|
|
4
|
+
const hasClass = (node, name) => {
|
|
5
|
+
if (node?.type !== `element`) return false;
|
|
6
|
+
const value = node.properties.className;
|
|
7
|
+
return Array.isArray(value) && value.includes(name);
|
|
8
|
+
};
|
|
9
|
+
const childrenWithClass = (parent, name) => (parent?.children ?? []).filter((child) => hasClass(child, name));
|
|
10
|
+
/**
|
|
11
|
+
* Sätteri plugin that adds an icon to each tab label of `@mirrordown/sat-tabs`: the one a `[!keyword]` marker at the start of the label names (`% [!npm] Install`), or, without a marker, the one the label's text matches (`% npm`, `% vite.config.ts`), as vitepress-plugin-group-icons matches code-group labels. The icon is an inline `<svg class="markdown-tab-icon" aria-hidden="true">` before the label's text, as a `raw` node, so it reaches the HTML exactly as the markdown-it and rehype plugins write it; the marker comes out of the label and the panel's `aria-label`. The matching and the icons are shared through `@mirrordown/core`.
|
|
12
|
+
*/
|
|
13
|
+
const satteriTabIcons = (options = {}) => {
|
|
14
|
+
const resolve = createTabIconResolver(options);
|
|
15
|
+
const { containerClass = `markdown-tabs` } = options;
|
|
16
|
+
return () => defineHastPlugin({
|
|
17
|
+
name: `tab-icons`,
|
|
18
|
+
after(root, ctx) {
|
|
19
|
+
const walk = (children) => {
|
|
20
|
+
for (const node of children) {
|
|
21
|
+
if (node.type !== `element`) continue;
|
|
22
|
+
if (hasClass(node, containerClass)) decorate(node);
|
|
23
|
+
walk(node.children);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const decorate = (group) => {
|
|
27
|
+
const [labelsRow] = childrenWithClass(group, `${containerClass}-labels`);
|
|
28
|
+
const [panelsRow] = childrenWithClass(group, `${containerClass}-panels`);
|
|
29
|
+
const labels = childrenWithClass(labelsRow, `${containerClass}-label`);
|
|
30
|
+
const panels = childrenWithClass(panelsRow, `${containerClass}-panel`);
|
|
31
|
+
for (const [index, label] of labels.entries()) {
|
|
32
|
+
const panel = panels[index];
|
|
33
|
+
const plain = panel?.properties.ariaLabel;
|
|
34
|
+
const { icon, marker, label: text } = resolve(typeof plain === `string` ? plain : ``);
|
|
35
|
+
if (marker) {
|
|
36
|
+
const first = label.children[0];
|
|
37
|
+
if (first?.type === `text`) {
|
|
38
|
+
const rest = first.value.slice(marker.length) || text;
|
|
39
|
+
if (rest === ``) ctx.removeNode(first);
|
|
40
|
+
else ctx.replaceNode(first, {
|
|
41
|
+
type: `text`,
|
|
42
|
+
value: rest
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (panel) ctx.setProperty(panel, `ariaLabel`, text);
|
|
46
|
+
}
|
|
47
|
+
if (icon) ctx.prependChild(label, {
|
|
48
|
+
type: `raw`,
|
|
49
|
+
value: tabIconMarkup(icon)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
walk(root.children);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
export { satteriTabIcons };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n createTabIconResolver,\n tabIconMarkup,\n type TabIconsOptions\n} from \"@mirrordown/core/tab-icons\";\nimport {\n defineHastPlugin,\n type HastVisitorInstance,\n type PluginFactoryContext\n} from \"satteri\";\n\nexport type { TabIcon, TabIconsOptions } from \"@mirrordown/core/tab-icons\";\n\ntype AfterHook = NonNullable<HastVisitorInstance[\"after\"]>;\ntype HastNode = Parameters<AfterHook>[0][\"children\"][number];\ntype Element = Extract<HastNode, { type: \"element\" }>;\n\n/**\n * The plugin's definition: only the keys it defines, with a synchronous hook, so `markdownToHtml` returns its result directly.\n */\nexport interface TabIconsPlugin {\n name: string;\n after: (...args: Parameters<AfterHook>) => void;\n}\n\nconst hasClass = (node: HastNode | undefined, name: string): boolean => {\n if (node?.type !== `element`) return false;\n const value = node.properties.className;\n return Array.isArray(value) && value.includes(name);\n};\n\n// A group's own children with a class: its labels or panels (a group nested in a panel has its own).\nconst childrenWithClass = (\n parent: Element | undefined,\n name: string\n): Element[] =>\n (parent?.children ?? []).filter((child): child is Element =>\n hasClass(child, name)\n );\n\n/**\n * Sätteri plugin that adds an icon to each tab label of `@mirrordown/sat-tabs`: the one a `[!keyword]` marker at the start of the label names (`% [!npm] Install`), or, without a marker, the one the label's text matches (`% npm`, `% vite.config.ts`), as vitepress-plugin-group-icons matches code-group labels. The icon is an inline `<svg class=\"markdown-tab-icon\" aria-hidden=\"true\">` before the label's text, as a `raw` node, so it reaches the HTML exactly as the markdown-it and rehype plugins write it; the marker comes out of the label and the panel's `aria-label`. The matching and the icons are shared through `@mirrordown/core`.\n */\nexport const satteriTabIcons = (\n options: TabIconsOptions = {}\n): ((ctx: PluginFactoryContext) => TabIconsPlugin) => {\n const resolve = createTabIconResolver(options);\n const { containerClass = `markdown-tabs` } = options;\n return () =>\n defineHastPlugin({\n name: `tab-icons`,\n after(root, ctx) {\n const walk = (children: readonly HastNode[]): void => {\n for (const node of children) {\n if (node.type !== `element`) continue;\n if (hasClass(node, containerClass)) decorate(node);\n walk(node.children);\n }\n };\n const decorate = (group: Element): void => {\n const [labelsRow] = childrenWithClass(\n group,\n `${containerClass}-labels`\n );\n const [panelsRow] = childrenWithClass(\n group,\n `${containerClass}-panels`\n );\n const labels = childrenWithClass(\n labelsRow,\n `${containerClass}-label`\n );\n const panels = childrenWithClass(\n panelsRow,\n `${containerClass}-panel`\n );\n for (const [index, label] of labels.entries()) {\n const panel = panels[index];\n const plain = panel?.properties.ariaLabel;\n const {\n icon,\n marker,\n label: text\n } = resolve(typeof plain === `string` ? plain : ``);\n if (marker) {\n const first = label.children[0];\n if (first?.type === `text`) {\n const rest = first.value.slice(marker.length) || text;\n if (rest === ``) ctx.removeNode(first);\n else ctx.replaceNode(first, { type: `text`, value: rest });\n }\n if (panel) ctx.setProperty(panel, `ariaLabel`, text);\n }\n if (icon)\n ctx.prependChild(label, {\n type: `raw`,\n value: tabIconMarkup(icon)\n });\n }\n };\n walk(root.children);\n }\n });\n};\n"],"mappings":";;;AAyBA,MAAM,YAAY,MAA4B,SAA0B;CACtE,IAAI,MAAM,SAAS,WAAW,OAAO;CACrC,MAAM,QAAQ,KAAK,WAAW;CAC9B,OAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,IAAI;AACpD;AAGA,MAAM,qBACJ,QACA,UAEC,QAAQ,YAAY,CAAC,EAAA,CAAG,QAAQ,UAC/B,SAAS,OAAO,IAAI,CACtB;;;;AAKF,MAAa,mBACX,UAA2B,CAAC,MACwB;CACpD,MAAM,UAAU,sBAAsB,OAAO;CAC7C,MAAM,EAAE,iBAAiB,oBAAoB;CAC7C,aACE,iBAAiB;EACf,MAAM;EACN,MAAM,MAAM,KAAK;GACf,MAAM,QAAQ,aAAwC;IACpD,KAAK,MAAM,QAAQ,UAAU;KAC3B,IAAI,KAAK,SAAS,WAAW;KAC7B,IAAI,SAAS,MAAM,cAAc,GAAG,SAAS,IAAI;KACjD,KAAK,KAAK,QAAQ;IACpB;GACF;GACA,MAAM,YAAY,UAAyB;IACzC,MAAM,CAAC,aAAa,kBAClB,OACA,GAAG,eAAe,QACpB;IACA,MAAM,CAAC,aAAa,kBAClB,OACA,GAAG,eAAe,QACpB;IACA,MAAM,SAAS,kBACb,WACA,GAAG,eAAe,OACpB;IACA,MAAM,SAAS,kBACb,WACA,GAAG,eAAe,OACpB;IACA,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,GAAG;KAC7C,MAAM,QAAQ,OAAO;KACrB,MAAM,QAAQ,OAAO,WAAW;KAChC,MAAM,EACJ,MACA,QACA,OAAO,SACL,QAAQ,OAAO,UAAU,WAAW,QAAQ,EAAE;KAClD,IAAI,QAAQ;MACV,MAAM,QAAQ,MAAM,SAAS;MAC7B,IAAI,OAAO,SAAS,QAAQ;OAC1B,MAAM,OAAO,MAAM,MAAM,MAAM,OAAO,MAAM,KAAK;OACjD,IAAI,SAAS,IAAI,IAAI,WAAW,KAAK;YAChC,IAAI,YAAY,OAAO;QAAE,MAAM;QAAQ,OAAO;OAAK,CAAC;MAC3D;MACA,IAAI,OAAO,IAAI,YAAY,OAAO,aAAa,IAAI;KACrD;KACA,IAAI,MACF,IAAI,aAAa,OAAO;MACtB,MAAM;MACN,OAAO,cAAc,IAAI;KAC3B,CAAC;IACL;GACF;GACA,KAAK,KAAK,QAAQ;EACpB;CACF,CAAC;AACL"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tab icons — default stylesheet
|
|
3
|
+
------------------------------
|
|
4
|
+
All rules live inside @layer markdown-tab-icons so any unlayered consumer
|
|
5
|
+
CSS overrides them without needing !important.
|
|
6
|
+
|
|
7
|
+
An icon fills a square as tall as the label's capital letters (1cap), its
|
|
8
|
+
drawing fitted inside, so it scales with the label's font. The square has an
|
|
9
|
+
explicit width because browsers leave an <svg> of auto width out of the
|
|
10
|
+
label's own width, which ran the text into its padding. Icons with light and dark
|
|
11
|
+
variants render both, marked data-scheme="light" and "dark"; these rules
|
|
12
|
+
show one by the reader's preferred color scheme. A site with its own theme
|
|
13
|
+
switch can show them by it instead, unlayered, e.g.:
|
|
14
|
+
|
|
15
|
+
:root[data-theme="light"] .markdown-tab-icon[data-scheme="light"],
|
|
16
|
+
:root[data-theme="dark"] .markdown-tab-icon[data-scheme="dark"] {
|
|
17
|
+
display: revert;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
:root[data-theme="light"] .markdown-tab-icon[data-scheme="dark"],
|
|
21
|
+
:root[data-theme="dark"] .markdown-tab-icon[data-scheme="light"] {
|
|
22
|
+
display: none;
|
|
23
|
+
}
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
@layer markdown-tab-icons {
|
|
27
|
+
.markdown-tab-icon {
|
|
28
|
+
flex: none;
|
|
29
|
+
inline-size: 1cap;
|
|
30
|
+
block-size: 1cap;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.markdown-tab-icon[data-scheme="dark"] {
|
|
34
|
+
display: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@media (prefers-color-scheme: dark) {
|
|
38
|
+
.markdown-tab-icon[data-scheme="light"] {
|
|
39
|
+
display: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.markdown-tab-icon[data-scheme="dark"] {
|
|
43
|
+
display: revert;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mirrordown/sat-tab-icons",
|
|
3
|
-
"version": "0.0.0",
|
|
4
3
|
"description": "A Sätteri plugin to add icons to tab labels: matched from the label, or named with a [!keyword] marker.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Drake Costa <drake@saeris.io> (https://saeris.gg)",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/mirrordown/mirrordown.git"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"development": "./src/index.ts",
|
|
18
|
+
"types": "./dist/index.d.mts",
|
|
19
|
+
"import": "./dist/index.mjs"
|
|
20
|
+
},
|
|
21
|
+
"./*.css": "./dist/*.css"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@mirrordown/core": "^0.1.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"satteri": "^0.11.5"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"satteri": "^0.10.5 || ^0.11.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=24.0.0"
|
|
34
|
+
},
|
|
35
|
+
"bumpy": {
|
|
36
|
+
"publishCommand": [
|
|
37
|
+
"node ../../scripts/sync-jsr-version.mjs",
|
|
38
|
+
"node ../../scripts/npm-publish.mjs",
|
|
39
|
+
"node ../../scripts/jsr-resolve-deps.mjs",
|
|
40
|
+
"node ../../scripts/jsr-publish.mjs"
|
|
41
|
+
]
|
|
10
42
|
}
|
|
11
|
-
}
|
|
43
|
+
}
|