Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* stylelint-disable stylelint-plugin-mozilla/use-design-tokens */
.stocks {
@include widget-base-style;
&.medium-widget {
grid-row: span 2;
padding-block-start: var(--space-large);
}
&.large-widget {
grid-row: span 4;
padding-block-start: var(--space-large);
}
}
.stocks-title-wrapper {
align-items: center;
display: flex;
justify-content: space-between;
.stocks-badge-title-wrapper {
align-items: center;
display: flex;
gap: var(--space-small);
min-inline-size: 0;
}
}
// The plain "Stocks" title shown at medium/small. Resets the h2 defaults to match the
// small widget-title look.
.stocks-heading {
margin: 0;
font-size: var(--font-size-small);
font-weight: var(--font-weight);
color: var(--text-color-deemphasized);
}
.stocks-small-symbol {
font-size: var(--font-size-small);
font-weight: var(--font-weight);
color: var(--text-color-deemphasized);
}
// Visually hidden but kept in the accessibility tree (same technique as .stock-ticker-sr).
.stocks .sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
// moz-badge renders its label in shadow DOM, so it can only be styled here
// through its token custom properties (padding/background) plus inherited
// properties (text-transform). The green "filled" look normally comes from
// type="new", which errors on the unregistered moz-badge-new2 string, so
// paint the pill on the host and recolor via the same tokens the default
// badge reads. Kept at the top level (not nested under .stocks-title-wrapper)
// so the forced-colors override below matches its specificity and wins.
.stocks-new-badge {
flex: none;
background-color: var(--badge-background-color-filled);
border-radius: var(--badge-border-radius);
text-transform: uppercase;
--badge-padding-block: 1px;
--badge-padding-inline: var(--space-small);
--badge-background-color-filled: var(--color-green-40);
--badge-text-color: var(--badge-text-color-filled);
--badge-border-color: transparent;
}
// Keep the badge readable in High Contrast Mode by using the system accent.
@media (forced-colors: active) {
.stocks-new-badge {
--badge-background-color-filled: AccentColor;
}
}
// Small font like the title; let the button shrink so a long label can truncate.
.stocks-list-button {
--button-font-size-small: var(--font-size-small);
min-inline-size: 0;
max-inline-size: 100%;
}
// Deemphasized text only outside forced colors and Increase Contrast, so those
// modes keep moz-button's own (higher-contrast) system colour.
@media (forced-colors: none) and (prefers-contrast: no-preference) {
.stocks-list-button {
--button-text-color-ghost: var(--text-color-deemphasized);
}
}
// The label is in moz-button's shadow DOM, so reach it via ::part to truncate
// a long label and keep the context-menu button in the row.
.stocks-list-button::part(button) {
overflow: hidden;
}
.stocks-list-button::part(moz-button-label) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.stocks-list-button::part(button),
.stocks-context-menu-button::part(button) {
background-color: transparent;
}
.stocks-list-button::part(button):hover,
.stocks-context-menu-button::part(button):hover {
background-color: var(--button-background-color-hover);
}
.stocks-list-button::part(button):active,
.stocks-context-menu-button::part(button):active {
background-color: var(--button-background-color-active);
}
.stocks-context-menu-wrapper {
opacity: 0;
pointer-events: none;
}
.stocks:hover,
.stocks:focus-within {
.stocks-context-menu-wrapper {
opacity: 1;
pointer-events: auto;
}
}
.stocks-body {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.stocks-error {
flex: 1;
display: flex;
align-items: center;
gap: var(--space-small);
color: var(--text-color-deemphasized);
.icon-info-warning {
flex-shrink: 0;
inline-size: var(--icon-size);
block-size: var(--icon-size);
}
.stocks-error-text {
margin: 0;
font-size: var(--font-size-small);
}
}
.large-widget .stocks-error {
flex-direction: column;
justify-content: center;
text-align: center;
}
.stocks-grid {
display: flex;
flex-wrap: wrap;
gap: var(--space-small) var(--space-medium);
margin: var(--space-medium) 0 0;
padding: 0;
list-style: none;
}
.stock-ticker {
display: flex;
align-items: center;
gap: var(--space-small);
flex: 1;
}
.stock-ticker-sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.stock-indicator {
flex-shrink: 0;
inline-size: var(--size-item-medium);
block-size: var(--size-item-medium);
border-radius: var(--border-radius-small);
border: 0.75px solid var(--border-color);
background-color: var(--background-color-canvas);
background-repeat: no-repeat;
background-position: center;
-moz-context-properties: fill;
fill: var(--icon-color);
}
.stock-indicator--up {
fill: var(--icon-color-success);
background-color: var(--background-color-success);
background-image: url('chrome://global/skin/icons/shaft-arrow-up.svg');
}
.stock-indicator--down {
fill: var(--icon-color-critical);
background-color: var(--background-color-critical);
background-image: url('chrome://global/skin/icons/shaft-arrow-down.svg');
}
.stock-indicator--flat {
// The arrow (shaft-arrow-right or -left, chosen for text direction) is set on
// the element in StockTicker.jsx.
background-color: var(--background-color-canvas);
}
.stock-ticker-label {
display: flex;
flex-direction: column;
min-inline-size: 0;
}
.stock-ticker-line {
display: flex;
align-items: center;
gap: var(--space-xsmall);
}
.stock-ticker-symbol {
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-small);
color: var(--text-color);
}
.stock-ticker-dot {
inline-size: 2px;
block-size: 2px;
border-radius: var(--border-radius-circle);
background-color: var(--text-color-deemphasized);
}
.stock-ticker-price,
.stock-ticker-exchange {
font-size: var(--newtab-font-size-xsmall);
color: var(--text-color-deemphasized);
}
.stock-ticker-change {
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-small);
}
// No shared tokens match these sizes.
.stock-ticker--small {
--stocks-small-indicator-size: 2.625rem;
--stocks-small-indicator-icon-size: 1.375rem;
--stocks-small-change-font-size: 1.3125rem;
// Prevent the row from growing so the list can align it to the bottom.
flex: none;
gap: var(--space-small);
}
.stock-ticker--small .stock-indicator {
inline-size: var(--stocks-small-indicator-size);
block-size: var(--stocks-small-indicator-size);
border-width: var(--border-width);
background-size: var(--stocks-small-indicator-icon-size);
}
.stock-ticker--small .stock-ticker-change {
font-weight: var(--font-weight);
font-size: var(--stocks-small-change-font-size);
}
.stock-ticker--small .stock-ticker-price {
font-size: var(--font-size-root);
}
.stocks-grid--loading .stock-indicator,
.stocks-grid--loading .stock-ticker-symbol,
.stocks-grid--loading .stock-ticker-price,
.stocks-grid--loading .stock-ticker-change,
.stocks-list--loading .stock-indicator,
.stocks-list--loading .stock-ticker-name,
.stocks-list--loading .stock-ticker-symbol,
.stocks-list--loading .stock-ticker-price,
.stocks-list--loading .stock-ticker-change {
opacity: 0.4;
}
.stocks-list {
display: flex;
flex-direction: column;
gap: var(--space-large);
margin: var(--space-small) 0 0;
padding: 0;
list-style: none;
}
// The watchlist can hold up to 10 rows, more than fit the large card, so it
// scrolls inside the widget while the title and dropdown stay fixed above it.
.stocks-list--watchlist {
flex: 1;
min-height: 0;
overflow-y: auto;
scrollbar-width: thin;
}
// Rows keep their natural height and stack from the top, so a short list is not
// stretched to fill the scroll area.
.stocks-list--watchlist .stock-ticker,
.stocks-search-results .stock-ticker {
flex: none;
}
.stocks-list--small {
flex: 1;
justify-content: flex-end;
padding-block-end: var(--space-small);
}
.stock-ticker--large {
// Large rows span the full card width instead of flexing like grid cells.
inline-size: auto;
gap: var(--space-large);
padding: calc(var(--space-xsmall) + var(--space-xxsmall));
border-radius: var(--border-radius-small);
transition: background-color 100ms;
}
// Skip the custom row highlight in forced colors, where the background and text aren't a
// guaranteed contrast pair; the focus outline still marks the active row there.
@media (forced-colors: none) {
.stock-ticker--large:hover,
.stock-ticker--large:focus-within {
background-color: var(--button-background-color-hover);
}
}
.stock-ticker--large .stock-indicator {
inline-size: var(--size-item-large);
block-size: var(--size-item-large);
border-width: var(--border-width);
background-size: var(--icon-size);
}
.stock-ticker--large .stock-ticker-label {
flex: 1;
min-inline-size: 0;
}
.stock-ticker--large .stock-ticker-line {
min-inline-size: 0;
}
.stock-ticker-name {
// No flex-grow so the ticker sits next to the name; min-inline-size: 0 (here
// and on the label) lets the name ellipsize only when it is too long.
min-inline-size: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: var(--font-weight-semibold);
}
.stock-ticker--large .stock-ticker-symbol,
.stock-ticker--large .stock-ticker-dot {
flex-shrink: 0;
}
.stock-ticker--large .stock-ticker-symbol {
font-weight: var(--font-weight);
color: var(--text-color-deemphasized);
}
.stock-ticker--large .stock-ticker-price,
.stock-ticker--large .stock-ticker-exchange {
font-size: var(--font-size-small);
}
.stock-ticker--large .stock-ticker-action {
flex-shrink: 0;
opacity: 0;
transition: opacity 100ms;
}
.stock-ticker--large .stock-ticker-action::part(button) {
inline-size: var(--size-item-medium);
block-size: var(--size-item-medium);
border: var(--border-width) solid var(--button-border-color);
border-radius: var(--border-radius-circle);
background-color: var(--button-background-color);
--button-icon-fill: var(--button-text-color);
}
// A disabled add button (watchlist full) needs a clear disabled look. newtab
// pins --button-background-color-disabled to the enabled colour and only dims to
// 0.7, which is imperceptible, so fade it further and show a not-allowed cursor.
// In High Contrast Mode opacity is ignored, so also switch the border and icon to
// the disabled tokens, which resolve to the system disabled colour there.
.stock-ticker--large .stock-ticker-action[disabled]::part(button) {
opacity: 0.4;
cursor: not-allowed;
border-color: var(--button-border-color-disabled);
--button-icon-fill: var(--button-text-color-disabled);
}
.stock-ticker--large:hover .stock-ticker-action,
.stock-ticker--large:focus-within .stock-ticker-action {
opacity: 1;
}
@media (hover: none) {
.stock-ticker--large .stock-ticker-action {
opacity: 1;
}
}
// Decorative (aria-hidden); its accessible label is the visually hidden
// .stock-ticker-in-watchlist text.
.stock-ticker--large .stock-ticker-added {
flex-shrink: 0;
inline-size: var(--size-item-medium);
block-size: var(--size-item-medium);
border-radius: var(--border-radius-circle);
background-color: var(--button-background-color-primary);
color: var(--button-text-color-primary);
background-image: url('chrome://global/skin/icons/check.svg');
background-position: center;
background-repeat: no-repeat;
background-size: var(--icon-size);
-moz-context-properties: fill;
fill: var(--button-text-color-primary);
opacity: 0;
transition: opacity 100ms;
}
.stock-ticker--large:hover .stock-ticker-added,
.stock-ticker--large:focus-within .stock-ticker-added,
.stock-ticker--large .stock-ticker-added--confirming {
opacity: 1;
}
// Keep the checkmark perceivable in High Contrast Mode, where the purple fill is dropped.
@media (forced-colors: active) {
.stock-ticker--large .stock-ticker-added {
border: var(--border-width) solid CanvasText;
}
}
// Search results show an enabled add button or the saved check without hover; a
// disabled add button (watchlist full) hover-reveals like the Markets rows.
.stock-ticker--large.stock-ticker--result .stock-ticker-action:not([disabled]),
.stock-ticker--large.stock-ticker--result .stock-ticker-added {
opacity: 1;
}
// The dot is a filled shape; forced-colors resets background-color, so pin it to
// a system colour to keep the separator visible in High Contrast Mode.
@media (forced-colors: active) {
.stock-ticker-dot {
background-color: CanvasText;
}
}
.stocks-search {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
gap: var(--space-small);
}
.stocks-search-header {
display: flex;
align-items: center;
gap: var(--space-small);
}
.stocks-search-form {
flex: 1;
min-inline-size: 0;
}
.stocks-search-input {
inline-size: 100%;
}
// Up to ten matches, more than fit the card, so the list scrolls. Unlike the
// watchlist it has no flex: 1, so a short list keeps its natural height and
// the status message sits directly under it.
.stocks-search-results {
display: flex;
flex-direction: column;
gap: var(--space-large);
min-height: 0;
overflow-y: auto;
scrollbar-width: thin;
margin: 0;
padding: 0;
list-style: none;
}
// flex: none keeps the message visible under a tall list; with overflow hidden
// the flex layout would otherwise shrink it to nothing. Two lines fit the
// watchlist-full message, and an unbroken long query wraps instead of overflowing.
.stocks-search-message {
flex: none;
margin: 0;
font-size: var(--font-size-small);
color: var(--text-color-deemphasized);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
overflow-wrap: anywhere;
}