Skip to content

Commit a019886

Browse files
committed
Massive utilities update
- Split several utilities across smaller pages - New underline thickness utility - Updated text-wrap utility to use `text-wrap` property, removes white-space utils - Adds a dozen new width utilities for now—TBD if we keep this, they're not documented yet - Redoes the color utilities—color, background color, border color, link color—to use color-mix without attribute selectors. Faster, simpler, better support for overriding other components. - Redesigns margin, padding, gap utils pages—shoutout Tailwind for having a better visualization here. Cribbed their approach to emphasize spacing utilities. - Fixed up a lot of usages of color utilities, likely more to do - Fixed up a lot of broken links, probably also more to do
1 parent 6fc23f2 commit a019886

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+1638
-1961
lines changed

build/generate-utilities-json.mjs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Generate utilities metadata JSON from Sass
5+
* This script compiles a special Sass file that outputs utility information as CSS comments,
6+
* then extracts and saves it as JSON for documentation use.
7+
*/
8+
9+
import { readFileSync, writeFileSync, unlinkSync } from 'node:fs'
10+
import { execSync } from 'node:child_process'
11+
import { fileURLToPath } from 'node:url'
12+
import path from 'node:path'
13+
14+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
15+
const rootDir = path.join(__dirname, '..')
16+
17+
// Compile the metadata generator SCSS file
18+
console.log('Compiling utilities metadata...')
19+
20+
try {
21+
execSync(
22+
'sass --style expanded --no-source-map build/generate-utilities-metadata.scss:dist/css/utilities-metadata.tmp.css',
23+
{ cwd: rootDir, stdio: 'inherit' }
24+
)
25+
} catch {
26+
console.error('Failed to compile metadata SCSS')
27+
process.exit(1)
28+
}
29+
30+
// Read the compiled CSS
31+
const cssPath = path.join(rootDir, 'dist/css/utilities-metadata.tmp.css')
32+
const cssContent = readFileSync(cssPath, 'utf8')
33+
34+
// Extract JSON from the CSS comment
35+
const startMarker = 'BOOTSTRAP-UTILITIES-METADATA-START'
36+
const endMarker = 'BOOTSTRAP-UTILITIES-METADATA-END'
37+
38+
const startIndex = cssContent.indexOf(startMarker)
39+
const endIndex = cssContent.indexOf(endMarker)
40+
41+
if (startIndex === -1 || endIndex === -1) {
42+
console.error('Could not find metadata markers in compiled CSS')
43+
process.exit(1)
44+
}
45+
46+
// Extract JSON content between markers
47+
const jsonContent = cssContent
48+
.slice(startIndex + startMarker.length, endIndex)
49+
.trim()
50+
51+
// Validate JSON
52+
try {
53+
const parsed = JSON.parse(jsonContent)
54+
console.log(`✓ Extracted metadata for ${Object.keys(parsed.utilities).length} utilities`)
55+
56+
// Write to JSON file
57+
const outputPath = path.join(rootDir, 'dist/css/bootstrap-utilities.metadata.json')
58+
writeFileSync(outputPath, JSON.stringify(parsed, null, 2))
59+
console.log(`✓ Wrote metadata to ${outputPath}`)
60+
61+
// Clean up temporary CSS files (including RTL variants that may have been generated)
62+
try {
63+
unlinkSync(cssPath)
64+
} catch {
65+
// File may not exist
66+
}
67+
68+
// Also clean up any RTL variants that postcss may have created
69+
const rtlFiles = [
70+
'dist/css/utilities-metadata.tmp.rtl.css',
71+
'dist/css/utilities-metadata.tmp.rtl.css.map',
72+
'dist/css/utilities-metadata.tmp.rtl.min.css',
73+
'dist/css/utilities-metadata.tmp.rtl.min.css.map',
74+
'dist/css/utilities-metadata.tmp.min.css',
75+
'dist/css/utilities-metadata.tmp.min.css.map'
76+
]
77+
78+
for (const file of rtlFiles) {
79+
try {
80+
unlinkSync(path.join(rootDir, file))
81+
} catch {
82+
// File may not exist, ignore
83+
}
84+
}
85+
86+
console.log('✓ Cleaned up temporary files')
87+
} catch (error) {
88+
console.error('Failed to parse extracted JSON:', error.message)
89+
console.error('Extracted content:', jsonContent.slice(0, 500))
90+
process.exit(1)
91+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Generate utilities metadata JSON for documentation
2+
// This file is compiled to extract utility information without generating CSS
3+
4+
@use "sass:map";
5+
@use "sass:list";
6+
@use "sass:string";
7+
@use "sass:meta";
8+
@use "../scss/config" as *;
9+
@use "../scss/colors" as *;
10+
@use "../scss/variables" as *;
11+
@use "../scss/functions" as *;
12+
@use "../scss/theme" as *;
13+
@use "../scss/utilities" as *;
14+
15+
// Access the utilities map
16+
$utilities-map: $utilities !default;
17+
18+
// Start JSON output
19+
$json: '{"utilities":{' !default;
20+
21+
$utility-count: 0 !default;
22+
$total-utilities: list.length(map.keys($utilities-map)) !default;
23+
24+
@each $key, $utility in $utilities-map {
25+
$utility-count: $utility-count + 1;
26+
27+
// Skip if utility is null or false (disabled)
28+
@if $utility {
29+
// Extract class prefix
30+
$class: if(map.has-key($utility, "class"), map.get($utility, "class"), $key);
31+
32+
// Extract property
33+
$property: if(map.has-key($utility, "property"), map.get($utility, "property"), null);
34+
35+
// Extract values
36+
$values: if(map.has-key($utility, "values"), map.get($utility, "values"), null);
37+
38+
// Generate class list
39+
$classes: "";
40+
@if $values {
41+
@if meta.type-of($values) == "map" {
42+
$value-keys: map.keys($values);
43+
$first: true;
44+
@each $value-key in $value-keys {
45+
@if not $first {
46+
$classes: $classes + ", ";
47+
}
48+
$class-name: if($value-key == "null" or $value-key == null, $class, "#{$class}-#{$value-key}");
49+
$classes: $classes + '"' + $class-name + '"';
50+
$first: false;
51+
}
52+
} @else if meta.type-of($values) == "list" {
53+
$first: true;
54+
@each $value in $values {
55+
@if not $first {
56+
$classes: $classes + ", ";
57+
}
58+
$class-name: "#{$class}-#{$value}";
59+
$classes: $classes + '"' + $class-name + '"';
60+
$first: false;
61+
}
62+
}
63+
}
64+
65+
// Build JSON entry
66+
$json: $json + '"' + $key + '":{"class":"' + $class + '"';
67+
68+
@if $property {
69+
@if meta.type-of($property) == "string" {
70+
$json: $json + ',"property":"' + $property + '"';
71+
} @else if meta.type-of($property) == "list" {
72+
$property-str: "";
73+
$first: true;
74+
@each $prop in $property {
75+
@if not $first {
76+
$property-str: $property-str + " ";
77+
}
78+
$property-str: $property-str + $prop;
79+
$first: false;
80+
}
81+
$json: $json + ',"property":"' + $property-str + '"';
82+
}
83+
// Skip map properties as they're complex and don't translate to JSON well
84+
}
85+
86+
@if $classes != "" {
87+
$json: $json + ',"classes":[' + $classes + "]";
88+
} @else {
89+
$json: $json + ',"classes":[]';
90+
}
91+
92+
$json: $json + "}";
93+
94+
@if $utility-count < $total-utilities {
95+
$json: $json + ",";
96+
}
97+
}
98+
}
99+
100+
// stylelint-disable-next-line scss/dollar-variable-default
101+
$json: $json + "}}";
102+
103+
// Output as CSS comment so it appears in compiled file
104+
105+
/*! BOOTSTRAP-UTILITIES-METADATA-START
106+
#{$json}
107+
BOOTSTRAP-UTILITIES-METADATA-END */
108+
109+
// Prevent any actual CSS output
110+
.bootstrap-utilities-metadata-generator {
111+
content: "This file should not generate CSS, only metadata comments";
112+
}

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,18 @@
4141
"scripts": {
4242
"start": "npm-run-all --parallel watch docs-serve",
4343
"bundlewatch": "bundlewatch --config .bundlewatch.config.json",
44-
"css": "npm-run-all css-compile css-prefix css-rtl css-minify",
44+
"css": "npm-run-all css-compile css-prefix css-rtl css-minify css-docs",
4545
"css-compile": "sass --style expanded --source-map --embed-sources --no-error-css scss/bootstrap.scss:dist/css/bootstrap.css scss/bootstrap-grid.scss:dist/css/bootstrap-grid.css scss/bootstrap-reboot.scss:dist/css/bootstrap-reboot.css scss/bootstrap-utilities.scss:dist/css/bootstrap-utilities.css",
46-
"css-rtl": "cross-env NODE_ENV=RTL postcss --config build/postcss.config.mjs --dir \"dist/css\" --ext \".rtl.css\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.rtl.css\"",
46+
"css-docs": "node build/generate-utilities-json.mjs",
47+
"css-rtl": "cross-env NODE_ENV=RTL postcss --config build/postcss.config.mjs --dir \"dist/css\" --ext \".rtl.css\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.rtl.css\" \"!dist/css/*.tmp.css\"",
4748
"css-lint": "npm-run-all --aggregate-output --continue-on-error --parallel css-lint-*",
4849
"css-lint-stylelint": "stylelint \"**/*.{css,scss}\" --cache --cache-location .cache/.stylelintcache",
4950
"css-lint-vars": "fusv scss/ site/src/scss/",
5051
"css-minify": "npm-run-all --aggregate-output --parallel css-minify-*",
51-
"css-minify-main": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*rtl*.css\"",
52+
"css-minify-main": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*rtl*.css\" \"!dist/css/*.tmp.css\"",
5253
"css-minify-rtl": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*rtl.css\" \"!dist/css/*.min.css\"",
5354
"css-prefix": "npm-run-all --aggregate-output --parallel css-prefix-*",
54-
"css-prefix-main": "postcss --config build/postcss.config.mjs --replace \"dist/css/*.css\" \"!dist/css/*.rtl*.css\" \"!dist/css/*.min.css\"",
55+
"css-prefix-main": "postcss --config build/postcss.config.mjs --replace \"dist/css/*.css\" \"!dist/css/*.rtl*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.tmp.css\"",
5556
"css-prefix-examples": "postcss --config build/postcss.config.mjs --replace \"site/src/assets/examples/**/*.css\"",
5657
"css-prefix-examples-rtl": "cross-env-shell NODE_ENV=RTL postcss --config build/postcss.config.mjs --dir \"site/src/assets/examples/\" --ext \".rtl.css\" --base \"site/src/assets/examples/\" \"site/src/assets/examples/{blog,carousel,dashboard,cheatsheet}/*.css\" \"!site/src/assets/examples/{blog,carousel,dashboard,cheatsheet}/*.rtl.css\"",
5758
"css-test": "jasmine --config=scss/tests/jasmine.js",

scss/_card.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ $card-group-margin: $grid-gutter-width * .5 !default;
116116

117117
.card-link {
118118
&:hover {
119-
text-decoration: if($link-hover-decoration == underline, none, null);
119+
text-decoration: none;
120120
}
121121

122122
+ .card-link {

scss/_config.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ $spacers: (
4242
) !default;
4343
// scss-docs-end spacer-variables-maps
4444

45+
$sizes: (
46+
1: $spacer,
47+
2: calc($spacer * 2),
48+
3: calc($spacer * 3),
49+
4: calc($spacer * 4),
50+
5: calc($spacer * 5),
51+
6: calc($spacer * 6),
52+
7: calc($spacer * 7),
53+
8: calc($spacer * 8),
54+
9: calc($spacer * 9),
55+
10: calc($spacer * 10),
56+
11: calc($spacer * 11),
57+
12: calc($spacer * 12),
58+
) !default;
59+
4560
// Grid breakpoints
4661
//
4762
// Define the minimum dimensions at which your layout will change,

scss/_dropdown.scss

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ $dropdown-dark-header-color: var(--#{$prefix}gray-500) !default;
241241
font-weight: $font-weight-normal;
242242
color: var(--#{$prefix}dropdown-link-color);
243243
text-align: inherit; // For `<button>`s
244-
text-decoration: if($link-decoration == none, null, none);
244+
text-decoration: none;
245245
white-space: nowrap; // prevent links from randomly breaking onto new lines
246246
background-color: transparent; // For `<button>`s
247247
border: 0; // For `<button>`s
@@ -250,14 +250,12 @@ $dropdown-dark-header-color: var(--#{$prefix}gray-500) !default;
250250
&:hover,
251251
&:focus {
252252
color: var(--#{$prefix}dropdown-link-hover-color);
253-
text-decoration: if($link-hover-decoration == underline, none, null);
254253
@include gradient-bg(var(--#{$prefix}dropdown-link-hover-bg));
255254
}
256255

257256
&.active,
258257
&:active {
259258
color: var(--#{$prefix}dropdown-link-active-color);
260-
text-decoration: none;
261259
@include gradient-bg(var(--#{$prefix}dropdown-link-active-bg));
262260
}
263261

scss/_functions.scss

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,23 @@
105105
@return $result;
106106
}
107107

108+
// Extract a specific nested property from all items in a map
109+
// Useful for extracting a single property from nested map structures
110+
// Example: map-get-nested($new-font-sizes, "font-size")
111+
// Returns: ("xs": clamp(...), "sm": clamp(...), ...)
112+
@function map-get-nested($map, $nested-key) {
113+
$result: ();
114+
@each $key, $value in $map {
115+
@if meta.type-of($value) == "map" {
116+
$nested-value: map.get($value, $nested-key);
117+
@if $nested-value != null {
118+
$result: map.merge($result, ($key: $nested-value));
119+
}
120+
}
121+
}
122+
@return $result;
123+
}
124+
108125
// Merge multiple maps
109126
@function map-merge-multiple($maps...) {
110127
$merged-maps: ();

scss/_nav.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,14 @@ $nav-underline-link-active-color: var(--#{$prefix}emphasis-color) !default;
6363
@include font-size(var(--#{$prefix}nav-link-font-size));
6464
font-weight: var(--#{$prefix}nav-link-font-weight);
6565
color: var(--#{$prefix}nav-link-color);
66-
text-decoration: if($link-decoration == none, null, none);
66+
text-decoration: none;
6767
background: none;
6868
border: 0;
6969
@include transition($nav-link-transition);
7070

7171
&:hover,
7272
&:focus {
7373
color: var(--#{$prefix}nav-link-hover-color);
74-
text-decoration: if($link-hover-decoration == underline, none, null);
7574
}
7675

7776
&:focus-visible {

scss/_navbar.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,12 @@ $navbar-dark-brand-hover-color: $navbar-dark-active-color !default;
123123
margin-right: var(--#{$prefix}navbar-brand-margin-end);
124124
@include font-size(var(--#{$prefix}navbar-brand-font-size));
125125
color: var(--#{$prefix}navbar-brand-color);
126-
text-decoration: if($link-decoration == none, null, none);
126+
text-decoration: none;
127127
white-space: nowrap;
128128

129129
&:hover,
130130
&:focus {
131131
color: var(--#{$prefix}navbar-brand-hover-color);
132-
text-decoration: if($link-hover-decoration == underline, none, null);
133132
}
134133
}
135134

scss/_pagination.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,14 @@ $pagination-border-radius-lg: var(--#{$prefix}border-radius-lg) !default;
9191
padding: var(--#{$prefix}pagination-padding-y) var(--#{$prefix}pagination-padding-x);
9292
@include font-size(var(--#{$prefix}pagination-font-size));
9393
color: var(--#{$prefix}pagination-color);
94-
text-decoration: if($link-decoration == none, null, none);
94+
text-decoration: none;
9595
background-color: var(--#{$prefix}pagination-bg);
9696
border: var(--#{$prefix}pagination-border-width) solid var(--#{$prefix}pagination-border-color);
9797
@include transition($pagination-transition);
9898

9999
&:hover {
100100
z-index: 2;
101101
color: var(--#{$prefix}pagination-hover-color);
102-
text-decoration: if($link-hover-decoration == underline, none, null);
103102
background-color: var(--#{$prefix}pagination-hover-bg);
104103
border-color: var(--#{$prefix}pagination-hover-border-color);
105104
}

0 commit comments

Comments
 (0)