mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-09 17:29:12 +00:00
refactor(web): migrate icons to unplugin-icons (#1467)
* refactor(web): migrate icons to unplugin-icons Replace the hand-written gen-icon-data.mjs + @iconify/utils runtime rendering with unplugin-icons build-time imports. The public API (<Icon name>, iconSvg, IconName, SIZE_PX, NAME_TO_REMIX) is unchanged; 127+ call sites are untouched. - add unplugin-icons@^23.0.0 (devDep) + Vite Icons() plugin (compiler: vue3) - rewrite src/lib/icons.ts: static ~icons/ri/* imports (component + ?raw) for 56 distinct Remix icons across 59 IconName entries - Icon.vue renders <component :is> with unknown-name fallback - append ICON_GROUPS export for DesignSystemView catalog - DesignSystemView: v-for catalog, remove legacy-script references - delete gen-icon-data.mjs, gen-icon-catalog.mjs, icon-data.ts, gen:icons script - remove @iconify/vue and @iconify/utils from dependencies; move @iconify-json/ri to devDependencies - drop Icon.vue from check-style ICON_EXEMPT (no hand-written <svg>) * refactor(web): drop unused NAME_TO_REMIX icon mapping NAME_TO_REMIX was a Record<IconName, string> table introduced to map internal icon names to their ri: ids. After the unplugin-icons migration it has no production consumers — only icons.test.ts imported it (for two drift tests) and DesignSystemView mentioned it in descriptive copy. The ICONS table already conveys the same ri: id via each entry's paired component + ?raw imports (e.g. RiFolderOpenLine / RawFolderOpenLine). - remove NAME_TO_REMIX const from src/lib/icons.ts (-63 lines) - remove NAME_TO_REMIX import + describe block from icons.test.ts - update DesignSystemView §02 copy: describe the import-pair idiom and stop claiming ICON_GROUPS is sourced from NAME_TO_REMIX * chore: add changeset for web icon migration * chore(nix): bump pnpmDeps hash for unplugin-icons Adding unplugin-icons changed pnpm-lock.yaml, so the fixed-output pnpmDeps derivation hash is stale. Update to the hash reported by the Nix Build CI run.
This commit is contained in:
parent
2a44ae075a
commit
ee385456d0
16 changed files with 555 additions and 578 deletions
|
|
@ -10,15 +10,11 @@
|
|||
"build": "vite build",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"check:style": "node scripts/check-style.mjs",
|
||||
"gen:icons": "node scripts/gen-icon-data.mjs"
|
||||
"check:style": "node scripts/check-style.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
||||
"@iconify-json/ri": "^1.2.10",
|
||||
"@iconify/utils": "^3.1.3",
|
||||
"@iconify/vue": "^5.0.1",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"katex": "^0.17.0",
|
||||
|
|
@ -30,8 +26,10 @@
|
|||
"vue-i18n": "^11.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/ri": "^1.2.10",
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"typescript": "6.0.2",
|
||||
"unplugin-icons": "^23.0.0",
|
||||
"vite": "^6.3.3",
|
||||
"vitest": "4.1.4",
|
||||
"vue-tsc": "~3.2.0",
|
||||
|
|
|
|||
|
|
@ -27,13 +27,13 @@ const DOMAIN_HEX_EXEMPT = new Set([
|
|||
'Terminal.vue',
|
||||
]);
|
||||
|
||||
// Files that legitimately render their own <svg>: the icon primitive itself,
|
||||
// bespoke data-viz / colored illustrations, the spinner, and brand marks
|
||||
// (the Kimi wordmark on the loading screen). Everything else should use
|
||||
// lib/icons.ts via <Icon>/iconSvg(). The 32x22 Kimi eye logo is also exempted
|
||||
// inline (matched by viewBox).
|
||||
// Files that legitimately render their own <svg>: bespoke data-viz / colored
|
||||
// illustrations, the spinner, and brand marks (the Kimi wordmark on the loading
|
||||
// screen). Everything else should use lib/icons.ts via <Icon>/iconSvg(). The
|
||||
// 32x22 Kimi eye logo is also exempted inline (matched by viewBox). The icon
|
||||
// primitive (components/ui/Icon.vue) itself renders no hand-written <svg>, so it
|
||||
// is not exempted here.
|
||||
const ICON_EXEMPT = new Set([
|
||||
'components/ui/Icon.vue',
|
||||
'components/ui/Spinner.vue',
|
||||
'components/ui/MoonSpinner.vue',
|
||||
'components/ui/ContextRing.vue',
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
// scripts/gen-icon-catalog.mjs — generate the design-system §02 icon catalog
|
||||
// HTML from the canonical registry (lib/icons.ts) so the two can never drift.
|
||||
// Run: node --experimental-strip-types scripts/gen-icon-catalog.mjs
|
||||
import { ICON_DATA } from '../src/lib/icon-data.ts';
|
||||
|
||||
// Display order + grouping. Names not listed here are appended under "Other".
|
||||
const GROUPS = [
|
||||
['Actions', ['plus', 'chat-new', 'close', 'check', 'search', 'copy', 'link', 'external-link', 'download', 'undo', 'send', 'image', 'settings', 'sliders', 'log-in']],
|
||||
['Navigation & layout', ['chevron-down', 'chevron-right', 'arrow-up', 'arrow-down', 'arrow-right', 'minus', 'panel-collapse', 'panel-expand', 'expand', 'collapse', 'list']],
|
||||
['Files & tools', ['folder', 'folder-closed', 'folder-plus', 'folder-solid', 'file', 'file-text', 'file-plus', 'file-off', 'image-off', 'code', 'terminal', 'pencil', 'glob', 'globe', 'check-list', 'bolt', 'git-pull-request']],
|
||||
['Communication', ['message', 'mail', 'user']],
|
||||
['Status & media', ['info', 'help-circle', 'alert-triangle', 'clock', 'sparkles', 'play', 'stop', 'star', 'star-outline', 'dots-horizontal']],
|
||||
];
|
||||
|
||||
function render(name) {
|
||||
const d = ICON_DATA[name];
|
||||
const vb = `0 0 ${d.width ?? 24} ${d.height ?? 24}`;
|
||||
return `<svg class="p-ic" viewBox="${vb}" fill="currentColor">${d.body}</svg>`;
|
||||
}
|
||||
|
||||
const seen = new Set();
|
||||
const lines = [];
|
||||
lines.push('<div class="icon-grid">');
|
||||
for (const [label, names] of GROUPS) {
|
||||
lines.push(` <div class="icon-group-label">${label.replaceAll('&', '&')}</div>`);
|
||||
for (const name of names) {
|
||||
seen.add(name);
|
||||
lines.push(` <div class="icon-cell">${render(name)}<span class="ic-name">${name}</span></div>`);
|
||||
}
|
||||
}
|
||||
const rest = Object.keys(ICON_DATA).filter((n) => !seen.has(n));
|
||||
if (rest.length) {
|
||||
lines.push(' <div class="icon-group-label">Other</div>');
|
||||
for (const name of rest) {
|
||||
lines.push(` <div class="icon-cell">${render(name)}<span class="ic-name">${name}</span></div>`);
|
||||
}
|
||||
}
|
||||
lines.push('</div>');
|
||||
|
||||
process.stdout.write(lines.join('\n') + '\n');
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
// scripts/gen-icon-data.mjs — generate src/lib/icon-data.ts, the tree-shaken
|
||||
// Remix Icon (ri) subset that backs apps/kimi-web's icon registry.
|
||||
//
|
||||
// Single source of truth for "which existing icon name maps to which Remix
|
||||
// icon" is the NAME_TO_REMIX map below. The SVG bytes are pulled straight from
|
||||
// @iconify-json/ri at generation time, so the registry stays library-sourced
|
||||
// (no hand-copied SVG) yet fully offline and tree-shaken (only the icons we
|
||||
// list here end up in the bundle).
|
||||
//
|
||||
// Run after changing the map: pnpm gen:icons
|
||||
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import ri from '@iconify-json/ri/icons.json' with { type: 'json' };
|
||||
import { getIconData } from '@iconify/utils';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const OUT = resolve(__dirname, '../src/lib/icon-data.ts');
|
||||
|
||||
// Existing icon name → Remix icon name (ri:<name>, prefix omitted here).
|
||||
// Keep keys sorted within their group; the generated IconName union follows
|
||||
// this order. Every value must exist in @iconify-json/ri (validated below).
|
||||
const GROUPS = [
|
||||
['Actions', {
|
||||
plus: 'add-line',
|
||||
'chat-new': 'chat-new-line',
|
||||
close: 'close-line',
|
||||
check: 'check-line',
|
||||
search: 'search-line',
|
||||
copy: 'file-copy-line',
|
||||
link: 'links-line',
|
||||
'external-link': 'external-link-line',
|
||||
download: 'download-line',
|
||||
undo: 'arrow-go-back-line',
|
||||
send: 'arrow-up-line',
|
||||
image: 'image-line',
|
||||
settings: 'settings-3-line',
|
||||
sliders: 'equalizer-line',
|
||||
'log-in': 'login-box-line',
|
||||
}],
|
||||
['Navigation & layout', {
|
||||
'chevron-down': 'arrow-down-s-line',
|
||||
'chevron-right': 'arrow-right-s-line',
|
||||
'arrow-up': 'arrow-up-line',
|
||||
'arrow-down': 'arrow-down-line',
|
||||
'arrow-right': 'arrow-right-line',
|
||||
minus: 'subtract-line',
|
||||
'panel-collapse': 'contract-left-line',
|
||||
'panel-expand': 'expand-right-line',
|
||||
expand: 'expand-diagonal-line',
|
||||
collapse: 'collapse-diagonal-line',
|
||||
list: 'list-unordered',
|
||||
sort: 'sort-desc',
|
||||
grip: 'draggable',
|
||||
}],
|
||||
['Files & tools', {
|
||||
folder: 'folder-open-line',
|
||||
'folder-closed': 'folder-line',
|
||||
'folder-plus': 'folder-add-line',
|
||||
'folder-solid': 'folder-fill',
|
||||
file: 'file-line',
|
||||
'file-text': 'file-text-line',
|
||||
'file-plus': 'file-add-line',
|
||||
'file-off': 'file-line',
|
||||
'image-off': 'image-line',
|
||||
code: 'code-line',
|
||||
terminal: 'terminal-box-line',
|
||||
pencil: 'pencil-line',
|
||||
tool: 'tools-line',
|
||||
glob: 'braces-line',
|
||||
globe: 'global-line',
|
||||
'check-list': 'list-check',
|
||||
bolt: 'flashlight-line',
|
||||
'git-pull-request': 'git-pull-request-line',
|
||||
}],
|
||||
['Communication', {
|
||||
message: 'message-line',
|
||||
mail: 'mail-line',
|
||||
user: 'user-line',
|
||||
}],
|
||||
['Status & media', {
|
||||
info: 'information-line',
|
||||
'help-circle': 'question-line',
|
||||
'alert-triangle': 'alert-line',
|
||||
clock: 'time-line',
|
||||
sparkles: 'sparkling-line',
|
||||
play: 'play-fill',
|
||||
stop: 'stop-fill',
|
||||
star: 'star-fill',
|
||||
'star-outline': 'star-line',
|
||||
'dots-horizontal': 'more-line',
|
||||
}],
|
||||
];
|
||||
|
||||
const NAME_TO_REMIX = Object.assign({}, ...GROUPS.map(([, m]) => m));
|
||||
|
||||
// --- resolve + validate ----------------------------------------------------
|
||||
const missing = [];
|
||||
const data = {};
|
||||
for (const [name, riName] of Object.entries(NAME_TO_REMIX)) {
|
||||
const icon = getIconData(ri, riName);
|
||||
if (!icon) {
|
||||
missing.push(`${name} → ri:${riName}`);
|
||||
continue;
|
||||
}
|
||||
data[name] = { body: icon.body, width: icon.width ?? 24, height: icon.height ?? 24 };
|
||||
}
|
||||
|
||||
if (missing.length) {
|
||||
console.error('gen-icon-data: the following Remix icons were not found in @iconify-json/ri:');
|
||||
for (const m of missing) console.error(` ${m}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// --- emit ------------------------------------------------------------------
|
||||
const names = Object.keys(data);
|
||||
const keyOf = (n) => (/^[a-zA-Z_$][\w$]*$/.test(n) ? n : JSON.stringify(n));
|
||||
|
||||
const lines = [];
|
||||
lines.push('// GENERATED FILE — do not edit by hand.');
|
||||
lines.push('// Source of truth: scripts/gen-icon-data.mjs (run `pnpm gen:icons`).');
|
||||
lines.push('// Icons are Remix Icon (ri) — https://remixicon.com/ — Apache-2.0.');
|
||||
lines.push('');
|
||||
lines.push('export type IconName =');
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const suffix = i === names.length - 1 ? ';' : '';
|
||||
lines.push(` | ${JSON.stringify(names[i])}${suffix}`);
|
||||
}
|
||||
lines.push('');
|
||||
lines.push('export interface IconData {');
|
||||
lines.push(' /** Inner SVG markup (paths/shapes), rendered inside our <svg> wrapper. */');
|
||||
lines.push(' body: string;');
|
||||
lines.push(' /** Source grid width in px. Remix icons are 24. */');
|
||||
lines.push(' width?: number;');
|
||||
lines.push(' /** Source grid height in px. Remix icons are 24. */');
|
||||
lines.push(' height?: number;');
|
||||
lines.push('}');
|
||||
lines.push('');
|
||||
lines.push('/** Existing name → fully-qualified Remix icon id. */');
|
||||
lines.push('export const NAME_TO_REMIX: Record<IconName, string> = {');
|
||||
for (const name of names) lines.push(` ${keyOf(name)}: ${JSON.stringify('ri:' + NAME_TO_REMIX[name])},`);
|
||||
lines.push('};');
|
||||
lines.push('');
|
||||
lines.push('/** Per-icon SVG data, pulled from @iconify-json/ri. */');
|
||||
lines.push('export const ICON_DATA: Record<IconName, IconData> = {');
|
||||
for (const name of names) {
|
||||
const { body, width, height } = data[name];
|
||||
lines.push(` ${keyOf(name)}: { body: ${JSON.stringify(body)}, width: ${width}, height: ${height} },`);
|
||||
}
|
||||
lines.push('};');
|
||||
lines.push('');
|
||||
|
||||
writeFileSync(OUT, lines.join('\n'));
|
||||
console.log(`gen-icon-data: wrote ${names.length} icons to src/lib/icon-data.ts`);
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<!-- apps/kimi-web/src/components/ui/Icon.vue -->
|
||||
<!-- Design-system §02 icon primitive. Renders a registered line icon from
|
||||
lib/icons.ts at a token size. Use everywhere instead of hand-writing <svg>. -->
|
||||
lib/icons.ts at a token size. Use everywhere instead of hand-writing raw SVG. -->
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { getIcon, SIZE_PX, type IconName, type IconSize } from '../../lib/icons';
|
||||
|
|
@ -15,38 +15,18 @@ const props = withDefaults(
|
|||
{ size: 'md' },
|
||||
);
|
||||
|
||||
const def = computed(() => getIcon(props.name));
|
||||
const entry = computed(() => getIcon(props.name));
|
||||
const px = computed(() => SIZE_PX[props.size]);
|
||||
const viewBox = computed(() => def.value.viewBox ?? '0 0 16 16');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
v-if="def.fill"
|
||||
<component
|
||||
v-if="entry"
|
||||
:is="entry.component"
|
||||
class="kw-icon"
|
||||
:width="px"
|
||||
:height="px"
|
||||
:viewBox="viewBox"
|
||||
fill="currentColor"
|
||||
:aria-label="label"
|
||||
:aria-hidden="label ? undefined : true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
v-html="def.body"
|
||||
/>
|
||||
<svg
|
||||
v-else
|
||||
class="kw-icon"
|
||||
:width="px"
|
||||
:height="px"
|
||||
:viewBox="viewBox"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
:aria-label="label"
|
||||
:aria-hidden="label ? undefined : true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
v-html="def.body"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
11
apps/kimi-web/src/env.d.ts
vendored
11
apps/kimi-web/src/env.d.ts
vendored
|
|
@ -27,3 +27,14 @@ declare module '*?worker&type=module' {
|
|||
const WorkerFactory: new () => Worker;
|
||||
export default WorkerFactory;
|
||||
}
|
||||
|
||||
// unplugin-icons `?raw` imports — `unplugin-icons/types/vue` declares
|
||||
// `~icons/*` as a Vue FunctionalComponent (for direct component imports). The
|
||||
// `?raw` query re-exports the raw SVG source, which must type as `string`;
|
||||
// this more-specific pattern overrides the component declaration for `?raw`
|
||||
// imports only (e.g. `~icons/ri/add-line?raw`), leaving component imports
|
||||
// (`~icons/ri/add-line`) typed as components.
|
||||
declare module '~icons/*?raw' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,199 +0,0 @@
|
|||
// GENERATED FILE — do not edit by hand.
|
||||
// Source of truth: scripts/gen-icon-data.mjs (run `pnpm gen:icons`).
|
||||
// Icons are Remix Icon (ri) — https://remixicon.com/ — Apache-2.0.
|
||||
|
||||
export type IconName =
|
||||
| "plus"
|
||||
| "chat-new"
|
||||
| "close"
|
||||
| "check"
|
||||
| "search"
|
||||
| "copy"
|
||||
| "link"
|
||||
| "external-link"
|
||||
| "download"
|
||||
| "undo"
|
||||
| "send"
|
||||
| "image"
|
||||
| "settings"
|
||||
| "sliders"
|
||||
| "log-in"
|
||||
| "chevron-down"
|
||||
| "chevron-right"
|
||||
| "arrow-up"
|
||||
| "arrow-down"
|
||||
| "arrow-right"
|
||||
| "minus"
|
||||
| "panel-collapse"
|
||||
| "panel-expand"
|
||||
| "expand"
|
||||
| "collapse"
|
||||
| "list"
|
||||
| "sort"
|
||||
| "grip"
|
||||
| "folder"
|
||||
| "folder-closed"
|
||||
| "folder-plus"
|
||||
| "folder-solid"
|
||||
| "file"
|
||||
| "file-text"
|
||||
| "file-plus"
|
||||
| "file-off"
|
||||
| "image-off"
|
||||
| "code"
|
||||
| "terminal"
|
||||
| "pencil"
|
||||
| "tool"
|
||||
| "glob"
|
||||
| "globe"
|
||||
| "check-list"
|
||||
| "bolt"
|
||||
| "git-pull-request"
|
||||
| "message"
|
||||
| "mail"
|
||||
| "user"
|
||||
| "info"
|
||||
| "help-circle"
|
||||
| "alert-triangle"
|
||||
| "clock"
|
||||
| "sparkles"
|
||||
| "play"
|
||||
| "stop"
|
||||
| "star"
|
||||
| "star-outline"
|
||||
| "dots-horizontal";
|
||||
|
||||
export interface IconData {
|
||||
/** Inner SVG markup (paths/shapes), rendered inside our <svg> wrapper. */
|
||||
body: string;
|
||||
/** Source grid width in px. Remix icons are 24. */
|
||||
width?: number;
|
||||
/** Source grid height in px. Remix icons are 24. */
|
||||
height?: number;
|
||||
}
|
||||
|
||||
/** Existing name → fully-qualified Remix icon id. */
|
||||
export const NAME_TO_REMIX: Record<IconName, string> = {
|
||||
plus: "ri:add-line",
|
||||
"chat-new": "ri:chat-new-line",
|
||||
close: "ri:close-line",
|
||||
check: "ri:check-line",
|
||||
search: "ri:search-line",
|
||||
copy: "ri:file-copy-line",
|
||||
link: "ri:links-line",
|
||||
"external-link": "ri:external-link-line",
|
||||
download: "ri:download-line",
|
||||
undo: "ri:arrow-go-back-line",
|
||||
send: "ri:arrow-up-line",
|
||||
image: "ri:image-line",
|
||||
settings: "ri:settings-3-line",
|
||||
sliders: "ri:equalizer-line",
|
||||
"log-in": "ri:login-box-line",
|
||||
"chevron-down": "ri:arrow-down-s-line",
|
||||
"chevron-right": "ri:arrow-right-s-line",
|
||||
"arrow-up": "ri:arrow-up-line",
|
||||
"arrow-down": "ri:arrow-down-line",
|
||||
"arrow-right": "ri:arrow-right-line",
|
||||
minus: "ri:subtract-line",
|
||||
"panel-collapse": "ri:contract-left-line",
|
||||
"panel-expand": "ri:expand-right-line",
|
||||
expand: "ri:expand-diagonal-line",
|
||||
collapse: "ri:collapse-diagonal-line",
|
||||
list: "ri:list-unordered",
|
||||
sort: "ri:sort-desc",
|
||||
grip: "ri:draggable",
|
||||
folder: "ri:folder-open-line",
|
||||
"folder-closed": "ri:folder-line",
|
||||
"folder-plus": "ri:folder-add-line",
|
||||
"folder-solid": "ri:folder-fill",
|
||||
file: "ri:file-line",
|
||||
"file-text": "ri:file-text-line",
|
||||
"file-plus": "ri:file-add-line",
|
||||
"file-off": "ri:file-line",
|
||||
"image-off": "ri:image-line",
|
||||
code: "ri:code-line",
|
||||
terminal: "ri:terminal-box-line",
|
||||
pencil: "ri:pencil-line",
|
||||
tool: "ri:tools-line",
|
||||
glob: "ri:braces-line",
|
||||
globe: "ri:global-line",
|
||||
"check-list": "ri:list-check",
|
||||
bolt: "ri:flashlight-line",
|
||||
"git-pull-request": "ri:git-pull-request-line",
|
||||
message: "ri:message-line",
|
||||
mail: "ri:mail-line",
|
||||
user: "ri:user-line",
|
||||
info: "ri:information-line",
|
||||
"help-circle": "ri:question-line",
|
||||
"alert-triangle": "ri:alert-line",
|
||||
clock: "ri:time-line",
|
||||
sparkles: "ri:sparkling-line",
|
||||
play: "ri:play-fill",
|
||||
stop: "ri:stop-fill",
|
||||
star: "ri:star-fill",
|
||||
"star-outline": "ri:star-line",
|
||||
"dots-horizontal": "ri:more-line",
|
||||
};
|
||||
|
||||
/** Per-icon SVG data, pulled from @iconify-json/ri. */
|
||||
export const ICON_DATA: Record<IconName, IconData> = {
|
||||
plus: { body: "<path fill=\"currentColor\" d=\"M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z\"/>", width: 24, height: 24 },
|
||||
"chat-new": { body: "<path fill=\"currentColor\" d=\"M14 3v2H4v13.385L5.763 17H20v-7h2v8a1 1 0 0 1-1 1H6.455L2 22.5V4a1 1 0 0 1 1-1zm5 0V0h2v3h3v2h-3v3h-2V5h-3V3z\"/>", width: 24, height: 24 },
|
||||
close: { body: "<path fill=\"currentColor\" d=\"m12 10.587l4.95-4.95l1.414 1.414l-4.95 4.95l4.95 4.95l-1.415 1.414l-4.95-4.95l-4.949 4.95l-1.414-1.415l4.95-4.95l-4.95-4.95L7.05 5.638z\"/>", width: 24, height: 24 },
|
||||
check: { body: "<path fill=\"currentColor\" d=\"m10 15.17l9.192-9.191l1.414 1.414L10 17.999l-6.364-6.364l1.414-1.414z\"/>", width: 24, height: 24 },
|
||||
search: { body: "<path fill=\"currentColor\" d=\"m18.031 16.617l4.283 4.282l-1.415 1.415l-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9s9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617m-2.006-.742A6.98 6.98 0 0 0 18 11c0-3.867-3.133-7-7-7s-7 3.133-7 7s3.133 7 7 7a6.98 6.98 0 0 0 4.875-1.975z\"/>", width: 24, height: 24 },
|
||||
copy: { body: "<path fill=\"currentColor\" d=\"M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1 1 0 0 1 3 21l.003-14c0-.552.45-1 1.006-1zM5.002 8L5 20h10V8zM9 6h8v10h2V4H9z\"/>", width: 24, height: 24 },
|
||||
link: { body: "<path fill=\"currentColor\" d=\"m13.06 8.111l1.415 1.414a7 7 0 0 1 0 9.9l-.354.353a7 7 0 1 1-9.9-9.9l1.415 1.415a5 5 0 1 0 7.071 7.071l.354-.354a5 5 0 0 0 0-7.07l-1.415-1.415zm6.718 6.01l-1.414-1.414a5 5 0 0 0-7.071-7.07l-.354.353a5 5 0 0 0 0 7.07l1.415 1.415l-1.415 1.414l-1.414-1.414a7 7 0 0 1 0-9.9l.354-.353a7 7 0 1 1 9.9 9.9\"/>", width: 24, height: 24 },
|
||||
"external-link": { body: "<path fill=\"currentColor\" d=\"M10 6v2H5v11h11v-5h2v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm11-3v8h-2V6.413l-7.793 7.794l-1.414-1.414L17.585 5H13V3z\"/>", width: 24, height: 24 },
|
||||
download: { body: "<path fill=\"currentColor\" d=\"M3 19h18v2H3zm10-5.828L19.071 7.1l1.414 1.414L12 17L3.515 8.515L4.929 7.1L11 13.173V2h2z\"/>", width: 24, height: 24 },
|
||||
undo: { body: "<path fill=\"currentColor\" d=\"m5.828 7l2.536 2.535L6.95 10.95L2 6l4.95-4.95l1.414 1.415L5.828 5H13a8 8 0 1 1 0 16H4v-2h9a6 6 0 0 0 0-12z\"/>", width: 24, height: 24 },
|
||||
send: { body: "<path fill=\"currentColor\" d=\"M13 7.828V20h-2V7.828l-5.364 5.364l-1.414-1.414L12 4l7.778 7.778l-1.414 1.414z\"/>", width: 24, height: 24 },
|
||||
image: { body: "<path fill=\"currentColor\" d=\"M2.992 21A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993zM20 15V5H4v14L14 9zm0 2.828l-6-6L6.828 19H20zM8 11a2 2 0 1 1 0-4a2 2 0 0 1 0 4\"/>", width: 24, height: 24 },
|
||||
settings: { body: "<path fill=\"currentColor\" d=\"M3.34 17a10 10 0 0 1-.979-2.326a3 3 0 0 0 .003-5.347a10 10 0 0 1 2.5-4.337a3 3 0 0 0 4.632-2.674a10 10 0 0 1 5.007.003a3 3 0 0 0 4.632 2.671a10.06 10.06 0 0 1 2.503 4.336a3 3 0 0 0-.002 5.347a10 10 0 0 1-2.501 4.337a3 3 0 0 0-4.632 2.674a10 10 0 0 1-5.007-.002a3 3 0 0 0-4.631-2.672A10 10 0 0 1 3.339 17m5.66.196a5 5 0 0 1 2.25 2.77q.75.07 1.499.002a5 5 0 0 1 2.25-2.772a5 5 0 0 1 3.526-.564q.435-.614.748-1.298A5 5 0 0 1 18 12c0-1.26.47-2.437 1.273-3.334a8 8 0 0 0-.75-1.298A5 5 0 0 1 15 6.804a5 5 0 0 1-2.25-2.77q-.75-.071-1.5-.001A5 5 0 0 1 9 6.804a5 5 0 0 1-3.526.564q-.436.614-.747 1.298A5 5 0 0 1 6 12c0 1.26-.471 2.437-1.273 3.334a8 8 0 0 0 .75 1.298A5 5 0 0 1 9 17.196M12 15a3 3 0 1 1 0-6a3 3 0 0 1 0 6m0-2a1 1 0 1 0 0-2a1 1 0 0 0 0 2\"/>", width: 24, height: 24 },
|
||||
sliders: { body: "<path fill=\"currentColor\" d=\"M6.17 18a3.001 3.001 0 0 1 5.66 0H22v2H11.83a3.001 3.001 0 0 1-5.66 0H2v-2zm6-7a3.001 3.001 0 0 1 5.66 0H22v2h-4.17a3.001 3.001 0 0 1-5.66 0H2v-2zm-6-7a3.001 3.001 0 0 1 5.66 0H22v2H11.83a3.001 3.001 0 0 1-5.66 0H2V4zM9 6a1 1 0 1 0 0-2a1 1 0 0 0 0 2m6 7a1 1 0 1 0 0-2a1 1 0 0 0 0 2m-6 7a1 1 0 1 0 0-2a1 1 0 0 0 0 2\"/>", width: 24, height: 24 },
|
||||
"log-in": { body: "<path fill=\"currentColor\" d=\"M4 15h2v5h12V4H6v5H4V3a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm6-4V8l5 4l-5 4v-3H2v-2z\"/>", width: 24, height: 24 },
|
||||
"chevron-down": { body: "<path fill=\"currentColor\" d=\"m12 13.171l4.95-4.95l1.414 1.415L12 16L5.636 9.636L7.05 8.222z\"/>", width: 24, height: 24 },
|
||||
"chevron-right": { body: "<path fill=\"currentColor\" d=\"m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z\"/>", width: 24, height: 24 },
|
||||
"arrow-up": { body: "<path fill=\"currentColor\" d=\"M13 7.828V20h-2V7.828l-5.364 5.364l-1.414-1.414L12 4l7.778 7.778l-1.414 1.414z\"/>", width: 24, height: 24 },
|
||||
"arrow-down": { body: "<path fill=\"currentColor\" d=\"m13 16.172l5.364-5.364l1.414 1.414L12 20l-7.778-7.778l1.414-1.414L11 16.172V4h2z\"/>", width: 24, height: 24 },
|
||||
"arrow-right": { body: "<path fill=\"currentColor\" d=\"m16.172 11l-5.364-5.364l1.414-1.414L20 12l-7.778 7.778l-1.414-1.414L16.172 13H4v-2z\"/>", width: 24, height: 24 },
|
||||
minus: { body: "<path fill=\"currentColor\" d=\"M5 11v2h14v-2z\"/>", width: 24, height: 24 },
|
||||
"panel-collapse": { body: "<path fill=\"currentColor\" d=\"m15.071 4.929l1.414 1.414L11.83 11H21v2h-9.17l4.656 4.657l-1.414 1.414L8.001 12zm-11.07 14.07V5h2v14z\"/>", width: 24, height: 24 },
|
||||
"panel-expand": { body: "<path fill=\"currentColor\" d=\"m17.172 11l-4.657-4.657l1.414-1.414L21 12l-7.071 7.071l-1.414-1.414L17.172 13H8v-2zM4 19V5h2v14z\"/>", width: 24, height: 24 },
|
||||
expand: { body: "<path fill=\"currentColor\" d=\"M17.586 5H14V3h7v7h-2V6.414l-4.293 4.293l-1.414-1.414zM3 14h2v3.586l4.293-4.293l1.414 1.414L6.414 19H10v2H3z\"/>", width: 24, height: 24 },
|
||||
collapse: { body: "<path fill=\"currentColor\" d=\"M15 4h-2v7h7V9h-3.586l4.293-4.293l-1.414-1.414L15 7.586zM4 15h3.586l-4.293 4.293l1.414 1.414L9 16.414V20h2v-7H4z\"/>", width: 24, height: 24 },
|
||||
list: { body: "<path fill=\"currentColor\" d=\"M8 4h13v2H8zM4.5 6.5a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3m0 7a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3m0 6.9a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3M8 11h13v2H8zm0 7h13v2H8z\"/>", width: 24, height: 24 },
|
||||
sort: { body: "<path fill=\"currentColor\" d=\"M20 4v12h3l-4 5l-4-5h3V4zm-8 14v2H3v-2zm2-7v2H3v-2zm0-7v2H3V4z\"/>", width: 24, height: 24 },
|
||||
grip: { body: "<path fill=\"currentColor\" d=\"M8.5 7a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m0 6.5a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m1.5 5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0M15.5 7a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m1.5 5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0m-1.5 8a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3\"/>", width: 24, height: 24 },
|
||||
folder: { body: "<path fill=\"currentColor\" d=\"M3 21a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414l2 2H20a1 1 0 0 1 1 1v3h-2V7h-7.414l-2-2H4v11.998L5.5 11h17l-2.31 9.243a1 1 0 0 1-.97.757zm16.938-8H7.062l-1.5 6h12.876z\"/>", width: 24, height: 24 },
|
||||
"folder-closed": { body: "<path fill=\"currentColor\" d=\"M4 5v14h16V7h-8.414l-2-2zm8.414 0H21a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414z\"/>", width: 24, height: 24 },
|
||||
"folder-plus": { body: "<path fill=\"currentColor\" d=\"M12.414 5H21a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414zM4 5v14h16V7h-8.414l-2-2zm7 7V9h2v3h3v2h-3v3h-2v-3H8v-2z\"/>", width: 24, height: 24 },
|
||||
"folder-solid": { body: "<path fill=\"currentColor\" d=\"M12.414 5H21a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414z\"/>", width: 24, height: 24 },
|
||||
file: { body: "<path fill=\"currentColor\" d=\"M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z\"/>", width: 24, height: 24 },
|
||||
"file-text": { body: "<path fill=\"currentColor\" d=\"M21 8v12.993A1 1 0 0 1 20.007 22H3.993A.993.993 0 0 1 3 21.008V2.992C3 2.455 3.449 2 4.002 2h10.995zm-2 1h-5V4H5v16h14zM8 7h3v2H8zm0 4h8v2H8zm0 4h8v2H8z\"/>", width: 24, height: 24 },
|
||||
"file-plus": { body: "<path fill=\"currentColor\" d=\"M15 4H5v16h14V8h-4zM3 2.992C3 2.444 3.447 2 3.999 2H16l5 5v13.993A1 1 0 0 1 20.007 22H3.993A1 1 0 0 1 3 21.008zM11 11V8h2v3h3v2h-3v3h-2v-3H8v-2z\"/>", width: 24, height: 24 },
|
||||
"file-off": { body: "<path fill=\"currentColor\" d=\"M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z\"/>", width: 24, height: 24 },
|
||||
"image-off": { body: "<path fill=\"currentColor\" d=\"M2.992 21A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993zM20 15V5H4v14L14 9zm0 2.828l-6-6L6.828 19H20zM8 11a2 2 0 1 1 0-4a2 2 0 0 1 0 4\"/>", width: 24, height: 24 },
|
||||
code: { body: "<path fill=\"currentColor\" d=\"m23 12l-7.071 7.071l-1.414-1.414L20.172 12l-5.657-5.657l1.414-1.414zM3.828 12l5.657 5.657l-1.414 1.414L1 12l7.071-7.071l1.414 1.414z\"/>", width: 24, height: 24 },
|
||||
terminal: { body: "<path fill=\"currentColor\" d=\"M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1m1 2v14h16V5zm8 10h6v2h-6zm-3.333-3L5.838 9.172l1.415-1.415L11.495 12l-4.242 4.243l-1.415-1.415z\"/>", width: 24, height: 24 },
|
||||
pencil: { body: "<path fill=\"currentColor\" d=\"m15.728 9.576l-1.414-1.414L5 17.476v1.414h1.414zm1.414-1.414l1.414-1.414l-1.414-1.414l-1.414 1.414zm-9.9 12.728H3v-4.243L16.435 3.212a1 1 0 0 1 1.414 0l2.829 2.829a1 1 0 0 1 0 1.414z\"/>", width: 24, height: 24 },
|
||||
tool: { body: "<path fill=\"currentColor\" d=\"M5.33 3.272a3.5 3.5 0 0 1 4.254 4.962l10.709 10.71l-1.414 1.414l-10.71-10.71a3.502 3.502 0 0 1-4.962-4.255L5.444 7.63a1.5 1.5 0 0 0 2.121-2.121zm10.367 1.883l3.182-1.768l1.414 1.415l-1.768 3.182l-1.768.353l-2.12 2.121l-1.415-1.414l2.121-2.121zm-6.718 8.132l1.415 1.414l-5.304 5.303a1 1 0 0 1-1.492-1.327l.078-.087z\"/>", width: 24, height: 24 },
|
||||
glob: { body: "<path fill=\"currentColor\" d=\"M4 18v-3.7a1.5 1.5 0 0 0-1.5-1.5H2v-1.6h.5A1.5 1.5 0 0 0 4 9.7V6a3 3 0 0 1 3-3h1v2H7a1 1 0 0 0-1 1v4.1A2 2 0 0 1 4.626 12A2 2 0 0 1 6 13.9V18a1 1 0 0 0 1 1h1v2H7a3 3 0 0 1-3-3m16-3.7V18a3 3 0 0 1-3 3h-1v-2h1a1 1 0 0 0 1-1v-4.1a2 2 0 0 1 1.374-1.9A2 2 0 0 1 18 10.1V6a1 1 0 0 0-1-1h-1V3h1a3 3 0 0 1 3 3v3.7a1.5 1.5 0 0 0 1.5 1.5h.5v1.6h-.5a1.5 1.5 0 0 0-1.5 1.5\"/>", width: 24, height: 24 },
|
||||
globe: { body: "<path fill=\"currentColor\" d=\"M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.01 8.01 0 0 0 5.648 6.667M10.03 13c.151 2.439.848 4.73 1.97 6.752A15.9 15.9 0 0 0 13.97 13zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.01 8.01 0 0 0 19.938 13M4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333A8.01 8.01 0 0 0 4.062 11m5.969 0h3.938A15.9 15.9 0 0 0 12 4.248A15.9 15.9 0 0 0 10.03 11m4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.01 8.01 0 0 0-5.648-6.667\"/>", width: 24, height: 24 },
|
||||
"check-list": { body: "<path fill=\"currentColor\" d=\"M8 4h13v2H8zm-5-.5h3v3H3zm0 7h3v3H3zm0 7h3v3H3zM8 11h13v2H8zm0 7h13v2H8z\"/>", width: 24, height: 24 },
|
||||
bolt: { body: "<path fill=\"currentColor\" d=\"M13 9h8L11 24v-9H4l9-15zm-2 2V7.22L7.532 13H13v4.394L17.263 11z\"/>", width: 24, height: 24 },
|
||||
"git-pull-request": { body: "<path fill=\"currentColor\" d=\"M15 5h2a2 2 0 0 1 2 2v8.17a3.001 3.001 0 1 1-2 0V7h-2v3l-4.5-4L15 2zM5 8.83a3.001 3.001 0 1 1 2 0v6.34a3.001 3.001 0 1 1-2 0zM6 7a1 1 0 1 0 0-2a1 1 0 0 0 0 2m0 12a1 1 0 1 0 0-2a1 1 0 0 0 0 2m12 0a1 1 0 1 0 0-2a1 1 0 0 0 0 2\"/>", width: 24, height: 24 },
|
||||
message: { body: "<path fill=\"currentColor\" d=\"M6.455 19L2 22.5V4a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1zm-.692-2H20V5H4v13.385zM8 10h8v2H8z\"/>", width: 24, height: 24 },
|
||||
mail: { body: "<path fill=\"currentColor\" d=\"M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1m17 4.238l-7.928 7.1L4 7.216V19h16zM4.511 5l7.55 6.662L19.502 5z\"/>", width: 24, height: 24 },
|
||||
user: { body: "<path fill=\"currentColor\" d=\"M4 22a8 8 0 1 1 16 0h-2a6 6 0 0 0-12 0zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6s6 2.685 6 6s-2.685 6-6 6m0-2c2.21 0 4-1.79 4-4s-1.79-4-4-4s-4 1.79-4 4s1.79 4 4 4\"/>", width: 24, height: 24 },
|
||||
info: { body: "<path fill=\"currentColor\" d=\"M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16M11 7h2v2h-2zm0 4h2v6h-2z\"/>", width: 24, height: 24 },
|
||||
"help-circle": { body: "<path fill=\"currentColor\" d=\"M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16m-1-5h2v2h-2zm2-1.645V14h-2v-1.5a1 1 0 0 1 1-1a1.5 1.5 0 1 0-1.471-1.794l-1.962-.393A3.501 3.501 0 1 1 13 13.355\"/>", width: 24, height: 24 },
|
||||
"alert-triangle": { body: "<path fill=\"currentColor\" d=\"m12.866 3l9.526 16.5a1 1 0 0 1-.866 1.5H2.474a1 1 0 0 1-.866-1.5L11.134 3a1 1 0 0 1 1.732 0m-8.66 16h15.588L12 5.5zM11 16h2v2h-2zm0-7h2v5h-2z\"/>", width: 24, height: 24 },
|
||||
clock: { body: "<path fill=\"currentColor\" d=\"M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16m1-8h4v2h-6V7h2z\"/>", width: 24, height: 24 },
|
||||
sparkles: { body: "<path fill=\"currentColor\" d=\"M14 4.438A2.437 2.437 0 0 0 16.438 2h1.125A2.437 2.437 0 0 0 20 4.438v1.125A2.437 2.437 0 0 0 17.563 8h-1.125A2.437 2.437 0 0 0 14 5.563zM1 11a6 6 0 0 0 6-6h2a6 6 0 0 0 6 6v2a6 6 0 0 0-6 6H7a6 6 0 0 0-6-6zm3.876 1A8.04 8.04 0 0 1 8 15.124A8.04 8.04 0 0 1 11.124 12A8.04 8.04 0 0 1 8 8.876A8.04 8.04 0 0 1 4.876 12m12.374 2A3.25 3.25 0 0 1 14 17.25v1.5A3.25 3.25 0 0 1 17.25 22h1.5A3.25 3.25 0 0 1 22 18.75v-1.5A3.25 3.25 0 0 1 18.75 14z\"/>", width: 24, height: 24 },
|
||||
play: { body: "<path fill=\"currentColor\" d=\"M19.376 12.416L8.777 19.482A.5.5 0 0 1 8 19.066V4.934a.5.5 0 0 1 .777-.416l10.599 7.066a.5.5 0 0 1 0 .832\"/>", width: 24, height: 24 },
|
||||
stop: { body: "<path fill=\"currentColor\" d=\"M6 5h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1\"/>", width: 24, height: 24 },
|
||||
star: { body: "<path fill=\"currentColor\" d=\"m12 18.26l-7.053 3.948l1.575-7.928L.588 8.792l8.027-.952L12 .5l3.385 7.34l8.027.952l-5.934 5.488l1.575 7.928z\"/>", width: 24, height: 24 },
|
||||
"star-outline": { body: "<path fill=\"currentColor\" d=\"m12 18.26l-7.053 3.948l1.575-7.928L.588 8.792l8.027-.952L12 .5l3.385 7.34l8.027.952l-5.934 5.488l1.575 7.928zm0-2.292l4.247 2.377l-.948-4.773l3.573-3.305l-4.833-.573l-2.038-4.419l-2.039 4.42l-4.833.572l3.573 3.305l-.948 4.773z\"/>", width: 24, height: 24 },
|
||||
"dots-horizontal": { body: "<path fill=\"currentColor\" d=\"M4.5 10.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S6 12.825 6 12s-.675-1.5-1.5-1.5m15 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S21 12.825 21 12s-.675-1.5-1.5-1.5m-7.5 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5s1.5-.675 1.5-1.5s-.675-1.5-1.5-1.5\"/>", width: 24, height: 24 },
|
||||
};
|
||||
|
|
@ -1,47 +1,65 @@
|
|||
// apps/kimi-web/src/lib/icons.test.ts
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { ICONS, iconSvg } from './icons';
|
||||
import { ICONS, SIZE_PX, getIcon, iconSvg } from './icons';
|
||||
|
||||
describe('ICONS registry', () => {
|
||||
it('has a non-empty body for every entry', () => {
|
||||
for (const [name, def] of Object.entries(ICONS)) {
|
||||
expect(def.body.trim(), `${name} body`).not.toBe('');
|
||||
it('is non-empty', () => {
|
||||
expect(Object.keys(ICONS).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('every entry has a component and a non-empty raw svg', () => {
|
||||
for (const [name, entry] of Object.entries(ICONS)) {
|
||||
// unplugin-icons component can be a function or a defineComponent object
|
||||
const ct = typeof entry.component;
|
||||
expect(['function', 'object'], `${name} component type`).toContain(ct);
|
||||
expect(typeof entry.svg, `${name} svg type`).toBe('string');
|
||||
expect(entry.svg.trim(), `${name} svg`).not.toBe('');
|
||||
expect(entry.svg.toLowerCase(), `${name} svg contains <svg`).toContain('<svg');
|
||||
}
|
||||
});
|
||||
|
||||
it('bodies contain only inner SVG markup (no outer <svg>)', () => {
|
||||
for (const [name, def] of Object.entries(ICONS)) {
|
||||
expect(def.body.toLowerCase(), `${name}`).not.toContain('<svg');
|
||||
it('every entry svg is on a 24x24 grid with a viewBox', () => {
|
||||
for (const [name, entry] of Object.entries(ICONS)) {
|
||||
expect(entry.svg, `${name} viewBox`).toContain('viewBox="0 0 24 24"');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('every entry is fill-based on a 24x24 grid (Remix)', () => {
|
||||
for (const [name, def] of Object.entries(ICONS)) {
|
||||
expect(def.fill, `${name} fill`).toBe(true);
|
||||
expect(def.viewBox, `${name} viewBox`).toBe('0 0 24 24');
|
||||
}
|
||||
describe('getIcon', () => {
|
||||
it('returns the entry for a known name', () => {
|
||||
expect(getIcon('plus')).toBe(ICONS.plus);
|
||||
});
|
||||
|
||||
it('returns undefined for an unknown name (runtime fallback)', () => {
|
||||
// @ts-expect-error - intentional runtime misuse path
|
||||
expect(getIcon('definitely-not-an-icon')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('iconSvg', () => {
|
||||
it('renders a Remix icon with the registry defaults', () => {
|
||||
it('renders a Remix icon with kw-icon class and default md size', () => {
|
||||
const svg = iconSvg('plus');
|
||||
expect(svg.startsWith('<svg class="kw-icon"')).toBe(true);
|
||||
expect(svg).toContain('viewBox="0 0 24 24"');
|
||||
expect(svg).toContain('fill="currentColor"');
|
||||
expect(svg).not.toContain('stroke=');
|
||||
expect(svg).toContain(ICONS.plus.body);
|
||||
expect(svg.startsWith('<svg ')).toBe(true);
|
||||
expect(svg).toContain('class="kw-icon"');
|
||||
expect(svg).toContain('width="16" height="16"');
|
||||
});
|
||||
|
||||
it('maps size tokens to pixel width/height', () => {
|
||||
expect(iconSvg('plus', 'sm')).toContain('width="14" height="14"');
|
||||
expect(iconSvg('plus', 'md')).toContain('width="16" height="16"');
|
||||
expect(iconSvg('plus', 'lg')).toContain('width="20" height="20"');
|
||||
expect(iconSvg('plus', 'sm')).toContain(`width="${SIZE_PX.sm}" height="${SIZE_PX.sm}"`);
|
||||
expect(iconSvg('plus', 'md')).toContain(`width="${SIZE_PX.md}" height="${SIZE_PX.md}"`);
|
||||
expect(iconSvg('plus', 'lg')).toContain(`width="${SIZE_PX.lg}" height="${SIZE_PX.lg}"`);
|
||||
});
|
||||
|
||||
it('renders a filled icon with currentColor and no stroke', () => {
|
||||
const svg = iconSvg('star');
|
||||
expect(svg).toContain('fill="currentColor"');
|
||||
expect(svg).not.toContain('stroke=');
|
||||
it('does not duplicate width/height attributes from the raw icon', () => {
|
||||
const svg = iconSvg('plus');
|
||||
const widthCount = (svg.match(/\bwidth="/g) ?? []).length;
|
||||
const heightCount = (svg.match(/\bheight="/g) ?? []).length;
|
||||
expect(widthCount).toBe(1);
|
||||
expect(heightCount).toBe(1);
|
||||
});
|
||||
|
||||
it('returns empty string for an unknown name', () => {
|
||||
// @ts-expect-error - intentional runtime misuse path
|
||||
expect(iconSvg('definitely-not-an-icon')).toBe('');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,57 +1,374 @@
|
|||
// apps/kimi-web/src/lib/icons.ts
|
||||
// Single source of truth for apps/kimi-web icons (design-system §02).
|
||||
//
|
||||
// Icons come from Remix Icon (https://remixicon.com/, Apache-2.0). The per-icon
|
||||
// SVG data is generated into ./icon-data.ts by scripts/gen-icon-data.mjs — a
|
||||
// tree-shaken subset of @iconify-json/ri — so the registry is library-sourced,
|
||||
// offline, and only bundles the icons we actually use.
|
||||
// Icons come from Remix Icon (https://remixicon.com/, Apache-2.0) and are
|
||||
// bundled by unplugin-icons at build time — only the icons listed below end up
|
||||
// in the production bundle. Each icon is imported twice: once as a Vue
|
||||
// component (for <Icon name=... />) and once as a `?raw` SVG string (for
|
||||
// iconSvg() in v-html contexts such as lib/toolMeta.ts).
|
||||
//
|
||||
// Remix icons are fill-based (fill="currentColor") on a 24x24 source grid; the
|
||||
// rendered size is always the token size. Colour follows text.
|
||||
// rendered size comes from the size token prop. Colour follows text.
|
||||
//
|
||||
// Two consumers share this registry:
|
||||
// - the <Icon> Vue component (components/ui/Icon.vue) for template use;
|
||||
// - iconSvg() below, for v-html contexts (e.g. lib/toolMeta.ts).
|
||||
|
||||
import { iconToSVG } from '@iconify/utils';
|
||||
import { ICON_DATA, type IconData, type IconName } from './icon-data';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
export type { IconName } from './icon-data';
|
||||
export { NAME_TO_REMIX } from './icon-data';
|
||||
// Components (Vue) ---------------------------------------------------------
|
||||
import RiAddLine from '~icons/ri/add-line';
|
||||
import RiAlertLine from '~icons/ri/alert-line';
|
||||
import RiArrowDownLine from '~icons/ri/arrow-down-line';
|
||||
import RiArrowDownSLine from '~icons/ri/arrow-down-s-line';
|
||||
import RiArrowGoBackLine from '~icons/ri/arrow-go-back-line';
|
||||
import RiArrowRightLine from '~icons/ri/arrow-right-line';
|
||||
import RiArrowRightSLine from '~icons/ri/arrow-right-s-line';
|
||||
import RiArrowUpLine from '~icons/ri/arrow-up-line';
|
||||
import RiBracesLine from '~icons/ri/braces-line';
|
||||
import RiChatNewLine from '~icons/ri/chat-new-line';
|
||||
import RiCheckLine from '~icons/ri/check-line';
|
||||
import RiCloseLine from '~icons/ri/close-line';
|
||||
import RiCodeLine from '~icons/ri/code-line';
|
||||
import RiCollapseDiagonalLine from '~icons/ri/collapse-diagonal-line';
|
||||
import RiContractLeftLine from '~icons/ri/contract-left-line';
|
||||
import RiDownloadLine from '~icons/ri/download-line';
|
||||
import RiDraggable from '~icons/ri/draggable';
|
||||
import RiEqualizerLine from '~icons/ri/equalizer-line';
|
||||
import RiExpandDiagonalLine from '~icons/ri/expand-diagonal-line';
|
||||
import RiExpandRightLine from '~icons/ri/expand-right-line';
|
||||
import RiExternalLinkLine from '~icons/ri/external-link-line';
|
||||
import RiFileAddLine from '~icons/ri/file-add-line';
|
||||
import RiFileCopyLine from '~icons/ri/file-copy-line';
|
||||
import RiFileLine from '~icons/ri/file-line';
|
||||
import RiFileTextLine from '~icons/ri/file-text-line';
|
||||
import RiFlashlightLine from '~icons/ri/flashlight-line';
|
||||
import RiFolderAddLine from '~icons/ri/folder-add-line';
|
||||
import RiFolderFill from '~icons/ri/folder-fill';
|
||||
import RiFolderLine from '~icons/ri/folder-line';
|
||||
import RiFolderOpenLine from '~icons/ri/folder-open-line';
|
||||
import RiGitPullRequestLine from '~icons/ri/git-pull-request-line';
|
||||
import RiGlobalLine from '~icons/ri/global-line';
|
||||
import RiImageLine from '~icons/ri/image-line';
|
||||
import RiInformationLine from '~icons/ri/information-line';
|
||||
import RiLinksLine from '~icons/ri/links-line';
|
||||
import RiListCheck from '~icons/ri/list-check';
|
||||
import RiListUnordered from '~icons/ri/list-unordered';
|
||||
import RiLoginBoxLine from '~icons/ri/login-box-line';
|
||||
import RiMailLine from '~icons/ri/mail-line';
|
||||
import RiMessageLine from '~icons/ri/message-line';
|
||||
import RiMoreLine from '~icons/ri/more-line';
|
||||
import RiPencilLine from '~icons/ri/pencil-line';
|
||||
import RiPlayFill from '~icons/ri/play-fill';
|
||||
import RiQuestionLine from '~icons/ri/question-line';
|
||||
import RiSearchLine from '~icons/ri/search-line';
|
||||
import RiSettings3Line from '~icons/ri/settings-3-line';
|
||||
import RiSortDesc from '~icons/ri/sort-desc';
|
||||
import RiSparklingLine from '~icons/ri/sparkling-line';
|
||||
import RiStarFill from '~icons/ri/star-fill';
|
||||
import RiStarLine from '~icons/ri/star-line';
|
||||
import RiStopFill from '~icons/ri/stop-fill';
|
||||
import RiSubtractLine from '~icons/ri/subtract-line';
|
||||
import RiTerminalBoxLine from '~icons/ri/terminal-box-line';
|
||||
import RiTimeLine from '~icons/ri/time-line';
|
||||
import RiToolsLine from '~icons/ri/tools-line';
|
||||
import RiUserLine from '~icons/ri/user-line';
|
||||
|
||||
// Raw SVG strings ----------------------------------------------------------
|
||||
import RawAddLine from '~icons/ri/add-line?raw';
|
||||
import RawAlertLine from '~icons/ri/alert-line?raw';
|
||||
import RawArrowDownLine from '~icons/ri/arrow-down-line?raw';
|
||||
import RawArrowDownSLine from '~icons/ri/arrow-down-s-line?raw';
|
||||
import RawArrowGoBackLine from '~icons/ri/arrow-go-back-line?raw';
|
||||
import RawArrowRightLine from '~icons/ri/arrow-right-line?raw';
|
||||
import RawArrowRightSLine from '~icons/ri/arrow-right-s-line?raw';
|
||||
import RawArrowUpLine from '~icons/ri/arrow-up-line?raw';
|
||||
import RawBracesLine from '~icons/ri/braces-line?raw';
|
||||
import RawChatNewLine from '~icons/ri/chat-new-line?raw';
|
||||
import RawCheckLine from '~icons/ri/check-line?raw';
|
||||
import RawCloseLine from '~icons/ri/close-line?raw';
|
||||
import RawCodeLine from '~icons/ri/code-line?raw';
|
||||
import RawCollapseDiagonalLine from '~icons/ri/collapse-diagonal-line?raw';
|
||||
import RawContractLeftLine from '~icons/ri/contract-left-line?raw';
|
||||
import RawDownloadLine from '~icons/ri/download-line?raw';
|
||||
import RawDraggable from '~icons/ri/draggable?raw';
|
||||
import RawEqualizerLine from '~icons/ri/equalizer-line?raw';
|
||||
import RawExpandDiagonalLine from '~icons/ri/expand-diagonal-line?raw';
|
||||
import RawExpandRightLine from '~icons/ri/expand-right-line?raw';
|
||||
import RawExternalLinkLine from '~icons/ri/external-link-line?raw';
|
||||
import RawFileAddLine from '~icons/ri/file-add-line?raw';
|
||||
import RawFileCopyLine from '~icons/ri/file-copy-line?raw';
|
||||
import RawFileLine from '~icons/ri/file-line?raw';
|
||||
import RawFileTextLine from '~icons/ri/file-text-line?raw';
|
||||
import RawFlashlightLine from '~icons/ri/flashlight-line?raw';
|
||||
import RawFolderAddLine from '~icons/ri/folder-add-line?raw';
|
||||
import RawFolderFill from '~icons/ri/folder-fill?raw';
|
||||
import RawFolderLine from '~icons/ri/folder-line?raw';
|
||||
import RawFolderOpenLine from '~icons/ri/folder-open-line?raw';
|
||||
import RawGitPullRequestLine from '~icons/ri/git-pull-request-line?raw';
|
||||
import RawGlobalLine from '~icons/ri/global-line?raw';
|
||||
import RawImageLine from '~icons/ri/image-line?raw';
|
||||
import RawInformationLine from '~icons/ri/information-line?raw';
|
||||
import RawLinksLine from '~icons/ri/links-line?raw';
|
||||
import RawListCheck from '~icons/ri/list-check?raw';
|
||||
import RawListUnordered from '~icons/ri/list-unordered?raw';
|
||||
import RawLoginBoxLine from '~icons/ri/login-box-line?raw';
|
||||
import RawMailLine from '~icons/ri/mail-line?raw';
|
||||
import RawMessageLine from '~icons/ri/message-line?raw';
|
||||
import RawMoreLine from '~icons/ri/more-line?raw';
|
||||
import RawPencilLine from '~icons/ri/pencil-line?raw';
|
||||
import RawPlayFill from '~icons/ri/play-fill?raw';
|
||||
import RawQuestionLine from '~icons/ri/question-line?raw';
|
||||
import RawSearchLine from '~icons/ri/search-line?raw';
|
||||
import RawSettings3Line from '~icons/ri/settings-3-line?raw';
|
||||
import RawSortDesc from '~icons/ri/sort-desc?raw';
|
||||
import RawSparklingLine from '~icons/ri/sparkling-line?raw';
|
||||
import RawStarFill from '~icons/ri/star-fill?raw';
|
||||
import RawStarLine from '~icons/ri/star-line?raw';
|
||||
import RawStopFill from '~icons/ri/stop-fill?raw';
|
||||
import RawSubtractLine from '~icons/ri/subtract-line?raw';
|
||||
import RawTerminalBoxLine from '~icons/ri/terminal-box-line?raw';
|
||||
import RawTimeLine from '~icons/ri/time-line?raw';
|
||||
import RawToolsLine from '~icons/ri/tools-line?raw';
|
||||
import RawUserLine from '~icons/ri/user-line?raw';
|
||||
|
||||
// Public types -------------------------------------------------------------
|
||||
export type IconName =
|
||||
| 'plus'
|
||||
| 'chat-new'
|
||||
| 'close'
|
||||
| 'check'
|
||||
| 'search'
|
||||
| 'copy'
|
||||
| 'link'
|
||||
| 'external-link'
|
||||
| 'download'
|
||||
| 'undo'
|
||||
| 'send'
|
||||
| 'image'
|
||||
| 'settings'
|
||||
| 'sliders'
|
||||
| 'log-in'
|
||||
| 'chevron-down'
|
||||
| 'chevron-right'
|
||||
| 'arrow-up'
|
||||
| 'arrow-down'
|
||||
| 'arrow-right'
|
||||
| 'minus'
|
||||
| 'panel-collapse'
|
||||
| 'panel-expand'
|
||||
| 'expand'
|
||||
| 'collapse'
|
||||
| 'list'
|
||||
| 'sort'
|
||||
| 'grip'
|
||||
| 'folder'
|
||||
| 'folder-closed'
|
||||
| 'folder-plus'
|
||||
| 'folder-solid'
|
||||
| 'file'
|
||||
| 'file-text'
|
||||
| 'file-plus'
|
||||
| 'file-off'
|
||||
| 'image-off'
|
||||
| 'code'
|
||||
| 'terminal'
|
||||
| 'pencil'
|
||||
| 'tool'
|
||||
| 'glob'
|
||||
| 'globe'
|
||||
| 'check-list'
|
||||
| 'bolt'
|
||||
| 'git-pull-request'
|
||||
| 'message'
|
||||
| 'mail'
|
||||
| 'user'
|
||||
| 'info'
|
||||
| 'help-circle'
|
||||
| 'alert-triangle'
|
||||
| 'clock'
|
||||
| 'sparkles'
|
||||
| 'play'
|
||||
| 'stop'
|
||||
| 'star'
|
||||
| 'star-outline'
|
||||
| 'dots-horizontal';
|
||||
|
||||
export type IconSize = 'sm' | 'md' | 'lg';
|
||||
|
||||
export const SIZE_PX: Record<IconSize, number> = { sm: 14, md: 16, lg: 20 };
|
||||
|
||||
export interface IconDef {
|
||||
/** Inner SVG markup only — no outer <svg>. Remix icons are fill-based. */
|
||||
body: string;
|
||||
/** Source grid. Remix icons are "0 0 24 24". */
|
||||
viewBox?: string;
|
||||
/** Solid icon (fill="currentColor", no stroke). Always true for Remix. */
|
||||
fill?: boolean;
|
||||
export interface IconEntry {
|
||||
/** Vue component that renders the icon (used by <Icon>). */
|
||||
component: Component;
|
||||
/** Raw `<svg>` string (used by iconSvg() in v-html contexts). */
|
||||
svg: string;
|
||||
}
|
||||
|
||||
function viewBoxOf(d: IconData): string {
|
||||
return `0 0 ${d.width ?? 24} ${d.height ?? 24}`;
|
||||
function entry(component: Component, svg: string): IconEntry {
|
||||
return { component, svg };
|
||||
}
|
||||
|
||||
/** Back-compat registry view over the generated Remix data. */
|
||||
export const ICONS: Record<IconName, IconDef> = Object.fromEntries(
|
||||
(Object.entries(ICON_DATA) as [IconName, IconData][]).map(([name, d]) => [
|
||||
name,
|
||||
{ body: d.body, viewBox: viewBoxOf(d), fill: true },
|
||||
]),
|
||||
) as Record<IconName, IconDef>;
|
||||
export const ICONS: Record<IconName, IconEntry> = {
|
||||
plus: entry(RiAddLine, RawAddLine),
|
||||
'chat-new': entry(RiChatNewLine, RawChatNewLine),
|
||||
close: entry(RiCloseLine, RawCloseLine),
|
||||
check: entry(RiCheckLine, RawCheckLine),
|
||||
search: entry(RiSearchLine, RawSearchLine),
|
||||
copy: entry(RiFileCopyLine, RawFileCopyLine),
|
||||
link: entry(RiLinksLine, RawLinksLine),
|
||||
'external-link': entry(RiExternalLinkLine, RawExternalLinkLine),
|
||||
download: entry(RiDownloadLine, RawDownloadLine),
|
||||
undo: entry(RiArrowGoBackLine, RawArrowGoBackLine),
|
||||
send: entry(RiArrowUpLine, RawArrowUpLine),
|
||||
image: entry(RiImageLine, RawImageLine),
|
||||
settings: entry(RiSettings3Line, RawSettings3Line),
|
||||
sliders: entry(RiEqualizerLine, RawEqualizerLine),
|
||||
'log-in': entry(RiLoginBoxLine, RawLoginBoxLine),
|
||||
'chevron-down': entry(RiArrowDownSLine, RawArrowDownSLine),
|
||||
'chevron-right': entry(RiArrowRightSLine, RawArrowRightSLine),
|
||||
'arrow-up': entry(RiArrowUpLine, RawArrowUpLine),
|
||||
'arrow-down': entry(RiArrowDownLine, RawArrowDownLine),
|
||||
'arrow-right': entry(RiArrowRightLine, RawArrowRightLine),
|
||||
minus: entry(RiSubtractLine, RawSubtractLine),
|
||||
'panel-collapse': entry(RiContractLeftLine, RawContractLeftLine),
|
||||
'panel-expand': entry(RiExpandRightLine, RawExpandRightLine),
|
||||
expand: entry(RiExpandDiagonalLine, RawExpandDiagonalLine),
|
||||
collapse: entry(RiCollapseDiagonalLine, RawCollapseDiagonalLine),
|
||||
list: entry(RiListUnordered, RawListUnordered),
|
||||
sort: entry(RiSortDesc, RawSortDesc),
|
||||
grip: entry(RiDraggable, RawDraggable),
|
||||
folder: entry(RiFolderOpenLine, RawFolderOpenLine),
|
||||
'folder-closed': entry(RiFolderLine, RawFolderLine),
|
||||
'folder-plus': entry(RiFolderAddLine, RawFolderAddLine),
|
||||
'folder-solid': entry(RiFolderFill, RawFolderFill),
|
||||
file: entry(RiFileLine, RawFileLine),
|
||||
'file-text': entry(RiFileTextLine, RawFileTextLine),
|
||||
'file-plus': entry(RiFileAddLine, RawFileAddLine),
|
||||
'file-off': entry(RiFileLine, RawFileLine),
|
||||
'image-off': entry(RiImageLine, RawImageLine),
|
||||
code: entry(RiCodeLine, RawCodeLine),
|
||||
terminal: entry(RiTerminalBoxLine, RawTerminalBoxLine),
|
||||
pencil: entry(RiPencilLine, RawPencilLine),
|
||||
tool: entry(RiToolsLine, RawToolsLine),
|
||||
glob: entry(RiBracesLine, RawBracesLine),
|
||||
globe: entry(RiGlobalLine, RawGlobalLine),
|
||||
'check-list': entry(RiListCheck, RawListCheck),
|
||||
bolt: entry(RiFlashlightLine, RawFlashlightLine),
|
||||
'git-pull-request': entry(RiGitPullRequestLine, RawGitPullRequestLine),
|
||||
message: entry(RiMessageLine, RawMessageLine),
|
||||
mail: entry(RiMailLine, RawMailLine),
|
||||
user: entry(RiUserLine, RawUserLine),
|
||||
info: entry(RiInformationLine, RawInformationLine),
|
||||
'help-circle': entry(RiQuestionLine, RawQuestionLine),
|
||||
'alert-triangle': entry(RiAlertLine, RawAlertLine),
|
||||
clock: entry(RiTimeLine, RawTimeLine),
|
||||
sparkles: entry(RiSparklingLine, RawSparklingLine),
|
||||
play: entry(RiPlayFill, RawPlayFill),
|
||||
stop: entry(RiStopFill, RawStopFill),
|
||||
star: entry(RiStarFill, RawStarFill),
|
||||
'star-outline': entry(RiStarLine, RawStarLine),
|
||||
'dots-horizontal': entry(RiMoreLine, RawMoreLine),
|
||||
};
|
||||
|
||||
export function getIcon(name: IconName): IconDef {
|
||||
export function getIcon(name: IconName): IconEntry {
|
||||
return ICONS[name];
|
||||
}
|
||||
|
||||
function applySize(svg: string, px: number): string {
|
||||
return svg
|
||||
.replace(/\s(?:width|height)="[^"]*"/g, '')
|
||||
.replace(/^<svg\b/, `<svg class="kw-icon" width="${px}" height="${px}" aria-hidden="true"`);
|
||||
}
|
||||
|
||||
/** Render an icon to a full <svg> string for v-html contexts. Mirrors <Icon>. */
|
||||
export function iconSvg(name: IconName, size: IconSize = 'md'): string {
|
||||
const px = SIZE_PX[size];
|
||||
const svg = iconToSVG(ICON_DATA[name], { width: px, height: px });
|
||||
const viewBox = svg.attributes.viewBox;
|
||||
return `<svg class="kw-icon" width="${px}" height="${px}" viewBox="${viewBox}" fill="currentColor" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">${svg.body}</svg>`;
|
||||
const entry = ICONS[name];
|
||||
if (!entry) return '';
|
||||
return applySize(entry.svg, SIZE_PX[size]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// catalog grouping — single source of truth for design-system §02 icon list
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Display order + grouping for the design-system §02 icon catalog. */
|
||||
export const ICON_GROUPS: ReadonlyArray<readonly [string, readonly IconName[]]> = [
|
||||
[
|
||||
'Actions',
|
||||
[
|
||||
'plus',
|
||||
'chat-new',
|
||||
'close',
|
||||
'check',
|
||||
'search',
|
||||
'copy',
|
||||
'link',
|
||||
'external-link',
|
||||
'download',
|
||||
'undo',
|
||||
'send',
|
||||
'image',
|
||||
'settings',
|
||||
'sliders',
|
||||
'log-in',
|
||||
],
|
||||
],
|
||||
[
|
||||
'Navigation & layout',
|
||||
[
|
||||
'chevron-down',
|
||||
'chevron-right',
|
||||
'arrow-up',
|
||||
'arrow-down',
|
||||
'arrow-right',
|
||||
'minus',
|
||||
'panel-collapse',
|
||||
'panel-expand',
|
||||
'expand',
|
||||
'collapse',
|
||||
'list',
|
||||
'sort',
|
||||
'grip',
|
||||
],
|
||||
],
|
||||
[
|
||||
'Files & tools',
|
||||
[
|
||||
'folder',
|
||||
'folder-closed',
|
||||
'folder-plus',
|
||||
'folder-solid',
|
||||
'file',
|
||||
'file-text',
|
||||
'file-plus',
|
||||
'file-off',
|
||||
'image-off',
|
||||
'code',
|
||||
'terminal',
|
||||
'pencil',
|
||||
'tool',
|
||||
'glob',
|
||||
'globe',
|
||||
'check-list',
|
||||
'bolt',
|
||||
'git-pull-request',
|
||||
],
|
||||
],
|
||||
['Communication', ['message', 'mail', 'user']],
|
||||
[
|
||||
'Status & media',
|
||||
[
|
||||
'info',
|
||||
'help-circle',
|
||||
'alert-triangle',
|
||||
'clock',
|
||||
'sparkles',
|
||||
'play',
|
||||
'stop',
|
||||
'star',
|
||||
'star-outline',
|
||||
'dots-horizontal',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -214,9 +214,11 @@ summary {
|
|||
}
|
||||
|
||||
/* -- icon primitive (design-system §02) -------------------------------------
|
||||
Shared by the <Icon> component and iconSvg() v-html strings (lib/icons.ts).
|
||||
Icons are Remix Icon (fill="currentColor" on a 24x24 grid), so colour follows
|
||||
text and the rendered size comes from the width/height attributes. */
|
||||
Applied to every design-system icon: the <Icon> component output
|
||||
(components/ui/Icon.vue) and the iconSvg() v-html strings (lib/icons.ts).
|
||||
Both source their SVG from the Remix Icon registry in lib/icons.ts, bundled
|
||||
by unplugin-icons at build time — so only registered icons ship. Colour
|
||||
follows text via fill="currentColor". */
|
||||
.kw-icon {
|
||||
display: inline-block;
|
||||
flex: none;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import { ICON_GROUPS } from '../lib/icons';
|
||||
import Icon from '../components/ui/Icon.vue';
|
||||
|
||||
const emit = defineEmits<{ close: [] }>();
|
||||
|
||||
|
|
@ -301,7 +303,7 @@ onUnmounted(() => {
|
|||
</table>
|
||||
|
||||
<h4 class="mini">Icon</h4>
|
||||
<p>Icons always come from the centralized registry <code>lib/icons.ts</code>: in templates use the <code><Icon name size /></code> component (<code>components/ui/Icon.vue</code>); for <code>v-html</code> contexts (such as a tool glyph) use <code>iconSvg(name, size)</code>. <b>Do not hand-write <code><svg></code></b> — the <code>scripts/check-style.mjs</code> <code>icon-from-registry</code> rule flags stray SVGs. Icons come from <a href="https://remixicon.com/">Remix Icon</a> (Apache-2.0), uniformly in a fill style (<code>fill="currentColor"</code>, 24×24 source grid), with color following the text; size uses the three tokens below. The registry is generated on demand by <code>scripts/gen-icon-data.mjs</code> from <code>@iconify-json/ri</code>, offline and tree-shaken. <b>The whole site uses only this one icon family</b>; do not mix in other icon libraries, and <b>never hand-write SVG paths</b>. When an icon is missing, add it to the registry (edit the mapping in <code>gen-icon-data.mjs</code> and regenerate) rather than drawing it in a component.</p>
|
||||
<p>Icons always come from the centralized registry <code>lib/icons.ts</code>: in templates use the <code><Icon name size /></code> component (<code>components/ui/Icon.vue</code>); for <code>v-html</code> contexts (such as a tool glyph) use <code>iconSvg(name, size)</code>. <b>Do not hand-write <code><svg></code></b> — the <code>scripts/check-style.mjs</code> <code>icon-from-registry</code> rule flags stray SVGs. Icons come from <a href="https://remixicon.com/">Remix Icon</a> (Apache-2.0), uniformly in a fill style (<code>fill="currentColor"</code>, 24×24 source grid), with color following the text; size uses the three tokens below. The registry is bundled on demand by <a href="https://github.com/unplugin/unplugin-icons">unplugin-icons</a> at build time from <code>@iconify-json/ri</code> — only icons imported in <code>lib/icons.ts</code> end up in the production bundle, fully offline and tree-shaken. <b>The whole site uses only this one icon family</b>; do not mix in other icon libraries, and <b>never hand-write SVG paths</b>. When an icon is missing, add it to the registry — two static <code>~icons/ri/*</code> imports (component + <code>?raw</code> string) plus one entry in <code>ICONS</code> in <code>lib/icons.ts</code>; the import names (e.g. <code>RiFolderOpenLine</code> / <code>RawFolderOpenLine</code>) show the <code>ri:</code> icon id. Do not draw it in a component.</p>
|
||||
|
||||
<h4 class="mini">Size scale</h4>
|
||||
<div class="icon-sizes">
|
||||
|
|
@ -311,72 +313,15 @@ onUnmounted(() => {
|
|||
</div>
|
||||
|
||||
<h4 class="mini">Icon library</h4>
|
||||
<p>Currently registered icons, grouped by purpose. The icon data is determined by the <code>NAME_TO_REMIX</code> mapping in <code>scripts/gen-icon-data.mjs</code>; running <code>pnpm gen:icons</code> generates <code>lib/icon-data.ts</code>. This catalog is regenerated with <code>node --experimental-strip-types scripts/gen-icon-catalog.mjs</code> so the registry and the document never drift.</p>
|
||||
<p>Currently registered icons, grouped by purpose. The display order and grouping are defined by <code>ICON_GROUPS</code> in <code>lib/icons.ts</code> (a hand-maintained array covering the same icon names), and this catalog is rendered directly from that array so the registry and the document never drift.</p>
|
||||
<div class="icon-grid">
|
||||
<div class="icon-group-label">Actions</div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></svg><span class="ic-name">plus</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M14 3v2H4v13.385L5.763 17H20v-7h2v8a1 1 0 0 1-1 1H6.455L2 22.5V4a1 1 0 0 1 1-1zm5 0V0h2v3h3v2h-3v3h-2V5h-3V3z"/></svg><span class="ic-name">chat-new</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m12 10.587l4.95-4.95l1.414 1.414l-4.95 4.95l4.95 4.95l-1.415 1.414l-4.95-4.95l-4.949 4.95l-1.414-1.415l4.95-4.95l-4.95-4.95L7.05 5.638z"/></svg><span class="ic-name">close</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m10 15.17l9.192-9.191l1.414 1.414L10 17.999l-6.364-6.364l1.414-1.414z"/></svg><span class="ic-name">check</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m18.031 16.617l4.283 4.282l-1.415 1.415l-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9s9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617m-2.006-.742A6.98 6.98 0 0 0 18 11c0-3.867-3.133-7-7-7s-7 3.133-7 7s3.133 7 7 7a6.98 6.98 0 0 0 4.875-1.975z"/></svg><span class="ic-name">search</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1 1 0 0 1 3 21l.003-14c0-.552.45-1 1.006-1zM5.002 8L5 20h10V8zM9 6h8v10h2V4H9z"/></svg><span class="ic-name">copy</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m13.06 8.111l1.415 1.414a7 7 0 0 1 0 9.9l-.354.353a7 7 0 1 1-9.9-9.9l1.415 1.415a5 5 0 1 0 7.071 7.071l.354-.354a5 5 0 0 0 0-7.07l-1.415-1.415zm6.718 6.01l-1.414-1.414a5 5 0 0 0-7.071-7.07l-.354.353a5 5 0 0 0 0 7.07l1.415 1.415l-1.415 1.414l-1.414-1.414a7 7 0 0 1 0-9.9l.354-.353a7 7 0 1 1 9.9 9.9"/></svg><span class="ic-name">link</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M10 6v2H5v11h11v-5h2v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zm11-3v8h-2V6.413l-7.793 7.794l-1.414-1.414L17.585 5H13V3z"/></svg><span class="ic-name">external-link</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M3 19h18v2H3zm10-5.828L19.071 7.1l1.414 1.414L12 17L3.515 8.515L4.929 7.1L11 13.173V2h2z"/></svg><span class="ic-name">download</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m5.828 7l2.536 2.535L6.95 10.95L2 6l4.95-4.95l1.414 1.415L5.828 5H13a8 8 0 1 1 0 16H4v-2h9a6 6 0 0 0 0-12z"/></svg><span class="ic-name">undo</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M13 7.828V20h-2V7.828l-5.364 5.364l-1.414-1.414L12 4l7.778 7.778l-1.414 1.414z"/></svg><span class="ic-name">send</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M2.992 21A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993zM20 15V5H4v14L14 9zm0 2.828l-6-6L6.828 19H20zM8 11a2 2 0 1 1 0-4a2 2 0 0 1 0 4"/></svg><span class="ic-name">image</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M3.34 17a10 10 0 0 1-.979-2.326a3 3 0 0 0 .003-5.347a10 10 0 0 1 2.5-4.337a3 3 0 0 0 4.632-2.674a10 10 0 0 1 5.007.003a3 3 0 0 0 4.632 2.671a10.06 10.06 0 0 1 2.503 4.336a3 3 0 0 0-.002 5.347a10 10 0 0 1-2.501 4.337a3 3 0 0 0-4.632 2.674a10 10 0 0 1-5.007-.002a3 3 0 0 0-4.631-2.672A10 10 0 0 1 3.339 17m5.66.196a5 5 0 0 1 2.25 2.77q.75.07 1.499.002a5 5 0 0 1 2.25-2.772a5 5 0 0 1 3.526-.564q.435-.614.748-1.298A5 5 0 0 1 18 12c0-1.26.47-2.437 1.273-3.334a8 8 0 0 0-.75-1.298A5 5 0 0 1 15 6.804a5 5 0 0 1-2.25-2.77q-.75-.071-1.5-.001A5 5 0 0 1 9 6.804a5 5 0 0 1-3.526.564q-.436.614-.747 1.298A5 5 0 0 1 6 12c0 1.26-.471 2.437-1.273 3.334a8 8 0 0 0 .75 1.298A5 5 0 0 1 9 17.196M12 15a3 3 0 1 1 0-6a3 3 0 0 1 0 6m0-2a1 1 0 1 0 0-2a1 1 0 0 0 0 2"/></svg><span class="ic-name">settings</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M6.17 18a3.001 3.001 0 0 1 5.66 0H22v2H11.83a3.001 3.001 0 0 1-5.66 0H2v-2zm6-7a3.001 3.001 0 0 1 5.66 0H22v2h-4.17a3.001 3.001 0 0 1-5.66 0H2v-2zm-6-7a3.001 3.001 0 0 1 5.66 0H22v2H11.83a3.001 3.001 0 0 1-5.66 0H2V4zM9 6a1 1 0 1 0 0-2a1 1 0 0 0 0 2m6 7a1 1 0 1 0 0-2a1 1 0 0 0 0 2m-6 7a1 1 0 1 0 0-2a1 1 0 0 0 0 2"/></svg><span class="ic-name">sliders</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M4 15h2v5h12V4H6v5H4V3a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm6-4V8l5 4l-5 4v-3H2v-2z"/></svg><span class="ic-name">log-in</span></div>
|
||||
<div class="icon-group-label">Navigation & layout</div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m12 13.171l4.95-4.95l1.414 1.415L12 16L5.636 9.636L7.05 8.222z"/></svg><span class="ic-name">chevron-down</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z"/></svg><span class="ic-name">chevron-right</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M13 7.828V20h-2V7.828l-5.364 5.364l-1.414-1.414L12 4l7.778 7.778l-1.414 1.414z"/></svg><span class="ic-name">arrow-up</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m13 16.172l5.364-5.364l1.414 1.414L12 20l-7.778-7.778l1.414-1.414L11 16.172V4h2z"/></svg><span class="ic-name">arrow-down</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m16.172 11l-5.364-5.364l1.414-1.414L20 12l-7.778 7.778l-1.414-1.414L16.172 13H4v-2z"/></svg><span class="ic-name">arrow-right</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M5 11v2h14v-2z"/></svg><span class="ic-name">minus</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m15.071 4.929l1.414 1.414L11.83 11H21v2h-9.17l4.656 4.657l-1.414 1.414L8.001 12zm-11.07 14.07V5h2v14z"/></svg><span class="ic-name">panel-collapse</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m17.172 11l-4.657-4.657l1.414-1.414L21 12l-7.071 7.071l-1.414-1.414L17.172 13H8v-2zM4 19V5h2v14z"/></svg><span class="ic-name">panel-expand</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M17.586 5H14V3h7v7h-2V6.414l-4.293 4.293l-1.414-1.414zM3 14h2v3.586l4.293-4.293l1.414 1.414L6.414 19H10v2H3z"/></svg><span class="ic-name">expand</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M15 4h-2v7h7V9h-3.586l4.293-4.293l-1.414-1.414L15 7.586zM4 15h3.586l-4.293 4.293l1.414 1.414L9 16.414V20h2v-7H4z"/></svg><span class="ic-name">collapse</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M8 4h13v2H8zM4.5 6.5a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3m0 7a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3m0 6.9a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3M8 11h13v2H8zm0 7h13v2H8z"/></svg><span class="ic-name">list</span></div>
|
||||
<div class="icon-group-label">Files & tools</div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M3 21a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414l2 2H20a1 1 0 0 1 1 1v3h-2V7h-7.414l-2-2H4v11.998L5.5 11h17l-2.31 9.243a1 1 0 0 1-.97.757zm16.938-8H7.062l-1.5 6h12.876z"/></svg><span class="ic-name">folder</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M4 5v14h16V7h-8.414l-2-2zm8.414 0H21a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414z"/></svg><span class="ic-name">folder-closed</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M12.414 5H21a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414zM4 5v14h16V7h-8.414l-2-2zm7 7V9h2v3h3v2h-3v3h-2v-3H8v-2z"/></svg><span class="ic-name">folder-plus</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M12.414 5H21a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h7.414z"/></svg><span class="ic-name">folder-solid</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z"/></svg><span class="ic-name">file</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M21 8v12.993A1 1 0 0 1 20.007 22H3.993A.993.993 0 0 1 3 21.008V2.992C3 2.455 3.449 2 4.002 2h10.995zm-2 1h-5V4H5v16h14zM8 7h3v2H8zm0 4h8v2H8zm0 4h8v2H8z"/></svg><span class="ic-name">file-text</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M15 4H5v16h14V8h-4zM3 2.992C3 2.444 3.447 2 3.999 2H16l5 5v13.993A1 1 0 0 1 20.007 22H3.993A1 1 0 0 1 3 21.008zM11 11V8h2v3h3v2h-3v3h-2v-3H8v-2z"/></svg><span class="ic-name">file-plus</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M9 2.003V2h10.998C20.55 2 21 2.455 21 2.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 20.993V8zM5.83 8H9V4.83zM11 4v5a1 1 0 0 1-1 1H5v10h14V4z"/></svg><span class="ic-name">file-off</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M2.992 21A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993zM20 15V5H4v14L14 9zm0 2.828l-6-6L6.828 19H20zM8 11a2 2 0 1 1 0-4a2 2 0 0 1 0 4"/></svg><span class="ic-name">image-off</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m23 12l-7.071 7.071l-1.414-1.414L20.172 12l-5.657-5.657l1.414-1.414zM3.828 12l5.657 5.657l-1.414 1.414L1 12l7.071-7.071l1.414 1.414z"/></svg><span class="ic-name">code</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1m1 2v14h16V5zm8 10h6v2h-6zm-3.333-3L5.838 9.172l1.415-1.415L11.495 12l-4.242 4.243l-1.415-1.415z"/></svg><span class="ic-name">terminal</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m15.728 9.576l-1.414-1.414L5 17.476v1.414h1.414zm1.414-1.414l1.414-1.414l-1.414-1.414l-1.414 1.414zm-9.9 12.728H3v-4.243L16.435 3.212a1 1 0 0 1 1.414 0l2.829 2.829a1 1 0 0 1 0 1.414z"/></svg><span class="ic-name">pencil</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M4 18v-3.7a1.5 1.5 0 0 0-1.5-1.5H2v-1.6h.5A1.5 1.5 0 0 0 4 9.7V6a3 3 0 0 1 3-3h1v2H7a1 1 0 0 0-1 1v4.1A2 2 0 0 1 4.626 12A2 2 0 0 1 6 13.9V18a1 1 0 0 0 1 1h1v2H7a3 3 0 0 1-3-3m16-3.7V18a3 3 0 0 1-3 3h-1v-2h1a1 1 0 0 0 1-1v-4.1a2 2 0 0 1 1.374-1.9A2 2 0 0 1 18 10.1V6a1 1 0 0 0-1-1h-1V3h1a3 3 0 0 1 3 3v3.7a1.5 1.5 0 0 0 1.5 1.5h.5v1.6h-.5a1.5 1.5 0 0 0-1.5 1.5"/></svg><span class="ic-name">glob</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.01 8.01 0 0 0 5.648 6.667M10.03 13c.151 2.439.848 4.73 1.97 6.752A15.9 15.9 0 0 0 13.97 13zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.01 8.01 0 0 0 19.938 13M4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333A8.01 8.01 0 0 0 4.062 11m5.969 0h3.938A15.9 15.9 0 0 0 12 4.248A15.9 15.9 0 0 0 10.03 11m4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.01 8.01 0 0 0-5.648-6.667"/></svg><span class="ic-name">globe</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M8 4h13v2H8zm-5-.5h3v3H3zm0 7h3v3H3zm0 7h3v3H3zM8 11h13v2H8zm0 7h13v2H8z"/></svg><span class="ic-name">check-list</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M13 9h8L11 24v-9H4l9-15zm-2 2V7.22L7.532 13H13v4.394L17.263 11z"/></svg><span class="ic-name">bolt</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M15 5h2a2 2 0 0 1 2 2v8.17a3.001 3.001 0 1 1-2 0V7h-2v3l-4.5-4L15 2zM5 8.83a3.001 3.001 0 1 1 2 0v6.34a3.001 3.001 0 1 1-2 0zM6 7a1 1 0 1 0 0-2a1 1 0 0 0 0 2m0 12a1 1 0 1 0 0-2a1 1 0 0 0 0 2m12 0a1 1 0 1 0 0-2a1 1 0 0 0 0 2"/></svg><span class="ic-name">git-pull-request</span></div>
|
||||
<div class="icon-group-label">Communication</div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M6.455 19L2 22.5V4a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1zm-.692-2H20V5H4v13.385zM8 10h8v2H8z"/></svg><span class="ic-name">message</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1m17 4.238l-7.928 7.1L4 7.216V19h16zM4.511 5l7.55 6.662L19.502 5z"/></svg><span class="ic-name">mail</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M4 22a8 8 0 1 1 16 0h-2a6 6 0 0 0-12 0zm8-9c-3.315 0-6-2.685-6-6s2.685-6 6-6s6 2.685 6 6s-2.685 6-6 6m0-2c2.21 0 4-1.79 4-4s-1.79-4-4-4s-4 1.79-4 4s1.79 4 4 4"/></svg><span class="ic-name">user</span></div>
|
||||
<div class="icon-group-label">Status & media</div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16M11 7h2v2h-2zm0 4h2v6h-2z"/></svg><span class="ic-name">info</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16m-1-5h2v2h-2zm2-1.645V14h-2v-1.5a1 1 0 0 1 1-1a1.5 1.5 0 1 0-1.471-1.794l-1.962-.393A3.501 3.501 0 1 1 13 13.355"/></svg><span class="ic-name">help-circle</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m12.866 3l9.526 16.5a1 1 0 0 1-.866 1.5H2.474a1 1 0 0 1-.866-1.5L11.134 3a1 1 0 0 1 1.732 0m-8.66 16h15.588L12 5.5zM11 16h2v2h-2zm0-7h2v5h-2z"/></svg><span class="ic-name">alert-triangle</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16m1-8h4v2h-6V7h2z"/></svg><span class="ic-name">clock</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M14 4.438A2.437 2.437 0 0 0 16.438 2h1.125A2.437 2.437 0 0 0 20 4.438v1.125A2.437 2.437 0 0 0 17.563 8h-1.125A2.437 2.437 0 0 0 14 5.563zM1 11a6 6 0 0 0 6-6h2a6 6 0 0 0 6 6v2a6 6 0 0 0-6 6H7a6 6 0 0 0-6-6zm3.876 1A8.04 8.04 0 0 1 8 15.124A8.04 8.04 0 0 1 11.124 12A8.04 8.04 0 0 1 8 8.876A8.04 8.04 0 0 1 4.876 12m12.374 2A3.25 3.25 0 0 1 14 17.25v1.5A3.25 3.25 0 0 1 17.25 22h1.5A3.25 3.25 0 0 1 22 18.75v-1.5A3.25 3.25 0 0 1 18.75 14z"/></svg><span class="ic-name">sparkles</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M19.376 12.416L8.777 19.482A.5.5 0 0 1 8 19.066V4.934a.5.5 0 0 1 .777-.416l10.599 7.066a.5.5 0 0 1 0 .832"/></svg><span class="ic-name">play</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M6 5h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1"/></svg><span class="ic-name">stop</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m12 18.26l-7.053 3.948l1.575-7.928L.588 8.792l8.027-.952L12 .5l3.385 7.34l8.027.952l-5.934 5.488l1.575 7.928z"/></svg><span class="ic-name">star</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="m12 18.26l-7.053 3.948l1.575-7.928L.588 8.792l8.027-.952L12 .5l3.385 7.34l8.027.952l-5.934 5.488l1.575 7.928zm0-2.292l4.247 2.377l-.948-4.773l3.573-3.305l-4.833-.573l-2.038-4.419l-2.039 4.42l-4.833.572l3.573 3.305l-.948 4.773z"/></svg><span class="ic-name">star-outline</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M4.5 10.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S6 12.825 6 12s-.675-1.5-1.5-1.5m15 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S21 12.825 21 12s-.675-1.5-1.5-1.5m-7.5 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5s1.5-.675 1.5-1.5s-.675-1.5-1.5-1.5"/></svg><span class="ic-name">dots-horizontal</span></div>
|
||||
<div class="icon-group-label">Other</div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M20 4v12h3l-4 5l-4-5h3V4zm-8 14v2H3v-2zm2-7v2H3v-2zm0-7v2H3V4z"/></svg><span class="ic-name">sort</span></div>
|
||||
<div class="icon-cell"><svg class="p-ic" viewBox="0 0 24 24" fill="currentColor"><path fill="currentColor" d="M8.5 7a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m0 6.5a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m1.5 5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0M15.5 7a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m1.5 5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0m-1.5 8a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3"/></svg><span class="ic-name">grip</span></div>
|
||||
<template v-for="[label, names] in ICON_GROUPS" :key="label">
|
||||
<div class="icon-group-label">{{ label }}</div>
|
||||
<div v-for="name in names" :key="name" class="icon-cell">
|
||||
<Icon :name="name" />
|
||||
<span class="ic-name">{{ name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<p>Do not use emoji as functional icons (the sole exception is the moon phases 🌑…🌘, used only in the "waiting for the Agent to respond" chat state). The Kimi brand mark (the 32×22 eye logo) is a brand asset and is not part of this icon system.</p>
|
||||
|
|
@ -2340,7 +2285,7 @@ onUnmounted(() => {
|
|||
.icon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 8px; margin: 14px 0; }
|
||||
.icon-group-label { grid-column: 1 / -1; margin-top: 10px; font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--d-fg-muted); }
|
||||
.icon-cell { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 1px solid var(--d-line); border-radius: 8px; background: var(--d-surface); }
|
||||
.icon-cell .p-ic { width: 20px; height: 20px; color: var(--d-fg-soft); }
|
||||
.icon-cell .kw-icon { width: 20px; height: 20px; color: var(--d-fg-soft); }
|
||||
.icon-cell .ic-name { font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; color: var(--d-fg); }
|
||||
.icon-sizes { display: flex; align-items: end; gap: 22px; flex-wrap: wrap; }
|
||||
.icon-sizes .sz { display: flex; flex-direction: column; align-items: center; gap: 8px; font-size: 11px; color: var(--d-fg-muted); font-family: "JetBrains Mono", ui-monospace, monospace; }
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"types": ["vite/client"]
|
||||
"types": ["vite/client", "unplugin-icons/types/vue"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.vue", "src/env.d.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import Icons from 'unplugin-icons/vite';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const webPort = Number(process.env.WEB_PORT) || 5175;
|
||||
|
|
@ -11,7 +12,7 @@ const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url),
|
|||
};
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
plugins: [vue(), Icons({ compiler: 'vue3' })],
|
||||
// Expose the dev proxy's upstream server target to the client so the UI can
|
||||
// show which server it is connected to (the browser otherwise only sees its
|
||||
// own same-origin URL). Unused by the same-origin production build.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue