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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,12 @@

/* ---------- Proof strip ----------

A single hairline grid: the 1px gap IS the rule between cells, so the four claims read as one
row of facts rather than as four more cards. It is also the one child of the stack that is a
A single hairline grid: the rules between cells are drawn in the 1px gap, so the four claims
read as one row of facts rather than as four more cards. They are the cells' own outlines
meeting in the gap, not the strip's color showing through it: when the cells wrap with room to
spare, the empty end of the last row stays the surface color instead of turning into a grey
block. Outlines paint above the siblings next to them, and the strip's overflow clips them at
its own frame. It is also the one child of the stack that is a
control rather than a band, so it opts out of the full-bleed treatment and keeps the
container's gutters. */
.bb-landing > section.bb-proof {
Expand All @@ -192,7 +196,7 @@
width: min(100% - 3rem, var(--bb-content-width));
margin: 3.5rem auto;
padding: 0;
background: var(--bb-border);
background: var(--bb-surface);
border: 1px solid var(--bb-border);
border-radius: var(--bb-radius-lg);
box-shadow: var(--bb-shadow-card);
Expand All @@ -201,6 +205,7 @@

.bb-proof > div {
background: var(--bb-surface);
outline: 1px solid var(--bb-border);
padding: 1.25rem 1.5rem;
display: flex;
flex-direction: column;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
.bb-palette-scrim {
position: fixed;
inset: 0;
/* The header's backdrop-filter makes it the containing block of its fixed descendants, so
`inset: 0` alone would span only the 56px bar. The header is pinned to the top of the
viewport, so a viewport-tall box from its top edge is the whole screen. */
bottom: auto;
height: 100vh;
z-index: 1100;
display: flex;
justify-content: center;
Expand Down
12 changes: 7 additions & 5 deletions src/Brouter/Bit.Brouter.Demo/Client/Shared/DocsShell.razor
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
the "on this page" rail. The rail's contents are built by site.js from whatever headings the
page rendered - see the note on the <nav> below. *@

<div class="bb-docs" @onkeydown="OnKeyDown">
<aside class="bb-docs-sidebar @(_menuOpen ? "bb-open" : null)" id="bb-docs-nav" aria-label="Documentation navigation">
<div class="bb-docs">
<aside class="bb-docs-sidebar @(_menuOpen ? "bb-open" : null)" id="bb-docs-nav" aria-label="Documentation navigation" @onkeydown="OnKeyDown">
<p class="bb-overline bb-docs-nav-title">Documentation</p>

@foreach (var section in DocsCatalog.Sections)
Expand Down Expand Up @@ -43,7 +43,7 @@
<div class="bb-docs-main">
@* The narrow-width entry into the panel above. It sits with the breadcrumb because at
those widths the two together are the whole "where am I / where else can I go" row. *@
<div class="bb-docs-topbar">
<div class="bb-docs-topbar" @onkeydown="OnKeyDown">
<button @ref="_toggle" type="button" class="bb-docs-menu-toggle" aria-expanded="@(_menuOpen ? "true" : "false")"
aria-controls="bb-docs-nav" @onclick="() => _menuOpen = !_menuOpen">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
Expand Down Expand Up @@ -139,8 +139,10 @@

/// <summary>
/// Escape closes the narrow-width drawer, the way every other overlay on the site closes. The
/// handler sits on the shell rather than on the panel because the button that opens the drawer
/// is outside the panel it controls, and a keydown reaches the shell from either of them.
/// handler sits on the panel and on the topbar - the button that opens the drawer is outside the
/// panel it controls - and deliberately not on the whole shell: there it would hear every key
/// typed into a docs page (the constraint tester's input, say), and each one would be an event
/// and a render of this shell.
/// </summary>
private void OnKeyDown(KeyboardEventArgs args)
{
Expand Down
4 changes: 4 additions & 0 deletions src/Brouter/Bit.Brouter.Demo/Client/Shared/Header.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@
.bb-header-scrim {
position: fixed;
inset: var(--bb-header-height) 0 0;
/* Fixed, but inside the header - whose backdrop-filter makes it the containing block - so the
height is spelled out rather than taken from a bottom edge that would be the bar's own. */
bottom: auto;
height: calc(100vh - var(--bb-header-height));
background: var(--bb-scrim);
animation: bb-fade-in var(--bb-motion-duration) var(--bb-motion-easing) both;
}
Expand Down
75 changes: 43 additions & 32 deletions src/Brouter/Bit.Brouter.Demo/Client/wwwroot/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -377,22 +377,23 @@ svg {

/* One focus indicator for the whole site, drawn the Fluent 2 way: a hairline in the surface
color hugging the control, and a heavier neutral stroke outside it. Written as an outline plus
a ring shadow so it follows the control's own corner radius and never displaces layout. */
:focus-visible {
a ring shadow so it follows the control's own corner radius and never displaces layout.

Not on elements with tabindex="-1": those are focused by script, never by Tab. The page heading
the router moves focus to after every navigation (o.FocusOnNavigateSelector = "h1"), so
assistive technology announces the new page, is one; the <main> the skip link lands on is
another; a library's own dialog or popover container is a third. A ring there would say "you are
on a control", which none of them is - and on a library's container the ring's shadow would
also replace the elevation the library draws. :where() keeps the exclusion from adding
specificity, so a field's own focus style (section 7b) still wins over this one. */
:focus-visible:where(:not([tabindex="-1"])) {
outline: 2px solid var(--fl-stroke-focus-outer);
outline-offset: 1px;
box-shadow: 0 0 0 1px var(--fl-stroke-focus-inner);
}

/* ...except on the things the ROUTER focuses. Brouter moves focus to the page heading after every
navigation (o.FocusOnNavigateSelector = "h1") so assistive technology announces the new page,
and it makes the heading focusable with tabindex="-1" to do it - the same trick the skip link
uses on <main>. A ring there would say "you are on a control", which none of these are: the
reader did not focus it, and there is nothing to activate. Interactive elements never carry
tabindex="-1", so nothing that IS a control loses its ring here. */
:is(h1, h2, h3, h4, main, article, section, div)[tabindex="-1"]:focus-visible {
[tabindex="-1"]:focus-visible:where(h1, h2, h3, h4, h5, h6, main, article, section) {
outline: none;
box-shadow: none;
}

/* The first thing in the tab order: a keyboard reader lands on it before the nav and can jump
Expand All @@ -410,14 +411,16 @@ svg {
color: var(--bb-primary-text);
font-size: var(--fl-font-size-200);
font-weight: var(--fl-weight-semibold);
box-shadow: var(--bb-shadow-flyout);
transition: transform var(--bb-motion-duration) var(--bb-motion-enter);
}

/* The elevation is only drawn once the link is on screen: parked above the viewport, its shadow
would still reach a few pixels down into the corner of the header. */
.bb-skip-link:focus-visible {
transform: translateY(0);
color: var(--bb-primary-text);
text-decoration: none;
box-shadow: var(--bb-shadow-flyout);
}

/* ============================================================================================
Expand Down Expand Up @@ -485,11 +488,14 @@ b {
font-weight: var(--fl-weight-semibold);
}

/* No ligatures: Cascadia Code would draw `=>` and `!=` as single glyphs, and code on this site is
read in order to be typed - the reader should see the characters they will type. */
code,
kbd,
pre,
samp {
font-family: var(--bb-mono);
font-variant-ligatures: none;
}

/* Inline code. Quieter than the old brand-tinted pill: in a docs paragraph half the nouns are
Expand Down Expand Up @@ -704,8 +710,10 @@ button.btn-outline:hover {
border-color: var(--bb-primary);
}

/* Large - the landing page's calls to action. */
.bb-btn-lg {
/* Large - the landing page's calls to action. Restated for <button>: `button.btn` above outranks a
lone class, and would otherwise keep a large button at the medium size. */
.bb-btn-lg,
button.bb-btn-lg {
min-height: 2.75rem;
padding: .625rem 1.25rem;
font-size: var(--fl-font-size-300);
Expand Down Expand Up @@ -756,12 +764,19 @@ button.btn-outline:hover {
the rest of the border stays put. Applied to the elements themselves rather than to a class,
because the demo pages render plain inputs - a design system that only reaches markup written
for it is not a design system.

Only the inputs a reader TYPES into are fields. A slider, a color well, a file picker or a
checkbox is not, and dressing one up as a text box changes its size as it gains and loses focus
- which moves everything below it.

Every selector here is wrapped in :where(), so the resting look has no specificity at all and the
states carry only their pseudo-class: a library that draws its own fields (a forms library, a
canvas text editor, a palette's search box) wins with a single class, at rest, on hover and on
focus, and a demo restyles a field the same way. The focus rule still outranks the site-wide
focus ring in section 3, by coming after it at the same weight.
============================================================================================ */

input:not([type="checkbox"]):not([type="radio"]),
textarea,
select,
.form-control {
:where(input:not([type]), input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="tel"], input[type="password"], input[type="number"], input[type="date"], input[type="datetime-local"], input[type="time"], input[type="month"], input[type="week"], textarea, select, .form-control) {
width: auto;
max-width: 100%;
min-height: 2rem;
Expand All @@ -777,30 +792,24 @@ select,
transition: border-color var(--bb-motion-duration-short) var(--bb-motion-easing);
}

textarea {
:where(textarea) {
width: 100%;
padding: .5rem .625rem;
line-height: 1.6;
resize: vertical;
}

input::placeholder,
textarea::placeholder {
:where(input, textarea)::placeholder {
color: var(--bb-text-soft);
}

input:hover:not(:disabled),
textarea:hover:not(:disabled),
select:hover:not(:disabled) {
:where(input:not([type]), input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="tel"], input[type="password"], input[type="number"], input[type="date"], input[type="datetime-local"], input[type="time"], input[type="month"], input[type="week"], textarea, select, .form-control):hover:where(:not(:disabled)) {
border-color: var(--bb-text-soft);
}

/* Focus: the brand underline, and no ring - the moving line IS the indicator here, which is what
Fluent 2 does for a field. Everything else on the site keeps the neutral double ring. */
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
.form-control:focus-visible {
:where(input:not([type]), input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="tel"], input[type="password"], input[type="number"], input[type="date"], input[type="datetime-local"], input[type="time"], input[type="month"], input[type="week"], textarea, select, .form-control):focus-visible {
outline: none;
box-shadow: none;
border-color: var(--bb-border-strong);
Expand All @@ -809,13 +818,11 @@ select:focus-visible,
padding-bottom: calc(.3125rem - 1px);
}

textarea:focus-visible {
:where(textarea):focus-visible {
padding-bottom: calc(.5rem - 1px);
}

input:disabled,
textarea:disabled,
select:disabled {
:where(input:not([type]), input[type="text"], input[type="search"], input[type="email"], input[type="url"], input[type="tel"], input[type="password"], input[type="number"], input[type="date"], input[type="datetime-local"], input[type="time"], input[type="month"], input[type="week"], textarea, select, .form-control):disabled {
color: var(--bb-text-soft);
background: var(--bb-surface-sunken);
cursor: not-allowed;
Expand Down Expand Up @@ -855,10 +862,14 @@ input[type="search"]::-webkit-search-cancel-button {
color: var(--bb-text-muted);
}

/* `anywhere` lets one long identifier in a heading (a type name, say) break rather than push a
phone-width page sideways: it lowers the title text's min-content width, which is what a flex
item is not allowed to shrink below. A title that fits never breaks. */
.bb-card-title {
display: flex;
align-items: center;
gap: .5rem;
overflow-wrap: anywhere;
font-size: var(--fl-font-size-300);
font-weight: var(--fl-weight-semibold);
line-height: 1.5;
Expand Down Expand Up @@ -1874,7 +1885,7 @@ article [id] {
background: LinkText;
}

:focus-visible {
:focus-visible:where(:not([tabindex="-1"])) {
outline: 2px solid CanvasText;
}
}
Expand Down
11 changes: 9 additions & 2 deletions src/Brouter/Bit.Brouter.Demo/Client/wwwroot/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,16 @@

function copy(text) {
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(text);
// The browser can still refuse the permission (a policy, an embedding frame, an automated
// session); a refusal falls back to the legacy path rather than surfacing as an unhandled
// rejection on the page.
return navigator.clipboard.writeText(text).catch(function () { return copyLegacy(text); });
}
// Fallback for non-secure contexts.
return copyLegacy(text);
}

// Fallback for non-secure contexts, and for a clipboard API that refused.
function copyLegacy(text) {
var area = document.createElement('textarea');
area.value = text;
area.style.position = 'fixed';
Expand Down
12 changes: 12 additions & 0 deletions src/Brouter/Bit.Brouter.Demo/Client/wwwroot/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
return darkQuery.matches ? 'dark' : 'light';
}

var current = null;

function apply(theme) {
current = theme;
document.documentElement.setAttribute('bit-theme', theme);
var themeColor = document.querySelector('meta[name="theme-color"]');
if (themeColor) {
Expand All @@ -34,6 +37,15 @@

apply(getStoredTheme() || getSystemTheme());

/* Blazor's enhanced navigation - a link followed before the app is interactive, or a redirect
handed back to the page loader - fetches the next page and merges it into this document,
<html> included. The server's markup carries no bit-theme, so the merge removes it and the
page falls back to the light palette, and this script does not run again to put it back.
Restoring it from an observer happens before the merged page is painted. */
new MutationObserver(function () {
if (document.documentElement.getAttribute('bit-theme') !== current) apply(current);
}).observe(document.documentElement, { attributes: true, attributeFilter: ['bit-theme'] });

darkQuery.addEventListener('change', function () {
if (getStoredTheme() === null) {
apply(getSystemTheme());
Expand Down
Loading
Loading