kempo-ui 0.0.75 → 0.0.77
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/.github/skills/get-icon/SKILL.md +144 -0
- package/.github/skills/highlight-code/SKILL.md +57 -0
- package/README.md +83 -1
- package/bin/get_icon.js +106 -0
- package/bin/highlight_code.js +39 -0
- package/bin/icon.js +131 -0
- package/bin/list_icons.js +70 -0
- package/dist/components/Resize.js +1 -0
- package/dist/components/Split.js +7 -3
- package/dist/components/Tree.js +1 -1
- package/docs/components/icon.html +28 -6
- package/docs/components/photo-viewer.html +7 -7
- package/docs/components/tree.html +26 -6
- package/docs/icons/account-circle.svg +1 -1
- package/docs/icons/add.svg +1 -1
- package/docs/icons/arrow-circle.svg +1 -1
- package/docs/icons/arrow-line.svg +1 -1
- package/docs/icons/arrow.svg +1 -1
- package/docs/icons/arrow_drop_down.svg +1 -1
- package/docs/icons/cards.svg +1 -1
- package/docs/icons/check.svg +1 -1
- package/docs/icons/chevron-double.svg +1 -1
- package/docs/icons/chevron-line.svg +1 -1
- package/docs/icons/chevron.svg +1 -1
- package/docs/icons/close.svg +1 -1
- package/docs/icons/code.svg +1 -1
- package/docs/icons/code_blocks.svg +1 -1
- package/docs/icons/copy.svg +1 -1
- package/docs/icons/delete.svg +1 -1
- package/docs/icons/drag-handle.svg +1 -1
- package/docs/icons/edit.svg +1 -1
- package/docs/icons/error.svg +1 -1
- package/docs/icons/export-file.svg +1 -1
- package/docs/icons/file-text.svg +1 -1
- package/docs/icons/file.svg +1 -1
- package/docs/icons/filter.svg +1 -1
- package/docs/icons/folder-open.svg +1 -1
- package/docs/icons/folder.svg +1 -1
- package/docs/icons/format_align_center.svg +1 -1
- package/docs/icons/format_align_justify.svg +1 -1
- package/docs/icons/format_align_left.svg +1 -1
- package/docs/icons/format_align_right.svg +1 -1
- package/docs/icons/format_bold.svg +1 -1
- package/docs/icons/format_clear.svg +1 -1
- package/docs/icons/format_color_fill.svg +1 -1
- package/docs/icons/format_color_text.svg +1 -1
- package/docs/icons/format_h1.svg +1 -1
- package/docs/icons/format_h2.svg +1 -1
- package/docs/icons/format_h3.svg +1 -1
- package/docs/icons/format_h4.svg +1 -1
- package/docs/icons/format_h5.svg +1 -1
- package/docs/icons/format_h6.svg +1 -1
- package/docs/icons/format_italic.svg +1 -1
- package/docs/icons/format_list_bulleted.svg +1 -1
- package/docs/icons/format_list_numbered.svg +1 -1
- package/docs/icons/format_paragraph.svg +1 -1
- package/docs/icons/format_underlined.svg +1 -1
- package/docs/icons/github-mark.svg +1 -1
- package/docs/icons/help.svg +1 -1
- package/docs/icons/hide.svg +1 -1
- package/docs/icons/image.svg +1 -1
- package/docs/icons/label.svg +1 -1
- package/docs/icons/license.svg +1 -1
- package/docs/icons/link.svg +1 -1
- package/docs/icons/menu.svg +1 -1
- package/docs/icons/mode-auto.svg +1 -1
- package/docs/icons/mode-dark.svg +1 -1
- package/docs/icons/mode-light.svg +1 -1
- package/docs/icons/more_vert.svg +1 -1
- package/docs/icons/settings.svg +1 -1
- package/docs/icons/show.svg +1 -1
- package/docs/icons/strikethrough_s.svg +1 -1
- package/docs/icons/table-visibility.svg +1 -1
- package/docs/icons/table.svg +1 -1
- package/docs/icons/warning.svg +1 -1
- package/docs/icons/wysiwyg.svg +1 -1
- package/docs/src/components/Resize.js +1 -0
- package/docs/src/components/Split.js +7 -3
- package/docs/src/components/Tree.js +1 -1
- package/icons/account-circle.svg +1 -1
- package/icons/add.svg +1 -1
- package/icons/arrow-circle.svg +1 -1
- package/icons/arrow-line.svg +1 -1
- package/icons/arrow.svg +1 -1
- package/icons/arrow_drop_down.svg +1 -1
- package/icons/cards.svg +1 -1
- package/icons/check.svg +1 -1
- package/icons/chevron-double.svg +1 -1
- package/icons/chevron-line.svg +1 -1
- package/icons/chevron.svg +1 -1
- package/icons/close.svg +1 -1
- package/icons/code.svg +1 -1
- package/icons/code_blocks.svg +1 -1
- package/icons/copy.svg +1 -1
- package/icons/delete.svg +1 -1
- package/icons/drag-handle.svg +1 -1
- package/icons/edit.svg +1 -1
- package/icons/error.svg +1 -1
- package/icons/export-file.svg +1 -1
- package/icons/file-text.svg +1 -1
- package/icons/file.svg +1 -1
- package/icons/filter.svg +1 -1
- package/icons/folder-open.svg +1 -1
- package/icons/folder.svg +1 -1
- package/icons/format_align_center.svg +1 -1
- package/icons/format_align_justify.svg +1 -1
- package/icons/format_align_left.svg +1 -1
- package/icons/format_align_right.svg +1 -1
- package/icons/format_bold.svg +1 -1
- package/icons/format_clear.svg +1 -1
- package/icons/format_color_fill.svg +1 -1
- package/icons/format_color_text.svg +1 -1
- package/icons/format_h1.svg +1 -1
- package/icons/format_h2.svg +1 -1
- package/icons/format_h3.svg +1 -1
- package/icons/format_h4.svg +1 -1
- package/icons/format_h5.svg +1 -1
- package/icons/format_h6.svg +1 -1
- package/icons/format_italic.svg +1 -1
- package/icons/format_list_bulleted.svg +1 -1
- package/icons/format_list_numbered.svg +1 -1
- package/icons/format_paragraph.svg +1 -1
- package/icons/format_underlined.svg +1 -1
- package/icons/github-mark.svg +1 -1
- package/icons/help.svg +1 -1
- package/icons/hide.svg +1 -1
- package/icons/image.svg +1 -1
- package/icons/label.svg +1 -1
- package/icons/license.svg +1 -1
- package/icons/link.svg +1 -1
- package/icons/menu.svg +1 -1
- package/icons/mode-auto.svg +1 -1
- package/icons/mode-dark.svg +1 -1
- package/icons/mode-light.svg +1 -1
- package/icons/more_vert.svg +1 -1
- package/icons/settings.svg +1 -1
- package/icons/show.svg +1 -1
- package/icons/strikethrough_s.svg +1 -1
- package/icons/table-visibility.svg +1 -1
- package/icons/table.svg +1 -1
- package/icons/warning.svg +1 -1
- package/icons/wysiwyg.svg +1 -1
- package/package.json +13 -4
- package/src/components/Resize.js +1 -0
- package/src/components/Split.js +7 -3
- package/src/components/Tree.js +4 -5
- package/icons/circle.svg +0 -1
- package/icons/compare.svg +0 -1
- package/icons/drawing2.svg +0 -124
- package/icons/filter-off.svg +0 -1
- package/icons/first.svg +0 -1
- package/icons/folder-clear.svg +0 -1
- package/icons/folder-create.svg +0 -1
- package/icons/key.svg +0 -1
- package/icons/label-add.svg +0 -1
- package/icons/last.svg +0 -1
- package/icons/login.svg +0 -1
- package/icons/logout.svg +0 -1
- package/icons/open-in-browser.svg +0 -1
- package/icons/pause.svg +0 -1
- package/icons/play.svg +0 -1
- package/icons/refresh.svg +0 -1
- package/icons/remove.svg +0 -1
- package/icons/save.svg +0 -1
- package/icons/search.svg +0 -1
- package/icons/storage.svg +0 -1
- package/icons/tag.svg +0 -1
- package/icons/tools.svg +0 -1
- package/icons/trash-x.svg +0 -1
- package/scripts/getIcon.js +0 -52
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: get-icon
|
|
3
|
+
description: Fetches icons from the Google Material Design Icon set, saves it to this project, and then formats it.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Get Icon
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Use this skill any time an icon is needed — whether the user explicitly asks for one or you determine that a UI element (button, control, action, etc.) would benefit from one. For example, if a user asks you to build a "bold" button for a WYSIWYG toolbar, you should proactively source an appropriate icon without being explicitly told to.
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Icons live in the `icons/` directory as SVG files. The project uses the **Material Symbols Outlined** style from Google's CDN. The workflow has three stages:
|
|
15
|
+
|
|
16
|
+
1. **Check local icons first** — the icon may already exist
|
|
17
|
+
2. **Find** the correct Material Symbols name if it doesn't
|
|
18
|
+
3. **Fetch, format, and save** using the script (directional handling is automatic)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Stage 1: Check Local Icons First
|
|
23
|
+
|
|
24
|
+
Before going to the internet, list the contents of `icons/` and look for an existing icon that fits the need. Use `file_search` or `list_dir` on the `icons/` directory.
|
|
25
|
+
|
|
26
|
+
- If a **clear match** exists (e.g. `icons/format_bold.svg` for a bold button), use it — no download needed.
|
|
27
|
+
- If a **reasonable match** exists for a directional icon (e.g. `icons/arrow.svg` for a left-arrow), use it with the appropriate `direction` attribute — no download needed.
|
|
28
|
+
- If **nothing fits**, proceed to Stage 2.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Stage 2: Find the Icon Name
|
|
33
|
+
|
|
34
|
+
If the user describes an icon but doesn't supply an exact name, search Google Material Symbols to find the right one.
|
|
35
|
+
|
|
36
|
+
Run the search script, which fetches icon names and tags from GitHub and caches them locally in `node_modules/.cache/kempo-ui/`. On subsequent runs it only re-downloads if the icon list has changed (SHA check — ~1KB request vs ~500KB full list). Search matches both icon names **and tags**, so searching `chevron` will also surface `keyboard_arrow_right` because it has a `chevron` tag:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm run listicons -- <search_term>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
```bash
|
|
44
|
+
npm run listicons -- bold
|
|
45
|
+
# → format_bold
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm run listicons -- chevron
|
|
50
|
+
# → chevron_right
|
|
51
|
+
# chevron_left
|
|
52
|
+
# keyboard_arrow_right
|
|
53
|
+
# keyboard_arrow_left
|
|
54
|
+
# ...
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The results show **full icon names including direction suffixes** so you can see what variants are available. Pick the best match for the user's intent. If no results are returned, try different search terms (e.g. `type` instead of `font`, `text` instead of `word`).
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Stage 3: Fetch, Format, and Save
|
|
62
|
+
|
|
63
|
+
Run the fetch script with the chosen icon name. The script automatically handles directional icons — if the name you pass is directional (has a `_left`, `_up`, `_down`, `_backward` suffix, or has no exact match but a `_right`/`_forward` variant exists), it will prompt:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
"chevron_left" is a directional icon. kempo-ui uses right direction only and applies CSS rotation.
|
|
67
|
+
Save right-facing variant as "chevron.svg"? (Y/n)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm run geticon -- <icon_name> [custom_name] [-y]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- `icon_name` — the Material Symbols name as returned by the search script
|
|
75
|
+
- `custom_name` — optional rename (e.g. `keyboard_double_arrow_right double_chevron` saves as `double_chevron.svg`)
|
|
76
|
+
- `-y` — auto-accept the directional prompt without user interaction
|
|
77
|
+
|
|
78
|
+
Examples:
|
|
79
|
+
```bash
|
|
80
|
+
# Non-directional — downloads silently
|
|
81
|
+
npm run geticon -- format_bold
|
|
82
|
+
|
|
83
|
+
# Directional — prompts to confirm saving right-facing variant as "chevron"
|
|
84
|
+
npm run geticon -- chevron_left
|
|
85
|
+
|
|
86
|
+
# Directional with rename and auto-accept
|
|
87
|
+
npm run geticon -- keyboard_double_arrow_up double_chevron -y
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If the script exits with a non-zero code mentioning a 404, the icon name is wrong — return to Stage 2 and search for the correct name.
|
|
91
|
+
|
|
92
|
+
The script produces minified output with only `xmlns` and `viewBox` on `<svg>`, and only `fill="currentColor"` and `d` on each `<path>`:
|
|
93
|
+
```svg
|
|
94
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="..."/></svg>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Directional Icons and `<k-icon>`
|
|
100
|
+
|
|
101
|
+
When the script saves a right-facing icon (after a directional prompt), the `<k-icon>` component handles rotation via its `direction` attribute:
|
|
102
|
+
|
|
103
|
+
| `direction` value | Rotation applied |
|
|
104
|
+
|---|---|
|
|
105
|
+
| *(omitted)* | 0° — points right |
|
|
106
|
+
| `down` | 90° |
|
|
107
|
+
| `left` | 180° |
|
|
108
|
+
| `up` | 270° |
|
|
109
|
+
|
|
110
|
+
**Example:** A user asking for a "left arrow" after downloading `arrow.svg`:
|
|
111
|
+
```html
|
|
112
|
+
<k-icon name="arrow" direction="left"></k-icon>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Complete Example
|
|
118
|
+
|
|
119
|
+
**User asks to "add a bold button" (implicit icon need)**
|
|
120
|
+
|
|
121
|
+
1. **Local check:** `icons/format_bold.svg` exists → use it directly, no download needed
|
|
122
|
+
2. **Use:** `<k-icon name="format_bold"></k-icon>`
|
|
123
|
+
|
|
124
|
+
**User asks to "add a thumbs up icon" (explicit, not in local icons)**
|
|
125
|
+
|
|
126
|
+
1. **Local check:** No match in `icons/`
|
|
127
|
+
2. **Find:** Search Material Symbols for `thumbs+up` → `thumb_up`
|
|
128
|
+
3. **Arrow check:** Not directional, proceed normally
|
|
129
|
+
4. **Run:** `node .github/skills/get-icon/get_icon.js thumb_up`
|
|
130
|
+
5. **Result:** `icons/thumb_up.svg` saved and build triggered
|
|
131
|
+
|
|
132
|
+
**User asks for a "left arrow icon"**
|
|
133
|
+
|
|
134
|
+
1. **Local check:** `icons/arrow.svg` exists and is right-facing
|
|
135
|
+
2. **Arrow check:** Directional — right-facing variant already present
|
|
136
|
+
3. **No download needed:** use `<k-icon name="arrow" direction="left"></k-icon>`
|
|
137
|
+
|
|
138
|
+
**User asks for a "left arrow icon" (no arrow in local icons)**
|
|
139
|
+
|
|
140
|
+
1. **Local check:** No arrow icon found
|
|
141
|
+
2. **Find:** Search for `arrow_forward` or `arrow_right`
|
|
142
|
+
3. **Arrow check:** Directional — download the right-facing variant with a generic name
|
|
143
|
+
4. **Run:** `node .github/skills/get-icon/get_icon.js arrow_forward arrow`
|
|
144
|
+
5. **Use:** `<k-icon name="arrow" direction="left"></k-icon>`
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: highlight-code
|
|
3
|
+
description: Generates syntax-highlighted HTML for use in documentation code examples. USE THIS instead of creating temporary scripts.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Highlight Code
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Use this skill whenever you need syntax-highlighted HTML to embed in a documentation page — for example, when updating a `<pre><code class="hljs ...">` block in a `docs/components/*.html` file. **Do not create temporary `.mjs` scripts to do this.**
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm run highlightcode -- <lang> "<code>"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or via npx:
|
|
19
|
+
```bash
|
|
20
|
+
npx kempo-highlightcode <lang> "<code>"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Supported languages:** `html`, `css`, `js` / `javascript`, `ts` / `typescript`, `json`, `md` / `markdown`, `sh` / `bash`, `xml`
|
|
24
|
+
|
|
25
|
+
## Output
|
|
26
|
+
|
|
27
|
+
The script returns the **inner highlighted content only** — the hljs span markup without any `<pre><code>` wrapper. Paste it directly inside an existing `<pre><code class="hljs <lang>">...</code></pre>` block.
|
|
28
|
+
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm run highlightcode -- html "<p>Hello World</p>"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
```
|
|
37
|
+
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>Hello World<span class="hljs-tag"></<span class="hljs-name">p</span>></span>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Multi-line Code
|
|
41
|
+
|
|
42
|
+
For multi-line code, use a here-string in PowerShell:
|
|
43
|
+
|
|
44
|
+
```powershell
|
|
45
|
+
$code = @"
|
|
46
|
+
class Foo extends Bar {
|
|
47
|
+
render(){ return html`<p>hello</p>`; }
|
|
48
|
+
}
|
|
49
|
+
"@
|
|
50
|
+
node bin/highlight_code.js js $code
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Workflow for Updating Doc Code Samples
|
|
54
|
+
|
|
55
|
+
1. Run `npm run highlightcode -- <lang> "<new code>"` to get the highlighted HTML.
|
|
56
|
+
2. Replace the inner content of the matching `<pre><code class="hljs ...">` block in the doc file with the output.
|
|
57
|
+
3. Also update the live demo `<script type="module">` block with the raw (unhighlighted) code.
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ npm install kempo-ui
|
|
|
22
22
|
<script>
|
|
23
23
|
window.kempo = {
|
|
24
24
|
pathToStylesheet: '/kempo.min.css',
|
|
25
|
-
|
|
25
|
+
pathsToIcons: ['/icons']
|
|
26
26
|
};
|
|
27
27
|
</script>
|
|
28
28
|
</head>
|
|
@@ -63,6 +63,88 @@ Import and use components in your HTML:
|
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
## Icons
|
|
67
|
+
|
|
68
|
+
kempo-ui ships a small set of icons used internally by its components. The `<k-icon>` component can also serve icons from your own directory, so you can add project-specific icons alongside the built-in ones.
|
|
69
|
+
|
|
70
|
+
### Interactive picker (for humans)
|
|
71
|
+
|
|
72
|
+
Run a single command, type to filter, arrow keys to navigate, Enter to select:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx kempo-icon
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Finding an icon (for scripts/LLMs)
|
|
79
|
+
|
|
80
|
+
Search the full Material Symbols library by name or tag:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx kempo-listicons <search_term>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx kempo-listicons chevron
|
|
88
|
+
# → chevron_right
|
|
89
|
+
# chevron_left
|
|
90
|
+
# keyboard_arrow_right
|
|
91
|
+
# ...
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Downloading an icon
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx kempo-geticon <icon_name> [custom_name] [--dir <path>] [-y]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
By default icons are saved to `icons/` in your project root. Use `--dir` to point to your own assets directory:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Save to icons/ (default)
|
|
104
|
+
npx kempo-geticon format_bold
|
|
105
|
+
|
|
106
|
+
# Save to a custom directory
|
|
107
|
+
npx kempo-geticon format_bold --dir src/assets/icons
|
|
108
|
+
|
|
109
|
+
# Rename on download
|
|
110
|
+
npx kempo-geticon content_copy copy
|
|
111
|
+
|
|
112
|
+
# Auto-accept directional icon prompt
|
|
113
|
+
npx kempo-geticon chevron_left -y
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Directional icons:** Material Symbols has separate icons for each direction (`chevron_left`, `chevron_right`, etc.). kempo-ui only stores the right-facing version and uses the `direction` attribute to rotate it, so you only need one file per icon. If you pass a non-right directional name, the script will prompt you to download the right-facing variant instead and save it under the base name.
|
|
117
|
+
|
|
118
|
+
### Configuring icon directories
|
|
119
|
+
|
|
120
|
+
Tell kempo-ui where to look for icons in your page config. It checks directories in order and uses the first match, so your icons override the built-in ones:
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<script>
|
|
124
|
+
window.kempo = {
|
|
125
|
+
pathToStylesheet: '/kempo.min.css',
|
|
126
|
+
pathsToIcons: ['/my-icons', '/node_modules/kempo-ui/icons']
|
|
127
|
+
};
|
|
128
|
+
</script>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Using icons in markup
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<k-icon name="format_bold"></k-icon>
|
|
135
|
+
<k-icon name="chevron" direction="left"></k-icon>
|
|
136
|
+
<k-icon name="chevron" direction="down"></k-icon>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
| `direction` | Rotation |
|
|
140
|
+
|---|---|
|
|
141
|
+
| *(omitted)* | 0° — right |
|
|
142
|
+
| `down` | 90° |
|
|
143
|
+
| `left` | 180° |
|
|
144
|
+
| `up` | 270° |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
66
148
|
## Documentation
|
|
67
149
|
|
|
68
150
|
**Full Documentation**: [https://dustinpoissant.github.io/kempo-ui/](https://dustinpoissant.github.io/kempo-ui/)
|
package/bin/get_icon.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import readline from 'readline';
|
|
6
|
+
|
|
7
|
+
const CWD = process.cwd();
|
|
8
|
+
const CACHE_DIR = path.join(CWD, 'node_modules', '.cache', 'kempo-ui');
|
|
9
|
+
|
|
10
|
+
const yFlag = process.argv.includes('-y') || process.argv.includes('--yes') || process.env.npm_config_yes === 'true';
|
|
11
|
+
const rawArgs = process.argv.slice(2).filter(a => a !== '-y' && a !== '--yes');
|
|
12
|
+
|
|
13
|
+
let iconDir = 'icons';
|
|
14
|
+
const dirIdx = rawArgs.indexOf('--dir');
|
|
15
|
+
if(dirIdx !== -1){
|
|
16
|
+
iconDir = rawArgs[dirIdx + 1];
|
|
17
|
+
rawArgs.splice(dirIdx, 2);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const iconName = rawArgs[0];
|
|
21
|
+
const customName = rawArgs[1];
|
|
22
|
+
|
|
23
|
+
if(!iconName){
|
|
24
|
+
console.error('Usage: kempo-geticon <icon_name> [custom_name] [--dir <path>] [-y]');
|
|
25
|
+
console.error('Example: kempo-geticon format_bold');
|
|
26
|
+
console.error('Example: kempo-geticon content_copy copy');
|
|
27
|
+
console.error('Example: kempo-geticon chevron_left --dir src/assets/icons');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const RIGHT_SUFFIXES = ['_right', '_forward'];
|
|
32
|
+
const ALL_SUFFIXES = ['_right', '_forward', '_left', '_backward', '_up', '_down'];
|
|
33
|
+
|
|
34
|
+
const fetchSvg = async (name) => {
|
|
35
|
+
const res = await fetch(`https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/${name}/default/24px.svg`);
|
|
36
|
+
return res.ok ? res.text() : null;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const confirm = async (question) => {
|
|
40
|
+
if(yFlag) return true;
|
|
41
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
42
|
+
return new Promise(resolve => {
|
|
43
|
+
rl.question(question, answer => {
|
|
44
|
+
rl.close();
|
|
45
|
+
resolve(answer.trim().toLowerCase() !== 'n');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const dirSuffix = ALL_SUFFIXES.find(s => iconName.endsWith(s));
|
|
51
|
+
const baseName = dirSuffix ? iconName.slice(0, -dirSuffix.length) : iconName;
|
|
52
|
+
const fileName = customName || baseName;
|
|
53
|
+
|
|
54
|
+
let raw;
|
|
55
|
+
|
|
56
|
+
if(dirSuffix && !RIGHT_SUFFIXES.includes(dirSuffix)){
|
|
57
|
+
// Passed a non-right directional (e.g. chevron_left) — find right-facing equivalent
|
|
58
|
+
for(const rs of RIGHT_SUFFIXES){
|
|
59
|
+
raw = await fetchSvg(`${baseName}${rs}`);
|
|
60
|
+
if(raw) break;
|
|
61
|
+
}
|
|
62
|
+
if(!raw){
|
|
63
|
+
console.error(`Error: No right-facing variant found for "${baseName}"`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
const ok = await confirm(`"${iconName}" is a directional icon. kempo-ui uses right direction only and applies CSS rotation.\nSave right-facing variant as "${fileName}.svg"? (Y/n) `);
|
|
67
|
+
if(!ok) process.exit(0);
|
|
68
|
+
} else {
|
|
69
|
+
raw = await fetchSvg(iconName);
|
|
70
|
+
if(raw && dirSuffix){
|
|
71
|
+
// Passed chevron_right or chevron_forward explicitly
|
|
72
|
+
const ok = await confirm(`"${iconName}" is a directional icon. kempo-ui uses right direction only and applies CSS rotation.\nSave as "${fileName}.svg"? (Y/n) `);
|
|
73
|
+
if(!ok) process.exit(0);
|
|
74
|
+
} else if(!raw){
|
|
75
|
+
// No exact match — check if right-facing variants exist
|
|
76
|
+
let rightName;
|
|
77
|
+
for(const rs of RIGHT_SUFFIXES){
|
|
78
|
+
const candidate = `${iconName}${rs}`;
|
|
79
|
+
raw = await fetchSvg(candidate);
|
|
80
|
+
if(raw){ rightName = candidate; break; }
|
|
81
|
+
}
|
|
82
|
+
if(!raw){
|
|
83
|
+
console.error(`Error: "${iconName}" not found`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const ok = await confirm(`"${iconName}" appears to be a directional icon (found "${rightName}"). kempo-ui uses right direction only and applies CSS rotation.\nSave as "${fileName}.svg"? (Y/n) `);
|
|
87
|
+
if(!ok) process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const viewBox = raw.match(/viewBox="([^"]+)"/)?.[1];
|
|
92
|
+
const paths = [...raw.matchAll(/<path[^>]*\sd="([^"]+)"/g)].map(m => m[1]);
|
|
93
|
+
|
|
94
|
+
if(!viewBox || !paths.length){
|
|
95
|
+
console.error('Error: Unexpected SVG structure');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const formatted = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}">${paths.map(d => `<path fill="currentColor" d="${d}"/>`).join('')}</svg>\n`;
|
|
100
|
+
const outputDir = path.resolve(CWD, iconDir);
|
|
101
|
+
|
|
102
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
103
|
+
await fs.writeFile(path.join(outputDir, `${fileName}.svg`), formatted, 'utf8');
|
|
104
|
+
console.log(`✓ Saved ${iconDir}/${fileName}.svg`);
|
|
105
|
+
|
|
106
|
+
spawn('npm', ['run', 'build', '--if-present'], { stdio: 'inherit', shell: true, cwd: CWD });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { highlight } from '../tools/highlight.js';
|
|
3
|
+
|
|
4
|
+
const LANG_ALIASES = {
|
|
5
|
+
js: 'javascript',
|
|
6
|
+
ts: 'typescript',
|
|
7
|
+
html: 'html',
|
|
8
|
+
css: 'css',
|
|
9
|
+
json: 'json',
|
|
10
|
+
md: 'markdown',
|
|
11
|
+
sh: 'bash',
|
|
12
|
+
bash: 'bash',
|
|
13
|
+
xml: 'xml',
|
|
14
|
+
javascript: 'javascript',
|
|
15
|
+
typescript: 'typescript',
|
|
16
|
+
markdown: 'markdown'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
|
|
21
|
+
if(args.length < 2){
|
|
22
|
+
console.error('Usage: kempo-highlightcode <lang> <code>');
|
|
23
|
+
console.error('Example: kempo-highlightcode html "<p>Hello World</p>"');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const [langArg, ...rest] = args;
|
|
28
|
+
const lang = LANG_ALIASES[langArg.toLowerCase()];
|
|
29
|
+
|
|
30
|
+
if(!lang){
|
|
31
|
+
console.error(`Unknown language: "${langArg}"`);
|
|
32
|
+
console.error(`Supported: ${Object.keys(LANG_ALIASES).join(', ')}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const code = rest.join(' ');
|
|
37
|
+
const full = highlight(code, lang);
|
|
38
|
+
// Strip the <pre><code> wrapper — return just the inner highlighted content
|
|
39
|
+
process.stdout.write(full.replace(/^<pre><code class="hljs [^"]*">/, '').replace(/<\/code><\/pre>\n?$/, '') + '\n');
|
package/bin/icon.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { search, input, confirm } from '@inquirer/prompts';
|
|
6
|
+
|
|
7
|
+
const CWD = process.cwd();
|
|
8
|
+
const CACHE_DIR = path.join(CWD, 'node_modules', '.cache', 'kempo-ui');
|
|
9
|
+
const CACHE_FILE = path.join(CACHE_DIR, 'icons-cache.json');
|
|
10
|
+
const SYMBOLS_URL = 'https://api.github.com/repos/google/material-design-icons/contents/symbols';
|
|
11
|
+
|
|
12
|
+
const RIGHT_SUFFIXES = ['_right', '_forward'];
|
|
13
|
+
const ALL_SUFFIXES = ['_right', '_forward', '_left', '_backward', '_up', '_down'];
|
|
14
|
+
|
|
15
|
+
const fetchJson = async (url) => {
|
|
16
|
+
const response = await fetch(url, { headers: { 'Accept': 'application/vnd.github+json' } });
|
|
17
|
+
if(!response.ok) throw new Error(`HTTP ${response.status}: ${url}`);
|
|
18
|
+
return response.json();
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const fetchSvg = async (name) => {
|
|
22
|
+
const res = await fetch(`https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/${name}/default/24px.svg`);
|
|
23
|
+
return res.ok ? res.text() : null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let cache = null;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
cache = JSON.parse(await fs.readFile(CACHE_FILE, 'utf8'));
|
|
30
|
+
} catch {
|
|
31
|
+
// no cache yet
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const currentSha = await (async () => {
|
|
35
|
+
const entries = await fetchJson(SYMBOLS_URL);
|
|
36
|
+
return entries.find(e => e.name === 'web')?.sha;
|
|
37
|
+
})();
|
|
38
|
+
|
|
39
|
+
if(cache?.sha !== currentSha){
|
|
40
|
+
process.stderr.write('Updating icon list cache...\n');
|
|
41
|
+
const [tree, metaRes] = await Promise.all([
|
|
42
|
+
fetchJson(`https://api.github.com/repos/google/material-design-icons/git/trees/${currentSha}`),
|
|
43
|
+
fetch('https://fonts.google.com/metadata/icons').then(r => r.text())
|
|
44
|
+
]);
|
|
45
|
+
const icons = tree.tree.map(e => e.path);
|
|
46
|
+
const meta = JSON.parse(metaRes.replace(/^\)\]\}'\n/, ''));
|
|
47
|
+
const tagMap = {};
|
|
48
|
+
for(const icon of meta.icons) tagMap[icon.name] = icon.tags || [];
|
|
49
|
+
cache = { sha: currentSha, icons, tagMap };
|
|
50
|
+
await fs.mkdir(CACHE_DIR, { recursive: true });
|
|
51
|
+
await fs.writeFile(CACHE_FILE, JSON.stringify(cache), 'utf8');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const tagMap = cache.tagMap || {};
|
|
55
|
+
|
|
56
|
+
const matchesQuery = (name, query) => {
|
|
57
|
+
if(name.includes(query)) return true;
|
|
58
|
+
return (tagMap[name] || []).some(t => t.includes(query));
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const iconName = await search({
|
|
62
|
+
message: 'Search for an icon',
|
|
63
|
+
source: (query) => {
|
|
64
|
+
if(!query) return [];
|
|
65
|
+
return cache.icons
|
|
66
|
+
.filter(name => matchesQuery(name, query))
|
|
67
|
+
.slice(0, 20)
|
|
68
|
+
.map(name => ({ name, value: name }));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const dirSuffix = ALL_SUFFIXES.find(s => iconName.endsWith(s));
|
|
73
|
+
const baseName = dirSuffix ? iconName.slice(0, -dirSuffix.length) : iconName;
|
|
74
|
+
|
|
75
|
+
const customName = await input({
|
|
76
|
+
message: 'Save as',
|
|
77
|
+
default: baseName
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const fileName = customName || baseName;
|
|
81
|
+
let raw;
|
|
82
|
+
|
|
83
|
+
if(dirSuffix && !RIGHT_SUFFIXES.includes(dirSuffix)){
|
|
84
|
+
for(const rs of RIGHT_SUFFIXES){
|
|
85
|
+
raw = await fetchSvg(`${baseName}${rs}`);
|
|
86
|
+
if(raw) break;
|
|
87
|
+
}
|
|
88
|
+
if(!raw){
|
|
89
|
+
console.error(`Error: No right-facing variant found for "${baseName}"`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
const ok = await confirm({ message: `"${iconName}" is directional. kempo-ui stores right-facing only and uses CSS rotation. Download right-facing variant as "${fileName}.svg"?`, default: true });
|
|
93
|
+
if(!ok) process.exit(0);
|
|
94
|
+
} else {
|
|
95
|
+
raw = await fetchSvg(iconName);
|
|
96
|
+
if(raw && dirSuffix){
|
|
97
|
+
const ok = await confirm({ message: `"${iconName}" is directional. kempo-ui stores right-facing only and uses CSS rotation. Save as "${fileName}.svg"?`, default: true });
|
|
98
|
+
if(!ok) process.exit(0);
|
|
99
|
+
} else if(!raw){
|
|
100
|
+
let rightName;
|
|
101
|
+
for(const rs of RIGHT_SUFFIXES){
|
|
102
|
+
const candidate = `${iconName}${rs}`;
|
|
103
|
+
raw = await fetchSvg(candidate);
|
|
104
|
+
if(raw){ rightName = candidate; break; }
|
|
105
|
+
}
|
|
106
|
+
if(!raw){
|
|
107
|
+
console.error(`Error: "${iconName}" not found`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
const ok = await confirm({ message: `"${iconName}" appears directional (found "${rightName}"). kempo-ui stores right-facing only and uses CSS rotation. Save as "${fileName}.svg"?`, default: true });
|
|
111
|
+
if(!ok) process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const viewBox = raw.match(/viewBox="([^"]+)"/)?.[1];
|
|
116
|
+
const paths = [...raw.matchAll(/<path[^>]*\sd="([^"]+)"/g)].map(m => m[1]);
|
|
117
|
+
|
|
118
|
+
if(!viewBox || !paths.length){
|
|
119
|
+
console.error('Error: Unexpected SVG structure');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const iconDir = 'icons';
|
|
124
|
+
const formatted = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}">${paths.map(d => `<path fill="currentColor" d="${d}"/>`).join('')}</svg>\n`;
|
|
125
|
+
const outputDir = path.resolve(CWD, iconDir);
|
|
126
|
+
|
|
127
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
128
|
+
await fs.writeFile(path.join(outputDir, `${fileName}.svg`), formatted, 'utf8');
|
|
129
|
+
console.log(`✓ Saved ${iconDir}/${fileName}.svg`);
|
|
130
|
+
|
|
131
|
+
spawn('npm', ['run', 'build', '--if-present'], { stdio: 'inherit', shell: true, cwd: CWD });
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
const CACHE_DIR = path.join(process.cwd(), 'node_modules', '.cache', 'kempo-ui');
|
|
6
|
+
const CACHE_FILE = path.join(CACHE_DIR, 'icons-cache.json');
|
|
7
|
+
const SYMBOLS_URL = 'https://api.github.com/repos/google/material-design-icons/contents/symbols';
|
|
8
|
+
|
|
9
|
+
const query = process.argv[2];
|
|
10
|
+
|
|
11
|
+
if(!query){
|
|
12
|
+
console.error('Usage: kempo-listicons <search_term>');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const fetchJson = async (url) => {
|
|
17
|
+
const response = await fetch(url, { headers: { 'Accept': 'application/vnd.github+json' } });
|
|
18
|
+
if(!response.ok) throw new Error(`HTTP ${response.status}: ${url}`);
|
|
19
|
+
return response.json();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getCurrentSha = async () => {
|
|
23
|
+
const entries = await fetchJson(SYMBOLS_URL);
|
|
24
|
+
return entries.find(e => e.name === 'web')?.sha;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const fetchAllIcons = async (treeSha) => {
|
|
28
|
+
const [tree, metaRes] = await Promise.all([
|
|
29
|
+
fetchJson(`https://api.github.com/repos/google/material-design-icons/git/trees/${treeSha}`),
|
|
30
|
+
fetch('https://fonts.google.com/metadata/icons').then(r => r.text())
|
|
31
|
+
]);
|
|
32
|
+
const icons = tree.tree.map(e => e.path);
|
|
33
|
+
const meta = JSON.parse(metaRes.replace(/^\)\]\}'\n/, ''));
|
|
34
|
+
const tagMap = {};
|
|
35
|
+
for(const icon of meta.icons) tagMap[icon.name] = icon.tags || [];
|
|
36
|
+
return { icons, tagMap };
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let cache = null;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
cache = JSON.parse(await fs.readFile(CACHE_FILE, 'utf8'));
|
|
43
|
+
} catch {
|
|
44
|
+
// no cache yet
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const currentSha = await getCurrentSha();
|
|
48
|
+
|
|
49
|
+
if(cache?.sha !== currentSha){
|
|
50
|
+
process.stderr.write('Cache outdated or missing, fetching icon list...\n');
|
|
51
|
+
const { icons, tagMap } = await fetchAllIcons(currentSha);
|
|
52
|
+
cache = { sha: currentSha, icons, tagMap };
|
|
53
|
+
await fs.mkdir(CACHE_DIR, { recursive: true });
|
|
54
|
+
await fs.writeFile(CACHE_FILE, JSON.stringify(cache), 'utf8');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const tagMap = cache.tagMap || {};
|
|
58
|
+
|
|
59
|
+
const matchesQuery = (name) => {
|
|
60
|
+
if(name.includes(query)) return true;
|
|
61
|
+
return (tagMap[name] || []).some(t => t.includes(query));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const results = cache.icons.filter(matchesQuery);
|
|
65
|
+
|
|
66
|
+
if(results.length === 0){
|
|
67
|
+
console.log(`No icons found matching "${query}"`);
|
|
68
|
+
} else {
|
|
69
|
+
console.log(results.join('\n'));
|
|
70
|
+
}
|