fix: replace emoji characters with SVG icons in buttons and labels
All checks were successful
Lint & Test / test (push) Successful in 1m19s
All checks were successful
Lint & Test / test (push) Successful in 1m19s
- Endpoint copy buttons: 📋 → ICON_CLONE (3 places) - Scene preset "used by": 🔗 → ICON_LINK - Automation webhook condition: 🔗 → ICON_WEB - Add "No Emoji" rule to contexts/frontend.md
This commit is contained in:
@@ -32,6 +32,16 @@ Defined in `server/src/wled_controller/static/css/base.css`.
|
|||||||
| `--success-color` | `#28a745` | `#2e7d32` | Success indicators |
|
| `--success-color` | `#28a745` | `#2e7d32` | Success indicators |
|
||||||
| `--shadow-color` | `rgba(0,0,0,0.3)` | `rgba(0,0,0,0.12)` | Box shadows |
|
| `--shadow-color` | `rgba(0,0,0,0.3)` | `rgba(0,0,0,0.12)` | Box shadows |
|
||||||
|
|
||||||
|
## Icons — No Emoji (IMPORTANT)
|
||||||
|
|
||||||
|
**NEVER use emoji characters (`🔗`, `📋`, `🔍`, etc.) in buttons, labels, or card metadata.** Always use SVG icons from `core/icons.ts` (which wraps Lucide icon paths from `core/icon-paths.ts`).
|
||||||
|
|
||||||
|
- Import the constant: `import { ICON_CLONE } from '../core/icons.ts'`
|
||||||
|
- Use in template literals: `` `<button class="btn btn-icon">${ICON_CLONE}</button>` ``
|
||||||
|
- To add a new icon: copy inner SVG elements from [Lucide](https://lucide.dev) into `icon-paths.ts`, then export a named constant in `icons.ts`
|
||||||
|
|
||||||
|
Emoji render inconsistently across OS, break monochrome icon themes, and cannot be styled with CSS `color`/`stroke`.
|
||||||
|
|
||||||
## UI Conventions for Dialogs
|
## UI Conventions for Dialogs
|
||||||
|
|
||||||
### Hints
|
### Hints
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { showToast, showConfirm, setTabRefreshing } from '../core/ui.ts';
|
|||||||
import { Modal } from '../core/modal.ts';
|
import { Modal } from '../core/modal.ts';
|
||||||
import { CardSection } from '../core/card-sections.ts';
|
import { CardSection } from '../core/card-sections.ts';
|
||||||
import { updateTabBadge, updateSubTabHash } from './tabs.ts';
|
import { updateTabBadge, updateSubTabHash } from './tabs.ts';
|
||||||
import { ICON_SETTINGS, ICON_START, ICON_PAUSE, ICON_CLOCK, ICON_AUTOMATION, ICON_HELP, ICON_OK, ICON_TIMER, ICON_MONITOR, ICON_RADIO, ICON_SCENE, ICON_CLONE, ICON_TRASH, ICON_CIRCLE_OFF, ICON_UNDO } from '../core/icons.ts';
|
import { ICON_SETTINGS, ICON_START, ICON_PAUSE, ICON_CLOCK, ICON_AUTOMATION, ICON_HELP, ICON_OK, ICON_TIMER, ICON_MONITOR, ICON_RADIO, ICON_SCENE, ICON_CLONE, ICON_TRASH, ICON_CIRCLE_OFF, ICON_UNDO, ICON_WEB } from '../core/icons.ts';
|
||||||
import * as P from '../core/icon-paths.ts';
|
import * as P from '../core/icon-paths.ts';
|
||||||
import { wrapCard } from '../core/card-colors.ts';
|
import { wrapCard } from '../core/card-colors.ts';
|
||||||
import { TagInput, renderTagChips } from '../core/tag-input.ts';
|
import { TagInput, renderTagChips } from '../core/tag-input.ts';
|
||||||
@@ -247,7 +247,7 @@ const CONDITION_PILL_RENDERERS: Record<string, ConditionPillRenderer> = {
|
|||||||
return `<span class="stream-card-prop">${ICON_MONITOR} ${t('automations.condition.display_state')}: ${stateLabel}</span>`;
|
return `<span class="stream-card-prop">${ICON_MONITOR} ${t('automations.condition.display_state')}: ${stateLabel}</span>`;
|
||||||
},
|
},
|
||||||
mqtt: (c) => `<span class="stream-card-prop stream-card-prop-full">${ICON_RADIO} ${t('automations.condition.mqtt')}: ${escapeHtml(c.topic || '')} = ${escapeHtml(c.payload || '*')}</span>`,
|
mqtt: (c) => `<span class="stream-card-prop stream-card-prop-full">${ICON_RADIO} ${t('automations.condition.mqtt')}: ${escapeHtml(c.topic || '')} = ${escapeHtml(c.payload || '*')}</span>`,
|
||||||
webhook: (c) => `<span class="stream-card-prop">🔗 ${t('automations.condition.webhook')}</span>`,
|
webhook: (c) => `<span class="stream-card-prop">${ICON_WEB} ${t('automations.condition.webhook')}</span>`,
|
||||||
};
|
};
|
||||||
|
|
||||||
function createAutomationCard(automation: Automation, sceneMap = new Map()) {
|
function createAutomationCard(automation: Automation, sceneMap = new Map()) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { fetchWithAuth, escapeHtml } from '../core/api.ts';
|
|||||||
import { t } from '../core/i18n.ts';
|
import { t } from '../core/i18n.ts';
|
||||||
import { showToast } from '../core/ui.ts';
|
import { showToast } from '../core/ui.ts';
|
||||||
import {
|
import {
|
||||||
ICON_SEARCH,
|
ICON_SEARCH, ICON_CLONE,
|
||||||
} from '../core/icons.ts';
|
} from '../core/icons.ts';
|
||||||
import * as P from '../core/icon-paths.ts';
|
import * as P from '../core/icon-paths.ts';
|
||||||
import { IconSelect } from '../core/icon-select.ts';
|
import { IconSelect } from '../core/icon-select.ts';
|
||||||
@@ -272,6 +272,6 @@ export function showNotificationEndpoint(cssId: any) {
|
|||||||
const url = `${base}/color-strip-sources/${cssId}/notify`;
|
const url = `${base}/color-strip-sources/${cssId}/notify`;
|
||||||
el.innerHTML = `
|
el.innerHTML = `
|
||||||
<small class="endpoint-label">POST</small>
|
<small class="endpoint-label">POST</small>
|
||||||
<div class="ws-url-row"><input type="text" value="${url}" readonly style="font-size:0.85em"><button type="button" class="btn btn-sm btn-secondary" onclick="copyEndpointUrl(this)" title="Copy">📋</button></div>
|
<div class="ws-url-row"><input type="text" value="${url}" readonly style="font-size:0.85em"><button type="button" class="btn btn-sm btn-secondary" onclick="copyEndpointUrl(this)" title="Copy">${ICON_CLONE}</button></div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1862,9 +1862,9 @@ function _showApiInputEndpoints(cssId: any) {
|
|||||||
const wsUrl = `${wsBase}/color-strip-sources/${cssId}/ws?token=${encodeURIComponent(apiKey)}`;
|
const wsUrl = `${wsBase}/color-strip-sources/${cssId}/ws?token=${encodeURIComponent(apiKey)}`;
|
||||||
el.innerHTML = `
|
el.innerHTML = `
|
||||||
<small class="endpoint-label">REST POST</small>
|
<small class="endpoint-label">REST POST</small>
|
||||||
<div class="ws-url-row" style="margin-bottom:6px"><input type="text" value="${restUrl}" readonly style="font-size:0.85em"><button type="button" class="btn btn-sm btn-secondary" onclick="copyEndpointUrl(this)" title="Copy">📋</button></div>
|
<div class="ws-url-row" style="margin-bottom:6px"><input type="text" value="${restUrl}" readonly style="font-size:0.85em"><button type="button" class="btn btn-sm btn-secondary" onclick="copyEndpointUrl(this)" title="Copy">${ICON_CLONE}</button></div>
|
||||||
<small class="endpoint-label">WebSocket</small>
|
<small class="endpoint-label">WebSocket</small>
|
||||||
<div class="ws-url-row"><input type="text" value="${wsUrl}" readonly style="font-size:0.85em"><button type="button" class="btn btn-sm btn-secondary" onclick="copyEndpointUrl(this)" title="Copy">📋</button></div>
|
<div class="ws-url-row"><input type="text" value="${wsUrl}" readonly style="font-size:0.85em"><button type="button" class="btn btn-sm btn-secondary" onclick="copyEndpointUrl(this)" title="Copy">${ICON_CLONE}</button></div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { showToast, showConfirm } from '../core/ui.ts';
|
|||||||
import { Modal } from '../core/modal.ts';
|
import { Modal } from '../core/modal.ts';
|
||||||
import { CardSection } from '../core/card-sections.ts';
|
import { CardSection } from '../core/card-sections.ts';
|
||||||
import {
|
import {
|
||||||
ICON_SCENE, ICON_CAPTURE, ICON_START, ICON_EDIT, ICON_REFRESH, ICON_TARGET, ICON_CLONE, ICON_TRASH,
|
ICON_SCENE, ICON_CAPTURE, ICON_START, ICON_EDIT, ICON_REFRESH, ICON_TARGET, ICON_CLONE, ICON_TRASH, ICON_LINK,
|
||||||
} from '../core/icons.ts';
|
} from '../core/icons.ts';
|
||||||
import { scenePresetsCache, outputTargetsCache, automationsCacheObj } from '../core/state.ts';
|
import { scenePresetsCache, outputTargetsCache, automationsCacheObj } from '../core/state.ts';
|
||||||
import { TagInput, renderTagChips } from '../core/tag-input.ts';
|
import { TagInput, renderTagChips } from '../core/tag-input.ts';
|
||||||
@@ -82,7 +82,7 @@ export function createSceneCard(preset: ScenePreset) {
|
|||||||
|
|
||||||
const meta = [
|
const meta = [
|
||||||
targetCount > 0 ? `${ICON_TARGET} ${targetCount} ${t('scenes.targets_count')}` : null,
|
targetCount > 0 ? `${ICON_TARGET} ${targetCount} ${t('scenes.targets_count')}` : null,
|
||||||
usedByCount > 0 ? `🔗 ${t('scene_preset.used_by').replace('%d', usedByCount)}` : null,
|
usedByCount > 0 ? `${ICON_LINK} ${t('scene_preset.used_by').replace('%d', usedByCount)}` : null,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
const updated = preset.updated_at ? new Date(preset.updated_at).toLocaleString() : '';
|
const updated = preset.updated_at ? new Date(preset.updated_at).toLocaleString() : '';
|
||||||
|
|||||||
Reference in New Issue
Block a user