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
129 changes: 114 additions & 15 deletions demo/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,39 +23,138 @@ function element<T extends HTMLElement>(id: string): T {

const settingsDialog = element<HTMLDialogElement>('chart-settings');
const settingsButton = element<HTMLButtonElement>('open-settings');
const desktopSettings = window.matchMedia('(min-width: 1000px)');
const chartPanel = element('smith').closest<HTMLElement>('.chart-panel')!;
const scalesPanel = element('parameter-panel').closest<HTMLElement>('.scales-panel')!;
const settingsBackdrop = element('settings-backdrop');
const settingsBackground = document.querySelectorAll<HTMLElement>(
'main > header, main > .toolbar, .scales-panel, .workspace > aside, main > footer',
);

function positionSettingsDialog(): void {
if (!settingsDialog.open || !window.matchMedia('(min-width: 1000px)').matches) {
if (!settingsDialog.open || !desktopSettings.matches) {
return;
}
const anchor = element('parameter-panel').closest<HTMLElement>('.scales-panel')!;
const bounds = anchor.getBoundingClientRect();
// Align with the readout panel while reserving at least 320 px for settings on short screens.
const top = Math.max(16, Math.min(bounds.top, window.innerHeight - 336));
settingsDialog.style.setProperty('--settings-top', `${top}px`);
settingsDialog.style.setProperty('--settings-left', `${bounds.left}px`);
settingsDialog.style.setProperty('--settings-width', `${bounds.width}px`);
const chartBounds = chartPanel.getBoundingClientRect();
const scalesBounds = scalesPanel.getBoundingClientRect();
settingsDialog.style.setProperty('--settings-top', `${chartBounds.top}px`);
settingsDialog.style.setProperty('--settings-left', `${scalesBounds.left}px`);
settingsDialog.style.setProperty('--settings-width', `${scalesBounds.width}px`);
settingsDialog.style.setProperty('--settings-height', `${chartBounds.height}px`);
}

function setDesktopSettingsActive(active: boolean): void {
document.body.classList.toggle('settings-active', active);
settingsBackdrop.hidden = !active;
for (const region of settingsBackground) {
region.inert = active;
}
}
settingsButton.addEventListener('click', () => {
settingsDialog.showModal();

function showSettings(): void {
setDesktopSettingsActive(desktopSettings.matches);
if (desktopSettings.matches) {
// A non-modal dialog leaves the chart available for zoom and marker interaction.
settingsDialog.show();
} else {
settingsDialog.showModal();
}
positionSettingsDialog();
});
}

settingsButton.addEventListener('click', showSettings);
window.addEventListener('resize', positionSettingsDialog);
window.addEventListener('scroll', positionSettingsDialog, { passive: true });
settingsDialog.addEventListener('close', () => settingsButton.focus({ preventScroll: true }));
const settingsResizeObserver = new ResizeObserver(positionSettingsDialog);
settingsResizeObserver.observe(chartPanel);
settingsResizeObserver.observe(scalesPanel);
desktopSettings.addEventListener('change', () => {
if (settingsDialog.open) {
settingsDialog.close();
showSettings();
}
});

let settingsPressOutside = false;
function outsideSettings(event: MouseEvent): boolean {
if (event.target === settingsBackdrop) {
return true;
}
const bounds = settingsDialog.getBoundingClientRect();
return (
event.target === settingsDialog &&
(event.clientX < bounds.left ||
event.clientX > bounds.right ||
event.clientY < bounds.top ||
event.clientY > bounds.bottom)
);
}
document.addEventListener('pointerdown', (event) => {
settingsPressOutside = settingsDialog.open && event.button === 0 && outsideSettings(event);
});
document.addEventListener('pointercancel', () => {
settingsPressOutside = false;
});
document.addEventListener('click', (event) => {
if (settingsPressOutside && outsideSettings(event)) {
settingsDialog.close();
}
settingsPressOutside = false;
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && settingsDialog.open && desktopSettings.matches) {
event.preventDefault();
settingsDialog.close();
}
});
settingsDialog.addEventListener('close', () => {
// A breakpoint change closes and immediately reopens the dialog in its new mode.
if (settingsDialog.open) {
return;
}
settingsPressOutside = false;
setDesktopSettingsActive(false);
settingsButton.focus({ preventScroll: true });
});

function demoAppearance(theme: SmithAppearance['theme']): SmithAppearance {
return {
theme,
...(theme === 'dark'
? {
overrides: {
background: '#191c20',
fontFamily: 'Arial, sans-serif',
grid: { stroke: '#727984', textColor: '#c7cbd1' },
boundary: { stroke: '#9da3ad' },
scales: {
stroke: '#727984',
boundaryColor: '#9da3ad',
textColor: '#b5bcc5',
valueColor: '#e4e7eb',
indicatorColor: '#e9ac59',
indicatorOutline: '#191c20',
},
cursor: { pointColor: '#a0bce5', impedanceColor: '#a0bce5' },
},
}
: {}),
};
}

const smith = new Smith({
appearance: { theme: 'dark' },
appearance: demoAppearance('dark'),
interaction: { zoom: true, cursor: true },
peripheralScales: { visible: true },
grid: { detail: 'detailed' },
});
smith.draw('#smith');
const scales = new SmithScales({ theme: 'dark' });
const scales = new SmithScales(demoAppearance('dark'));
scales.draw('#smith-scales');

element<HTMLSelectElement>('theme').addEventListener('change', (event) => {
const theme = (event.target as HTMLSelectElement).value as SmithAppearance['theme'];
const appearance: SmithAppearance = { theme };
const appearance = demoAppearance(theme);
smith.setOptions({ appearance });
scales.setAppearance(appearance);
document.documentElement.dataset.theme = theme;
Expand Down
144 changes: 106 additions & 38 deletions demo/style.css
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
:root {
--text: #24364b;
--page: #f3f6f9;
--text: #25292f;
--page: #f4f5f6;
--surface: white;
--muted: #617286;
--accent: #287e76;
--border: #dce4eb;
--input-border: #bbcbd4;
--hover: #edf5f4;
--muted: #626a75;
--accent: #315f94;
--border: #dfe2e6;
--input-border: #b8bec7;
--hover: #e9edf2;
--error: #b42318;
color-scheme: light;

font-family: Inter, system-ui, sans-serif;
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
sans-serif;
color: var(--text);
background: var(--page);
font-synthesis: none;
Expand All @@ -29,15 +34,9 @@ main {
header {
margin-bottom: 28px;
}
.eyebrow {
color: var(--accent);
font-size: 11px;
font-weight: 750;
letter-spacing: 0.16em;
}
h1 {
font-size: 36px;
letter-spacing: -0.04em;
letter-spacing: -0.025em;
margin: 6px 0 8px;
}
p {
Expand All @@ -62,10 +61,8 @@ nav[aria-label='Resources'] a {
align-items: center;
justify-content: flex-start;
gap: 20px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px 20px;
border-bottom: 1px solid var(--border);
padding: 0 0 12px;
margin-bottom: 20px;
}
fieldset {
Expand Down Expand Up @@ -121,7 +118,7 @@ select:focus-visible {
.panel {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
border-radius: 6px;
}
.chart-column {
min-width: 0;
Expand Down Expand Up @@ -389,7 +386,7 @@ footer {
}
.toolbar {
gap: 12px;
padding: 8px 16px;
padding: 0 0 12px;
margin-bottom: 12px;
}
.workspace {
Expand All @@ -398,7 +395,7 @@ footer {
}
.chart-column {
grid-template-columns: repeat(2, minmax(0, 1fr));
align-items: start;
align-items: stretch;
gap: 16px;
}
#smith {
Expand All @@ -416,7 +413,7 @@ footer {
}
aside {
grid-template-columns: repeat(2, minmax(0, 1fr));
align-items: start;
align-items: stretch;
}
}
@media (max-width: 800px) {
Expand Down Expand Up @@ -447,15 +444,15 @@ footer {
}

:root[data-theme='dark'] {
--text: #e2e8f0;
--page: #020617;
--surface: #0f172a;
--muted: #94a3b8;
--accent: #5eead4;
--border: #334155;
--input-border: #475569;
--hover: #1e293b;
--error: #fca5a5;
--text: #e4e7eb;
--page: #111315;
--surface: #191c20;
--muted: #a0a7b2;
--accent: #a0bce5;
--border: #30353c;
--input-border: #4d5560;
--hover: #272c33;
--error: #efa19b;
color-scheme: dark;
}
#chart-settings,
Expand All @@ -464,26 +461,40 @@ footer {
max-height: calc(100dvh - 32px);
padding: 24px;
border: 1px solid var(--border);
border-radius: 16px;
border-radius: 8px;
background: var(--surface);
color: var(--text);
box-shadow: 0 16px 64px rgb(0 0 0 / 25%);
box-shadow: 0 12px 32px rgb(0 0 0 / 22%);
}
#chart-settings::backdrop,
#image-export::backdrop {
background: rgb(2 6 23 / 45%);
background: rgb(0 0 0 / 42%);
}
@media (min-width: 1000px) {
#chart-settings {
position: fixed;
z-index: 21;
inset: var(--settings-top, 16px) auto auto var(--settings-left, calc(50% + 8px));
margin: 0;
width: var(--settings-width, calc(50vw - 32px));
max-height: calc(100dvh - var(--settings-top, 16px) - 16px);
height: var(--settings-height);
max-height: none;
overflow-y: auto;
overscroll-behavior: contain;
}
#chart-settings::backdrop {
background: rgb(2 6 23 / 25%);
.settings-active .chart-panel {
position: relative;
z-index: 21;
}
}
#settings-backdrop:not([hidden]) {
position: fixed;
inset: 0;
z-index: 20;
background: rgb(10 12 15 / 72%);
backdrop-filter: grayscale(1);
-webkit-backdrop-filter: grayscale(1);
}
.settings-heading {
display: flex;
align-items: center;
Expand Down Expand Up @@ -667,3 +678,60 @@ select,
width: 100%;
margin: 6px 0 12px;
}

.header-controls {
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
gap: 12px 24px;
font-size: 13px;
}
.theme-control {
display: flex;
align-items: center;
gap: 8px;
}
.theme-control label {
color: var(--muted);
}
.theme-control select {
min-height: 36px;
border: 1px solid var(--input-border);
border-radius: 4px;
padding-block: 6px;
padding-left: 10px;
font: inherit;
}
button:disabled,
select:disabled {
opacity: 0.5;
cursor: default;
}
input[type='file']::file-selector-button {
margin-right: 12px;
padding: 7px 10px;
border: 1px solid var(--input-border);
border-radius: 4px;
background: var(--surface);
color: var(--text);
cursor: pointer;
}
input[type='file']::file-selector-button:hover {
background: var(--hover);
}
@media (max-width: 600px) {
.header-heading {
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.header-controls {
width: 100%;
justify-content: space-between;
gap: 12px;
}
nav[aria-label='Resources'] {
gap: 12px;
}
}
6 changes: 5 additions & 1 deletion docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ those individual overrides. Invalid appearance settings throw before making chan

SVG exports resolve the appearance without requiring theme CSS. A configured
background is included in the exported image; `transparent` preserves transparency.
The **Theme** selector in the demo's **Chart settings** dialog updates both library components and the demo UI.
The **Theme** selector in the demo's top-right header updates both library components and the demo UI.
On desktop, **Chart settings** opens beside the chart at the same height. The chart
remains interactive, including zoom and markers, while the rest of the page is
dimmed and inactive. Click the dimmed area, press Escape, or use Close to dismiss
settings. On smaller screens, settings use a modal dialog.

## Grid layers

Expand Down
Loading
Loading