Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cascade Layers #2322

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
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: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ To add a language, you will need its BCP-47 tag and a label. See [“Adding a ne

- Components that require client-side JavaScript or CSS should use JavaScript/CSS features that are well-supported by browsers.

You can find a list of supported browsers and their versions using this [browserslist query](https://browsersl.ist/#q=%3E+0.5%25%2C+not+dead%2C+Chrome+%3E%3D+88%2C+Edge+%3E%3D+88%2C+Firefox+%3E%3D+98%2C+Safari+%3E%3D+15.4%2C+iOS+%3E%3D+15.4%2C+not+op_mini+all). To check whether or not a feature is supported, you can visit the [Can I use](https://caniuse.com) website and search for the feature.
You can find a list of supported browsers and their versions using this [browserslist query](https://browsersl.ist/#q=%3E+0.5%25%2C+not+dead%2C+Chrome+%3E%3D+99%2C+Edge+%3E%3D+99%2C+Firefox+%3E%3D+98%2C+Safari+%3E%3D+15.4%2C+iOS+%3E%3D+15.4%2C+not+op_mini+all). To check whether or not a feature is supported, you can visit the [Can I use](https://caniuse.com) website and search for the feature.

[discord]: https://astro.build/chat
[issues]: https://github.com/withastro/starlight/issues
Expand Down
2 changes: 2 additions & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const ogUrl = new URL('og.jpg?v=1', site).href;
const ogImageAlt = 'Make your docs shine with Starlight';

export default defineConfig({
// TODO(HiDeoo) Remove this, only used to avoid screenshoting the dev toolbar.
devToolbar: { enabled: false },
site,
trailingSlash: 'always',
integrations: [
Expand Down
7 changes: 7 additions & 0 deletions docs/src/content/docs/guides/css-and-tailwind.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ Customize the styles applied to your Starlight site by providing additional CSS

You can see all the CSS custom properties used by Starlight that you can set to customize your site in the [`props.css` file on GitHub](https://github.com/withastro/starlight/blob/main/packages/starlight/style/props.css).

Starlight uses [cascade layers](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers) internally so that any custom unlayered CSS will override the default styles.
If you are using cascade layers in your custom CSS, you can use the [`@layer`](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) CSS at-rule to define the order of precedence for different layers including the ones used by Starlight:

```css
@layer my-reset, starlight, my-layer;
```

## Tailwind CSS

Tailwind CSS support in Astro projects is provided by the [Astro Tailwind integration](https://docs.astro.build/en/guides/integrations-guide/tailwind/).
Expand Down
1 change: 1 addition & 0 deletions packages/local-prod-visual-diff/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
screenshots/
9 changes: 9 additions & 0 deletions packages/local-prod-visual-diff/diff.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import fs from 'node:fs/promises';
import path from 'node:path';

// Delete the dev and diff screenshots directories before running the tests.
await fs.rm(path.join('screenshots', 'dev'), { force: true, recursive: true });
await fs.rm(path.join('screenshots', 'diff'), { force: true, recursive: true });

// Ensure the diff screenshots directory exists.
await fs.mkdir(path.join('screenshots', 'diff'), { recursive: true });
154 changes: 154 additions & 0 deletions packages/local-prod-visual-diff/diff.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { expect, test, type Page } from '@playwright/test';
import pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs';

const prodUrl = new URL('https://starlight.astro.build');

// A list of all the routes to visually compare between the production and development environments.
// This could be based on the sitemap but it was not worth the effort for this PR.
const routePaths = [
'/',
'/getting-started/',
'/manual-setup/',
'/environmental-impact/',
'/guides/pages/',
'/guides/authoring-content/',
'/guides/components/',
'/guides/css-and-tailwind/',
'/guides/customization/',
'/guides/i18n/',
'/guides/overriding-components/',
'/guides/sidebar/',
'/guides/site-search/',
'/reference/configuration/',
'/resources/plugins/',
'/resources/community-content/',
'/resources/showcase/',
];

// The maximum number of mismatched pixels between the production and development screenshots.
const maxDiffPixels = 10;

for (const routePath of routePaths) {
test.only(`CSS Layers: ${routePath}`, async ({ page }) => {
const prodScreenshotPath = getScreenshotPath(routePath, 'prod');

// Take a screenshot of the production route if it doesn't exist.
if (!(await exists(prodScreenshotPath))) {
await page.goto(new URL(routePath, prodUrl).toString());
await takeScreenshot(page, prodScreenshotPath);
}

const devScreenshotPath = getScreenshotPath(routePath, 'dev');

// Take a screenshot of the development route.
await page.goto(routePath);
await takeScreenshot(page, devScreenshotPath);

// Compare the screenshots between the production and development routes.
const { diffPixels, diffImage } = await compareScreenshots(
prodScreenshotPath,
devScreenshotPath
);

// Save the diff image if the number of mismatched pixels is greater than the maximum allowed.
if (diffPixels >= maxDiffPixels) {
await fs.writeFile(getScreenshotPath(routePath, 'diff'), PNG.sync.write(diffImage));
}

// Assert the number of mismatched pixels is less than the maximum allowed.
expect(diffPixels).toBeLessThan(maxDiffPixels);
});
}

async function compareScreenshots(screenshotPath1: string, screenshotPath2: string) {
let screenshot1: PNG = await getScreenshot(screenshotPath1);
let screenshot2: PNG = await getScreenshot(screenshotPath2);

let diffSize: ScreenshotSize;

// If the screenshots have different dimensions, resize them to the largest dimensions so we can
// see where the differences start to appear.
if (screenshot1.width !== screenshot2.width || screenshot1.height !== screenshot2.height) {
diffSize = {
width: Math.max(screenshot1.width, screenshot2.width),
height: Math.max(screenshot1.height, screenshot2.height),
};

screenshot1 = resizeScreenshot(screenshot1, diffSize);
screenshot2 = resizeScreenshot(screenshot2, diffSize);
} else {
diffSize = { width: screenshot1.width, height: screenshot1.height };
}

const diffImage = new PNG(diffSize);

const diffPixels = pixelmatch(
screenshot1.data,
screenshot2.data,
diffImage.data,
diffSize.width,
diffSize.height,
{ threshold: 0.2 }
);

return { diffPixels, diffImage };
}

function resizeScreenshot(screenshot: PNG, size: ScreenshotSize) {
const resized = new PNG(size);
PNG.bitblt(screenshot, resized, 0, 0, screenshot.width, screenshot.height);
return resized;
}

async function getScreenshot(screenshotPath: string) {
const data = await fs.readFile(screenshotPath);
return PNG.sync.read(data);
}

async function takeScreenshot(page: Page, screenshotPath: string) {
// Ensure all images are loaded before taking the screenshot.
for (const lazyImage of await page.locator('img[loading="lazy"]:visible').all()) {
await lazyImage.scrollIntoViewIfNeeded();
}

const overviewLink = page.getByRole('link', { name: 'Overview', exact: true });

// Scroll to the top of the page to ensure the screenshot is consistent.
if (await overviewLink.isVisible()) {
// Use the Overview link when possible to avoid mismatched pixels due to ToC highlighting.
overviewLink.click();
} else {
await page.evaluate(() => window.scrollTo(0, 0));
}
await page.waitForTimeout(500);

await page.screenshot({ path: screenshotPath, fullPage: true });
}

function getScreenshotPath(routePath: string, type: ScreenshotType) {
return path.join(
'screenshots',
type,
routePath.replace(/\//g, '-').replace(/^-/, '').replace(/-$/, '').replace(/^$/, 'index') +
'.png'
);
}

async function exists(filePath: string) {
try {
await fs.access(filePath, fs.constants.F_OK);
return true;
} catch {
return false;
}
}

type ScreenshotType = 'dev' | 'prod' | 'diff';

interface ScreenshotSize {
width: number;
height: number;
}
18 changes: 18 additions & 0 deletions packages/local-prod-visual-diff/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "starlight-local-prod-visual-diff",
"version": "0.1.0",
"description": "Visual diffing between local and production builds of Starlight",
"private": true,
"scripts": {
"test": "playwright install --with-deps chromium && playwright test"
},
"license": "MIT",
"dependencies": {
"@playwright/test": "^1.45.0",
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.5",
"pixelmatch": "^6.0.0",
"pngjs": "^7.0.0"
},
"type": "module"
}
34 changes: 34 additions & 0 deletions packages/local-prod-visual-diff/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { defineConfig, devices } from '@playwright/test';

const baseURL = 'http://localhost:4321';

export default defineConfig({
forbidOnly: !!process.env['CI'],
projects: [
{
name: 'setup diff',
testMatch: /diff\.setup\.ts/,
},
{
name: 'Chrome Stable',
use: {
...devices['Desktop Chrome'],
headless: true,
},
dependencies: ['setup diff'],
},
],
testMatch: '*.test.ts',
use: {
baseURL,
},
webServer: [
{
command: 'pnpm run dev',
// command: 'pnpm run build && pnpm run preview',
cwd: '../../docs',
reuseExistingServer: !process.env['CI'],
url: baseURL,
},
],
});
26 changes: 14 additions & 12 deletions packages/starlight/components/Banner.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ const { banner } = Astro.props.entry.data;
{banner && <div class="sl-banner" set:html={banner.content} />}

<style>
.sl-banner {
--__sl-banner-text: var(--sl-color-banner-text, var(--sl-color-text-invert));
padding: var(--sl-nav-pad-y) var(--sl-nav-pad-x);
background-color: var(--sl-color-banner-bg, var(--sl-color-bg-accent));
color: var(--__sl-banner-text);
line-height: var(--sl-line-height-headings);
text-align: center;
text-wrap: balance;
box-shadow: var(--sl-shadow-sm);
}
.sl-banner :global(a) {
color: var(--__sl-banner-text);
@layer starlight.components {
.sl-banner {
--__sl-banner-text: var(--sl-color-banner-text, var(--sl-color-text-invert));
padding: var(--sl-nav-pad-y) var(--sl-nav-pad-x);
background-color: var(--sl-color-banner-bg, var(--sl-color-bg-accent));
color: var(--__sl-banner-text);
line-height: var(--sl-line-height-headings);
text-align: center;
text-wrap: balance;
box-shadow: var(--sl-shadow-sm);
}
.sl-banner :global(a) {
color: var(--__sl-banner-text);
}
}
</style>
24 changes: 13 additions & 11 deletions packages/starlight/components/ContentNotice.astro
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,18 @@ const { icon, label } = Astro.props;
</p>

<style>
p {
border: 1px solid var(--sl-color-orange);
padding: 0.75em 1em;
background-color: var(--sl-color-orange-low);
color: var(--sl-color-orange-high);
width: max-content;
max-width: 100%;
align-items: center;
gap: 0.75em;
font-size: var(--sl-text-body-sm);
line-height: var(--sl-line-height-headings);
@layer starlight.components {
p {
border: 1px solid var(--sl-color-orange);
padding: 0.75em 1em;
background-color: var(--sl-color-orange-low);
color: var(--sl-color-orange-high);
width: max-content;
max-width: 100%;
align-items: center;
gap: 0.75em;
font-size: var(--sl-text-body-sm);
line-height: var(--sl-line-height-headings);
}
}
</style>
32 changes: 17 additions & 15 deletions packages/starlight/components/ContentPanel.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,25 @@ import type { Props } from '../props';
</div>

<style>
.content-panel {
padding: 1.5rem var(--sl-content-pad-x);
}
.content-panel + .content-panel {
border-top: 1px solid var(--sl-color-hairline);
}
.sl-container {
max-width: var(--sl-content-width);
}
@layer starlight.components {
.content-panel {
padding: 1.5rem var(--sl-content-pad-x);
}
.content-panel + .content-panel {
border-top: 1px solid var(--sl-color-hairline);
}
.sl-container {
max-width: var(--sl-content-width);
}

.sl-container > :global(* + *) {
margin-top: 1.5rem;
}
.sl-container > :global(* + *) {
margin-top: 1.5rem;
}

@media (min-width: 72rem) {
.sl-container {
margin-inline: var(--sl-content-margin-inline, auto);
@media (min-width: 72rem) {
.sl-container {
margin-inline: var(--sl-content-margin-inline, auto);
}
}
}
</style>
18 changes: 10 additions & 8 deletions packages/starlight/components/EditLink.astro
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ const { editUrl, labels } = Astro.props;
}

<style>
a {
gap: 0.5rem;
align-items: center;
text-decoration: none;
color: var(--sl-color-gray-3);
}
a:hover {
color: var(--sl-color-white);
@layer starlight.components {
a {
gap: 0.5rem;
align-items: center;
text-decoration: none;
color: var(--sl-color-gray-3);
}
a:hover {
color: var(--sl-color-white);
}
}
</style>
Loading
Loading