@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 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
- 🐣 This package name has been claimed with [fledgling](https://github.com/dmno-dev/fledgling) for [https://github.com/mirrordown/mirrordown](https://github.com/mirrordown/mirrordown), and a real release will land here soon.
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
- This is a placeholder — there is nothing to install yet.
5
+ A Sätteri plugin for the `tab-icons` syntax extension.
6
6
 
7
- ---
7
+ ## Overview
8
8
 
9
- Want to claim a name of your own? Run `npx fledgling add <name> --new --yes` — it reserves the name on npm and can set up token-less (OIDC trusted) publishing for when you are ready.
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)
@@ -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
+ }