Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ SF.createButton({ text: 'Stop', variant: 'danger' }) // red bg, white tex
SF.createButton({ text: 'Save', variant: 'primary' }) // emerald-700 bg
SF.createButton({ text: 'Cancel', variant: 'default' }) // gray border
SF.createButton({ icon: 'fa-gear', variant: 'ghost', circle: true })
SF.createButton({ text: 'Settings', icon: 'fa-gear', variant: 'ghost', iconOnly: true })
SF.createButton({ text: 'Submit', variant: 'primary', pill: true })
SF.createButton({ text: 'Delete', variant: 'danger', outline: true })
SF.createButton({ text: 'Sm', variant: 'primary', size: 'small' })
Expand Down Expand Up @@ -184,6 +185,10 @@ var card = SF.rail.createCard({
columns: 5,
type: 'CAMERA',
typeStyle: { bg: 'rgba(59,130,246,0.15)', color: '#1d4ed8', border: '1px solid rgba(59,130,246,0.3)' },
badges: [
'TEMPRA',
{ label: 'HOT', style: { bg: 'rgba(239,68,68,0.12)', color: '#b91c1c', border: '1px solid rgba(239,68,68,0.3)' } },
],
gauges: [
{ label: 'Temp', pct: 85, style: 'heat', text: '850/1000°C' },
{ label: 'Load', pct: 60, style: 'load', text: '120/200 kg' },
Expand Down Expand Up @@ -216,6 +221,7 @@ card.setSolving(true);
```

Gauge styles: `heat` (blue→amber→red), `load` (emerald→amber→red), `emerald` (solid green).
`badges` accepts either strings or `{ label, style }` objects for extra resource metadata.

## Gantt Chart

Expand Down
1 change: 1 addition & 0 deletions WIREFRAME.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ var header = SF.rail.createHeader({
var card = SF.rail.createCard({
id: 'furnace-1', name: 'FORNO 1', labelWidth: 200,
type: 'CAMERA', typeStyle: { bg: 'rgba(59,130,246,0.15)', color: '#1d4ed8' },
badges: ['TEMPRA', { label: 'HOT', style: { bg: 'rgba(239,68,68,0.12)', color: '#b91c1c' } }],
columns: 5,
gauges: [
{ label: 'Temp', pct: 85, style: 'heat', text: '850/1000°C' },
Expand Down
4 changes: 3 additions & 1 deletion js-src/03-buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
btn.appendChild(icon);
}

if (config.text && !config.circle) {
if (config.text && !config.circle && !config.iconOnly) {
btn.appendChild(document.createTextNode(config.text));
}

Expand All @@ -46,6 +46,8 @@

if (config.ariaLabel) {
btn.setAttribute('aria-label', config.ariaLabel);
} else if (config.iconOnly && config.text) {
btn.setAttribute('aria-label', config.text);
} else if (config.icon && !config.text) {
btn.setAttribute('aria-label', config.icon.replace(/fa-/, '').replace(/-/g, ' '));
}
Expand Down
23 changes: 23 additions & 0 deletions js-src/13-rail.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,27 @@
}
meta.appendChild(badge);
}
var badges = Array.isArray(config.badges)
? config.badges
: config.badges
? [config.badges]
: [];
if (badges.length) {
badges.forEach(function (entry) {
if (!entry) return;
if (typeof entry === 'string') {
meta.appendChild(sf.el('span', { className: 'sf-resource-type-badge' }, entry));
return;
}
var extraBadge = sf.el('span', { className: 'sf-resource-type-badge' }, entry.label || '');
if (entry.style) {
extraBadge.style.background = entry.style.bg || '';
extraBadge.style.color = entry.style.color || '';
extraBadge.style.border = entry.style.border || '';
}
meta.appendChild(extraBadge);
});
}
identity.appendChild(meta);
}
resHeader.appendChild(identity);
Expand Down Expand Up @@ -130,6 +151,7 @@
horizon: config.heatmap.horizon || 1,
label: config.heatmap.label,
segments: config.heatmap.segments,
labelWidth: labelWidth,
};
heatmapCfg.railConfig = config;
var heatmap = sf.rail.createHeatmap(heatmapCfg);
Expand Down Expand Up @@ -185,6 +207,7 @@
if (!config || !config.segments || !Array.isArray(config.segments) || config.segments.length === 0) return null;

var heatmap = sf.el('div', { className: 'sf-heatmap' });
heatmap.style.gridTemplateColumns = (config.labelWidth || 200) + 'px 1fr';
var label = sf.el('div', { className: 'sf-heatmap-label' }, config.label || '');
heatmap.appendChild(label);

Expand Down
27 changes: 26 additions & 1 deletion static/sf/sf.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ const SF = (function () {
btn.appendChild(icon);
}

if (config.text && !config.circle) {
if (config.text && !config.circle && !config.iconOnly) {
btn.appendChild(document.createTextNode(config.text));
}

Expand All @@ -229,6 +229,8 @@ const SF = (function () {

if (config.ariaLabel) {
btn.setAttribute('aria-label', config.ariaLabel);
} else if (config.iconOnly && config.text) {
btn.setAttribute('aria-label', config.text);
} else if (config.icon && !config.text) {
btn.setAttribute('aria-label', config.icon.replace(/fa-/, '').replace(/-/g, ' '));
}
Expand Down Expand Up @@ -1220,6 +1222,27 @@ const SF = (function () {
}
meta.appendChild(badge);
}
var badges = Array.isArray(config.badges)
? config.badges
: config.badges
? [config.badges]
: [];
if (badges.length) {
badges.forEach(function (entry) {
if (!entry) return;
if (typeof entry === 'string') {
meta.appendChild(sf.el('span', { className: 'sf-resource-type-badge' }, entry));
return;
}
var extraBadge = sf.el('span', { className: 'sf-resource-type-badge' }, entry.label || '');
if (entry.style) {
extraBadge.style.background = entry.style.bg || '';
extraBadge.style.color = entry.style.color || '';
extraBadge.style.border = entry.style.border || '';
}
meta.appendChild(extraBadge);
});
}
identity.appendChild(meta);
}
resHeader.appendChild(identity);
Expand Down Expand Up @@ -1285,6 +1308,7 @@ const SF = (function () {
horizon: config.heatmap.horizon || 1,
label: config.heatmap.label,
segments: config.heatmap.segments,
labelWidth: labelWidth,
};
heatmapCfg.railConfig = config;
var heatmap = sf.rail.createHeatmap(heatmapCfg);
Expand Down Expand Up @@ -1340,6 +1364,7 @@ const SF = (function () {
if (!config || !config.segments || !Array.isArray(config.segments) || config.segments.length === 0) return null;

var heatmap = sf.el('div', { className: 'sf-heatmap' });
heatmap.style.gridTemplateColumns = (config.labelWidth || 200) + 'px 1fr';
var label = sf.el('div', { className: 'sf-heatmap-label' }, config.label || '');
heatmap.appendChild(label);

Expand Down
65 changes: 65 additions & 0 deletions tests/button-rail-knobs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const vm = require('node:vm');

const { createDom } = require('./support/fake-dom');

const ROOT = path.resolve(__dirname, '..');

function loadSf(files, overrides = {}) {
const { document, window, Node } = createDom();
const context = vm.createContext({
console,
document,
window,
Node,
setTimeout,
clearTimeout,
...overrides,
});

files.forEach((file) => {
const source = fs.readFileSync(path.join(ROOT, file), 'utf8');
vm.runInContext(source, context, { filename: file });
});

return { SF: context.window.SF, document };
}

test('iconOnly buttons keep an accessible label without rendering text content', () => {
const { SF } = loadSf(['js-src/00-core.js', 'js-src/03-buttons.js']);

const button = SF.createButton({
text: 'Settings',
icon: 'fa-gear',
iconOnly: true,
});

assert.equal(button.textContent, '');
assert.equal(button.attributes['aria-label'], 'Settings');
});

test('rail card badges accept a single string badge and preserve heatmap alignment', () => {
const { SF } = loadSf(['js-src/00-core.js', 'js-src/13-rail.js']);

const card = SF.rail.createCard({
name: 'Kiln 1',
badges: 'TEMPRA',
labelWidth: 220,
columns: 4,
heatmap: {
label: 'Load',
horizon: 100,
segments: [{ start: 0, end: 25, color: '#0f0' }],
},
});

const badges = card.el.querySelectorAll('.sf-resource-type-badge');
const heatmap = card.el.querySelector('.sf-heatmap');

assert.equal(badges.length, 1);
assert.equal(badges[0].textContent, 'TEMPRA');
assert.equal(heatmap.style.gridTemplateColumns, '220px 1fr');
});
Loading