feat(Progress): gradient bars and a segmented variant - #1181
Merged
Conversation
Add `--cui-progress-bar-bg-image` (default `none`), painted by `.progress-bar` over its colour, so a gradient is a single variable set on the `.progress` or on one bar. The stripes move to their own tokens, `--cui-progress-bar-stripes` and `--cui-progress-bar-stripe-color`, and `.progress-bar-striped` layers the pattern over the image with a per-layer `background-size`. The stripe keyframes shift only the first background layer, so the image below does not scroll with the animation. Add `.progress-segmented`: a three-layer mask (two caps, one body) tiled with `space` along the track that cuts a `.progress` or `.progress-stacked` into whole rounded pills sized by `--cui-progress-segment-width` and `--cui-progress-segment-gap`; the gaps absorb the remainder of the width. The body layer repeats horizontally only — with `space` on both axes a track at least twice the body's height tiled it vertically and squared the pills. The variant zeroes the track radius and shadow, on the nested `.progress` of a stack too, since the pill outline replaces both. Docs: Gradients and Segmented sections, segmented tokens under Customizing, migration entries. sass-true covers the token maps.
Coverage Report for CI Build 34000013115Coverage increased (+0.006%) to 93.431%Details
Uncovered Changes
Coverage RegressionsNo coverage regressions found. Coverage Stats💛 - Coveralls |
The cap circles faded over 1 px, which the body layer's hard edge did not, so the joint showed a soft shoulder that grew with zoom. A hard stop is antialiased by the browser like any gradient edge and lines up with the body.
The section's first sentence already says the mask covers .progress-stacked, and the example set its height on the stack, which the nested .progress does not follow, so the fill covered only the top half.
Add `--cui-progress-segment-border-radius`, half the segment width by default, so a segmented track can draw square or lightly rounded segments instead of pills only. The mask becomes a rounded rectangle per segment: two crossed bars plus a circle in each corner. At the default radius the shape is the same pill as before; at `0px` the corner circles collapse and the bars form a square. The value feeds the mask's `calc()`, so it has to be a length: a unitless `0` is a number there, which invalidates `mask-size` and drops the whole mask. Documented on the page and in the migration entry.
`--cui-progress-segment-border-radius` defaults to `--cui-radius-2` instead of half the segment width, so widening a segment keeps the same corner rather than scaling it into a bigger cap. On the default width the two are equal, so the default look is unchanged. The mask clamps the radius to half the segment width, as `border-radius` does, so a segment narrower than twice the radius stays a pill instead of inverting the corner circles.
Segments are a count now, `--cui-progress-segments` (40 by default), instead of a fixed width: the mask tile is `(100% + gap) / n`, so the pitch is known to the stylesheet, segments scale with the track and the gap stays put. The mask repeats along the track only — `repeat-x no-repeat` is not a valid pair, it fell back to `repeat` on both axes and the body layer squared the corners. `--cui-progress-segment-width` is gone with it. With the pitch known, the bar's width can be a whole number of segments: `.progress-segmented > .progress-bar` reads `--cui-progress-segments-filled` and ends on that segment's far edge. The new Progress plugin sets it: it initializes on every `.progress-segmented`, reads the bar's `aria-valuenow`, `aria-valuemin` and `aria-valuemax`, floors the covered segments so the bar never reads as complete early, and follows the attributes through a MutationObserver. A bar without `aria-valuenow` is left alone, so a page can set the variable by hand or keep an inline `width` with a partial segment. Docs: Segmented rewritten around the count, a Usage section for the plugin, migration entries. Spec covers floor, min/max scaling, clamping, the observer, dispose, data API and the jQuery interface. bundlewatch: the six JS ceilings go up by 1 kB; the plugin left 30 bytes on `coreui.esm.js`, and on the v6 line size work is deferred to the end.
Below roughly 400 px the 40 default segments get narrower than two corner radii and the corner circles spilled into the gaps, drawing bone-shaped ends. Chromium accepts no math function that mixes a length with a percentage in a radial-gradient size, so the radius cannot be clamped there; instead a first mask layer covering just the segment's box is composited with `intersect` over the shape layers, so whatever a corner draws past the segment is cut off at the gap. Tiling stays at one pitch per segment with `repeat` — `space` with a segment-sized tile packed extra tiles whenever the gap was wider than the segment.
Add `--cui-progress-segment-min-width` (.375rem). The plugin watches the track with a ResizeObserver and, when the token's count would push segments below that width, writes the count that fits to `--cui-progress-segments-fit` and recounts the fill against it; the mask and the bar read the fit value with the token as fallback, so the token stays the ceiling and a page can still change it. A 150 px track shows 13 of the default 40, a 520 px one all of them. Zero minimum width disables the cap. Token lengths are read from computed style, so the plugin converts `rem` and `em` itself; other units pass through as pixels. bundlewatch: `coreui.css` ceilings up by .5 kB, 60 bytes were left.
`$progress-segments` is `null` by default and the token is only emitted when it is set. Without it the plugin packs as many segments of `--cui-progress-segment-min-width` as fit and follows the track; with it the count is the ceiling. The stylesheet keeps working without either: the pitch is a custom property computed from the count, so when no count exists it is invalid and `var()` falls back to `min-width + gap` tiles, with whatever is left at the end of the track clipped. Gap and minimum width default to .25rem.
A segment narrower than two radii had its corner circles cross, so the top and bottom edges showed two humps instead of one cap. The plugin now writes `--cui-progress-segment-radius-fit`: the token capped at half the segment width and half the track height, as `border-radius` would do. The mask reads it with a fallback of the token capped at half the minimum width — the only segment width the stylesheet knows on its own, and exactly right when no count is set. Chromium accepts `min()` in a circle radius as long as every operand is a length, which this fallback keeps to.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
Gradient bars.
--cui-progress-bar-bg-image(defaultnone) is painted by.progress-barover its colour, so a gradient is one variable set on the.progressor on a single bar. Stripes get their own tokens —--cui-progress-bar-stripesand--cui-progress-bar-stripe-color— and.progress-bar-stripedlayers the pattern over the image; theprogress-bar-stripeskeyframes shift only the first background layer..progress-segmented. A seven-layer mask (a segment box composited withintersectover two crossed bars and four hard-edged corner circles) cuts a.progressor.progress-stackedinto--cui-progress-segmentsrounded segments (40 by default) separated by--cui-progress-segment-gap, with corners from--cui-progress-segment-border-radius(--cui-radius-2by default,0pxfor square). The tile is(100% + gap) / n, so segments scale with the track and the pitch is known to the stylesheet; a corner wider than the segment is cut off at the gap. Everything under the mask — transition, theme, gradient, stripes — behaves as before.Whole segments.
.progress-segmented > .progress-barreads--cui-progress-segments-filledand ends on that segment's far edge. The new Progress plugin sets it: initialized on every.progress-segmented, it reads the bar'saria-valuenow/aria-valuemin/aria-valuemax, floors the covered segments, and follows the attributes through a MutationObserver. A bar withoutaria-valuenowis left alone, so a page can set the variable by hand or keep an inlinewidthwith a partial segment.Fewer segments on narrow tracks.
--cui-progress-segment-min-width(.375rem) is the width a segment may not drop below. The plugin watches the track with a ResizeObserver and writes the count that fits to--cui-progress-segments-fit, which the mask and the bar read with the token as fallback — 13 of the default 40 on a 150 px track, all 40 at 520 px. The token stays the ceiling.The radius feeds the mask's
calc(), so it has to be a length (0px, not0); documented. Chromium rejects math functions that mix a length with a percentage in aradial-gradientsize, which is why the corner clamp is a composited box rather thanmin().Docs
components/progress: Gradients and Segmented sections, Usage section for the plugin, segment tokens under Customizing. Migration: entries under Progress.Verification
css-lint,css-test(116 specs),js-lint,tsc,progress.spec.js(19 specs incl. ResizeObserver), full pre-push gate incl.docs-build.0pxandradius-5.Not in this PR
React and Vue:
CProgressBarknows its value, so it can write the variables itself; port follows once this lands.