Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/avatar-css-layer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': patch
---

Avatar: Add CSS layer support for component styles
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
"@testing-library/react": "^16.3.0",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/css-tree": "^2.3.11",
"@types/lodash.groupby": "4.6.9",
"@types/lodash.isempty": "4.4.9",
"@types/lodash.isobject": "3.0.9",
Expand All @@ -150,6 +151,7 @@
"concurrently": "9.1.2",
"copyfiles": "2.4.1",
"cross-env": "7.0.3",
"css-tree": "^2.3.1",
"fast-glob": "3.3.2",
"filesize": "10.1.6",
"front-matter": "4.0.2",
Expand Down
56 changes: 29 additions & 27 deletions packages/react/src/Avatar/Avatar.module.css
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
:where(.Avatar) {
display: inline-block;
width: var(--avatarSize-regular);
height: var(--avatarSize-regular);
overflow: hidden; /* Ensure page layout in Firefox should images fail to load */
/* stylelint-disable-next-line primer/typography */
line-height: 1;
vertical-align: middle;
border-radius: 50%;
/* stylelint-disable-next-line primer/box-shadow */
box-shadow: 0 0 0 1px var(--avatar-borderColor);
@layer primer.components.Avatar {
:where(.Avatar) {
display: inline-block;
width: var(--avatarSize-regular);
height: var(--avatarSize-regular);
overflow: hidden; /* Ensure page layout in Firefox should images fail to load */
/* stylelint-disable-next-line primer/typography */
line-height: 1;
vertical-align: middle;
border-radius: 50%;
/* stylelint-disable-next-line primer/box-shadow */
box-shadow: 0 0 0 1px var(--avatar-borderColor);

&:where([data-square]) {
/* stylelint-disable-next-line primer/borders */
border-radius: clamp(4px, calc(var(--avatarSize-regular) - 24px), var(--borderRadius-medium));
}

&:where([data-responsive]) {
@media screen and (--viewportRange-narrow) {
width: var(--avatarSize-narrow);
height: var(--avatarSize-narrow);
&:where([data-square]) {
/* stylelint-disable-next-line primer/borders */
border-radius: clamp(4px, calc(var(--avatarSize-regular) - 24px), var(--borderRadius-medium));
}

@media screen and (--viewportRange-regular) {
width: var(--avatarSize-regular);
height: var(--avatarSize-regular);
}
&:where([data-responsive]) {
@media screen and (--viewportRange-narrow) {
width: var(--avatarSize-narrow);
height: var(--avatarSize-narrow);
}

@media screen and (--viewportRange-regular) {
width: var(--avatarSize-regular);
height: var(--avatarSize-regular);
}

@media screen and (--viewportRange-wide) {
width: var(--avatarSize-wide);
height: var(--avatarSize-wide);
@media screen and (--viewportRange-wide) {
width: var(--avatarSize-wide);
height: var(--avatarSize-wide);
}
}
}
}
40 changes: 40 additions & 0 deletions packages/react/src/__tests__/css-layers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import fs from 'node:fs'
import path from 'node:path'
import {generate, parse} from 'css-tree'
import type {StyleSheet} from 'css-tree'
import {describe, expect, test} from 'vitest'

const allowlist = new Set([path.resolve(import.meta.dirname, '../Avatar/Avatar.module.css')])
const files = Array.from(allowlist).map(file => {
return [path.basename(file), file]
})

const CSS_LAYER_REGEX = /^primer\.components\.[A-Z][A-Za-z0-9]+$/

describe('CSS Layers', () => {
describe.each(files)('%s', (_name, filename) => {
const contents = fs.readFileSync(filename, 'utf8')
const ast = parse(contents, {
filename,
}) as StyleSheet

test('uses CSS Layer', () => {
const first = ast.children.first

expect(first?.type).toBe('Atrule')
if (!first || first.type !== 'Atrule') throw new Error('Expected stylesheet to start with an @layer at-rule')

expect(first.name).toBe('layer')
})

test('CSS Layer matches naming conventions', () => {
const first = ast.children.first

expect(first?.type).toBe('Atrule')
if (!first || first.type !== 'Atrule') throw new Error('Expected stylesheet to start with an @layer at-rule')

const layerName = first.prelude ? generate(first.prelude).trim() : ''
expect(layerName).toMatch(CSS_LAYER_REGEX)
})
Comment thread
joshblack marked this conversation as resolved.
})
})
1 change: 1 addition & 0 deletions packages/react/vitest.config.browser.mts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default defineConfig({
'**/*.types.test.tsx',
'src/__tests__/exports.test.ts',
'src/__tests__/storybook.test.tsx',
'src/__tests__/css-layers.test.ts',
],
include: ['src/**/*.test.?(c|m)[jt]s?(x)'],
setupFiles: ['config/vitest/browser/setup.ts'],
Expand Down
2 changes: 1 addition & 1 deletion packages/react/vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default defineConfig({
},
test: {
name: '@primer/react (node)',
include: ['src/__tests__/exports.test.ts', 'src/__tests__/storybook.test.tsx'],
include: ['src/__tests__/exports.test.ts', 'src/__tests__/storybook.test.tsx', 'src/__tests__/css-layers.test.ts'],
environment: 'node',
detectAsyncLeaks: true,
},
Expand Down
Loading