Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
/browser/IgBlazorSamples.Server/Components/**
/browser/IgBlazorSamples.Client/wwwroot/*.js
/browser/IgBlazorSamples.Server/wwwroot/*.js
# Hand-written, unlike the generated sample scripts the rule above targets.
!/browser/IgBlazorSamples.Client/wwwroot/sample-theme.js
/browser/IgBlazorSamples.Client/wwwroot/sb/*.js
/browser/IgBlazorSamples.Server/wwwroot/sb/*.js
/browser/IgBlazorSamples.Client/wwwroot/js/*bundle.js
Expand Down
11 changes: 8 additions & 3 deletions browser/IgBlazorSamples.Client/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@
<link href="https://dl.infragistics.com/x/css/samples/shared.v6.css" rel="stylesheet" />
<link href="https://dl.infragistics.com/x/css/samples/blazor.css" rel="stylesheet" />
<link href="https://dl.infragistics.com/x/img/browsers/blazor.ico" rel="icon" type="image/x-icon">
<link href="_content/IgniteUI.Blazor/themes/light/bootstrap.css" rel="stylesheet" />
<link href="_content/IgniteUI.Blazor/themes/grid/light/bootstrap.css" rel="stylesheet" />
<link href="_content/IgniteUI.Blazor/themes/light/bootstrap.css" rel="stylesheet" data-igd-theme-link="components" />
<link href="_content/IgniteUI.Blazor/themes/grid/light/bootstrap.css" rel="stylesheet" data-igd-theme-link="grid" />
<!-- Repoints the two links above when embedded in a docs sample iframe. -->
<script src="sample-theme.js"></script>
</head>
<body>
<body class="ig-typography ig-scrollbar">
<app class="sb-app"><img class="sb-loading" src="https://dl.infragistics.com/x/img/browsers/loading.gif" /></app>
<div class="sb-error-ui">
An unhandled error has occurred.
Expand Down Expand Up @@ -243,6 +245,9 @@
<script src="sb/chip-styling.js"></script>
<script src="sb/chip-variants.js"></script>
<script src="sb/combo-templates.js"></script>
<script src="sb/chat-features.js"></script>
<script src="sb/chat-overview.js"></script>
<script src="sb/chat-styling.js"></script>
<script src="sb/query-builder-overview.js"></script>
<script src="sb/query-builder-template.js"></script>
<script src="sb/dock-manager-styling.js"></script>
Expand Down
265 changes: 265 additions & 0 deletions browser/IgBlazorSamples.Client/wwwroot/sample-theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/**
* sample-theme.js
*
* Lets the docs site re-theme this samples browser while it is embedded in a
* docs <Sample> iframe.
*
* The docs ThemingWidget dispatches `igd-theme-change`; the Sample widget
* bridges that to `postMessage({ type: 'igd-sample-theme', theme, mode })` on
* the frame, and re-posts the current selection on every iframe `load`. Here we
* validate the sender, repoint the Ignite UI theme <link> elements and provide
* the selection to the web components as their theme context.
*
* Dormant unless a trusted docs host asks for a theme.
*/
(function () {
'use strict';

var MESSAGE_TYPE = 'igd-sample-theme';
var LINK_ATTR = 'data-igd-theme-link';
var DARK_QUERY = '(prefers-color-scheme: dark)';

// The theme stylesheets only carry the palette, typography and other global
// variables; each Shadow DOM component adopts its own per-theme styles from
// the nearest theme provider, which it looks up with a bubbling, composed
// `context-request` event for this key (the Lit context protocol that
// <igc-theme-provider> answers). Answering it on the window makes this script
// the provider for every component on the page, including ones rendered
// outside the app root (tile drag ghosts) and ones from separately bundled
// packages (GridLite), with no wrapper element and nothing extra to load.
var THEME_CONTEXT = 'ig-theme-context';

var THEMES = ['material', 'fluent', 'bootstrap', 'indigo'];

// Keyed by the LINK_ATTR value on each stylesheet in index.html. Hrefs stay
// relative so they keep resolving against the deployed <base>.
var HREFS = {
components: function (mode, theme) {
return '_content/IgniteUI.Blazor/themes/' + mode + '/' + theme + '.css';
},
grid: function (mode, theme) {
return '_content/IgniteUI.Blazor/themes/grid/' + mode + '/' + theme + '.css';
}
};

// The <link> in effect per key, and the replacements a pending selection is loading.
var activeLinks = {};
var stagedLinks = [];
// The newest selection, and the one whose stylesheets are in effect.
var selected = null;
var applied = null;
// Bumped per applied selection, so only the newest one can take effect.
var latestRequest = 0;

// Handed to the components; undefined until a docs theme is applied, which
// they ignore, keeping the global theme they would have without this script.
var themeContext;
// Each subscribed component's context callback, mapped to its unsubscribe.
var subscribers = new Map();

function isTrustedOrigin(origin) {
if (origin === window.location.origin) {
return true;
}

var hostname;
try {
hostname = new URL(origin).hostname;
} catch (e) {
return false;
}

if (hostname === 'localhost' || hostname === '127.0.0.1') {
return true;
}
return hostname === 'infragistics.com' ||
hostname.endsWith('.infragistics.com');
}

function parseMessage(data) {
if (!data || typeof data !== 'object') {
return null;
}
if (data.type !== MESSAGE_TYPE && data.event !== MESSAGE_TYPE) {
return null;
}

var theme = String(data.theme || data.themeName || '').toLowerCase();
if (THEMES.indexOf(theme) === -1) {
return null;
}

var rawMode = String(data.mode || 'light').toLowerCase();
var mode = (rawMode === 'dark' || rawMode === 'system') ? rawMode : 'light';

return { theme: theme, mode: mode };
}

function resolveMode(mode) {
if (mode === 'light' || mode === 'dark') {
return mode;
}
return window.matchMedia && window.matchMedia(DARK_QUERY).matches ? 'dark' : 'light';
}

function onContextRequest(event) {
if (event.context !== THEME_CONTEXT) {
return;
}
event.stopPropagation();

var callback = event.callback;
if (!event.subscribe) {
callback(themeContext);
return;
}
// Components unsubscribe when they disconnect, so this only ever holds
// the ones on the page.
if (!subscribers.has(callback)) {
subscribers.set(callback, function () {
subscribers.delete(callback);
});
}
callback(themeContext, subscribers.get(callback));
}

function provideTheme(theme, variant) {
if (themeContext && themeContext.theme === theme && themeContext.variant === variant) {
return;
}
themeContext = { theme: theme, variant: variant };
subscribers.forEach(function (unsubscribe, callback) {
callback(themeContext, unsubscribe);
});
}

// Drops the stylesheets a superseded or failed selection was loading; they
// never applied, so this can't leave the page unstyled.
function removeStaged() {
stagedLinks.forEach(function (link) {
if (link.parentNode) {
link.parentNode.removeChild(link);
}
});
stagedLinks = [];
}

function applyTheme(theme, mode) {
var resolved = resolveMode(mode);
var request = ++latestRequest;
var selection = { theme: theme, mode: mode };
var incoming = {};
var pending = 0;
var failed = false;

removeStaged();

// Everything switches in one step once every new stylesheet is in: the
// palette, the root attributes sample CSS keys on, and the components'
// own styles, so no frame mixes the old theme with the new one.
var commit = function () {
for (var key in incoming) {
var outgoing = activeLinks[key];
incoming[key].removeAttribute('media');
outgoing.parentNode.removeChild(outgoing);
activeLinks[key] = incoming[key];
}
stagedLinks = [];
applied = selection;

var root = document.documentElement;
root.setAttribute('data-igd-theme', theme);
root.setAttribute('data-igd-mode', resolved);
root.style.colorScheme = resolved;

provideTheme(theme, resolved);
};

var onLoad = function () {
if (!failed && request === latestRequest && --pending === 0) {
commit();
}
};

// Keeps the current theme and forgets the failed selection, so choosing
// it again retries instead of being skipped as a repeat.
var onError = function () {
if (!failed && request === latestRequest) {
failed = true;
removeStaged();
selected = applied;
}
};

for (var key in HREFS) {
if (!Object.prototype.hasOwnProperty.call(HREFS, key) || !activeLinks[key]) {
continue;
}
var href = HREFS[key](resolved, theme);
if (activeLinks[key].getAttribute('href') === href) {
continue;
}

// media="print" fetches the stylesheet without applying it before commit.
var link = activeLinks[key].cloneNode(false);
link.setAttribute('href', href);
link.setAttribute('media', 'print');
link.addEventListener('load', onLoad);
link.addEventListener('error', onError);
activeLinks[key].parentNode.insertBefore(link, activeLinks[key].nextSibling);
stagedLinks.push(link);
incoming[key] = link;
pending++;
}

if (pending === 0) {
commit();
}
}

function onMessage(event) {
// Only the embedding docs page may drive the theme.
if (event.source !== window.parent || !isTrustedOrigin(event.origin)) {
return;
}

var next = parseMessage(event.data);
if (!next) {
return;
}
if (selected && selected.theme === next.theme && selected.mode === next.mode) {
return;
}

selected = next;
applyTheme(next.theme, next.mode);
}

function watchSystemMode() {
var query = window.matchMedia && window.matchMedia(DARK_QUERY);
if (!query || !query.addEventListener) {
return;
}
query.addEventListener('change', function () {
if (selected && selected.mode === 'system') {
applyTheme(selected.theme, selected.mode);
}
});
}

if (window.parent === window) {
return;
}

for (var key in HREFS) {
if (Object.prototype.hasOwnProperty.call(HREFS, key)) {
activeLinks[key] = document.head.querySelector('link[' + LINK_ATTR + '="' + key + '"]');
}
}

// index.html loads this script before any component bundle, so every
// component's theme request reaches this listener.
window.addEventListener('context-request', onContextRequest);
window.addEventListener('message', onMessage);
watchSystemMode();
})();
2 changes: 1 addition & 1 deletion browser/IgBlazorSamples.Core/wwwroot/css/browser.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ app {

height: 100%;
width: 100%;
padding: 0.5rem;
/* padding: 0.5rem; */
display: flex;
flex-direction: column;
justify-content: stretch;
Expand Down
1 change: 1 addition & 0 deletions samples/inputs/badge/dot/App.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@using IgniteUI.Blazor.Controls


<div class="badge-dot">
<div class="dot-example icon-example">
<div class="icon-circle">
Expand Down
20 changes: 5 additions & 15 deletions samples/inputs/badge/dot/wwwroot/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,6 @@ igc-avatar {
width: 12px;
}

.row-indicator igc-badge,
.nav-icon igc-badge {
--ig-badge-background-color: #0057A9;
}

.row-indicator igc-badge::part(base),
.nav-icon igc-badge::part(base) {
background-color: #0057A9;
}

.row-title {
flex: 1;
}
Expand All @@ -109,7 +99,7 @@ igc-avatar {
}

.row-time.unread {
color: #0057A9;
color: var(--ig-gray-900);
font-weight: 600;
}

Expand All @@ -133,7 +123,7 @@ igc-avatar {
align-items: center;
gap: 4px;
min-width: 56px;
color: #556c86;
color: var(--ig-gray-600);
font-family: "Aktiv Grotesk", sans-serif;
font-size: 13px;
font-weight: 400;
Expand All @@ -143,12 +133,12 @@ igc-avatar {
}

.nav-item.active {
color: #0057A9;
color: var(--ig-primary-800);
}

.nav-item.active igc-icon {
color: #0057A9;
fill: #0057A9;
color: var(--ig-primary-800);;
fill: var(--ig-primary-800);
}

.nav-icon {
Expand Down
1 change: 1 addition & 0 deletions samples/inputs/badge/icon/App.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@using IgniteUI.Blazor.Controls


<div class="badge-icon">
@foreach (var item in Badges)
{
Expand Down
Loading
Loading