diff --git a/.changeset/pre.json b/.changeset/pre.json
index 04b9871b2c9..474bad5d717 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -6,7 +6,6 @@
"codesandbox": "0.0.0",
"example-app-router": "0.0.0",
"example-consumer-test": "0.0.0",
- "example-nextjs": "0.0.0",
"@primer/react": "36.27.0",
"rollup-plugin-import-css": "0.0.0",
"postcss-preset-primer": "0.0.0"
@@ -28,4 +27,4 @@
"twelve-tables-leave",
"young-meals-worry"
]
-}
+}
\ No newline at end of file
diff --git a/.changeset/quick-adults-buy.md b/.changeset/quick-adults-buy.md
new file mode 100644
index 00000000000..281f4551a6e
--- /dev/null
+++ b/.changeset/quick-adults-buy.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": patch
+---
+
+Refactor Link component to use CSS modules using the feature flag `primer_react_css_modules`
diff --git a/.eslintrc.js b/.eslintrc.js
index 77286a7ae85..6846aad47d8 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -42,7 +42,6 @@ module.exports = {
'types/**/*',
'consumer-test/**/*',
'contributor-docs/adrs/*',
- 'examples/nextjs/**',
'examples/codesandbox/**',
// Note: this file is inlined from an external dependency
'packages/react/src/utils/polymorphic.ts',
diff --git a/contributor-docs/CONTRIBUTING.md b/contributor-docs/CONTRIBUTING.md
index 7797612c5e3..d1269a43102 100644
--- a/contributor-docs/CONTRIBUTING.md
+++ b/contributor-docs/CONTRIBUTING.md
@@ -1,25 +1,29 @@
# Contribution guidelines
-1. [Roadmap](#roadmap)
-2. [Before Getting Started](#before-getting-started)
-3. [Discussing non-public features or products](#discussing-non-public-features-or-products)
-4. [Developing Components](#developing-components)
- - [Tools we use](#tools-we-use)
- - [File Structure](#file-structure)
- - [Component patterns](#component-patterns)
- - [SSR compatibility](#ssr-compatibility)
- - [Adding the sx prop](#adding-the-sx-prop)
- - [Linting](#linting)
- - [TypeScript support](#typescript-support)
- - [Additional resources](#additional-resources)
-5. [Writing documentation](#writing-documentation)
-6. [Creating a pull request](#creating-a-pull-request)
- - [Adding changeset to your pull request](#adding-changeset-to-your-pull-request)
- - [What to expect after opening a pull request](#what-to-expect-after-opening-a-pull-request)
- - [What we look for in reviews](#what-we-look-for-in-reviews)
- - [Previewing your changes](#previewing-your-changes)
-7. [Deploying](#deploying)
-8. [Troubleshooting](#troubleshooting)
+- [Contribution guidelines](#contribution-guidelines)
+ - [Roadmap](#roadmap)
+ - [Before Getting Started](#before-getting-started)
+ - [Proposing new components](#proposing-new-components)
+ - [Discussing non-public features or products](#discussing-non-public-features-or-products)
+ - [Developing components](#developing-components)
+ - [Tools we use](#tools-we-use)
+ - [File structure](#file-structure)
+ - [Component patterns](#component-patterns)
+ - [SSR compatibility](#ssr-compatibility)
+ - [Adding the `sx` prop](#adding-the-sx-prop)
+ - [Linting](#linting)
+ - [ESLint](#eslint)
+ - [Markdownlint](#markdownlint)
+ - [TypeScript support](#typescript-support)
+ - [Additional resources](#additional-resources)
+ - [Writing documentation](#writing-documentation)
+ - [Creating a pull request](#creating-a-pull-request)
+ - [Adding changeset to your pull request](#adding-changeset-to-your-pull-request)
+ - [What to expect after opening a pull request](#what-to-expect-after-opening-a-pull-request)
+ - [What we look for in reviews](#what-we-look-for-in-reviews)
+ - [Previewing your changes](#previewing-your-changes)
+ - [Deploying](#deploying)
+ - [Troubleshooting](#troubleshooting)
## Roadmap
@@ -148,8 +152,6 @@ We consider a component SSR-compatible if it...
We use [`eslint-plugin-ssr-friendly`](https://github.com/kopiro/eslint-plugin-ssr-friendly) to prevent misuse of DOM globals. If you see an error from this plugin, please fix it before merging your PR.
-If your component doesn't use DOM globals, it likely won't cause layout shift during hydration. However, if you suspect that your component might cause layout shift, you can use the example Next.js app (`examples/nextjs`) to debug. Import and render your component in `examples/nextjs/src/pages/index.js` then run the example app with `cd examples/nextjs && npm run develop`.
-
### Adding the `sx` prop
Each component should accept a prop called `sx` that allows for setting theme-aware ad-hoc styles. See the [overriding styles](https://primer.style/react/overriding-styles) doc for more information on using the prop.
diff --git a/e2e/components/Link.test.ts b/e2e/components/Link.test.ts
index 692444882f2..dc1f2f60d5c 100644
--- a/e2e/components/Link.test.ts
+++ b/e2e/components/Link.test.ts
@@ -31,6 +31,9 @@ test.describe('Link', () => {
id: story.id,
globals: {
colorScheme: theme,
+ featureFlags: {
+ primer_react_css_modules: true,
+ },
},
})
@@ -47,6 +50,39 @@ test.describe('Link', () => {
})
test('axe @aat', async ({page}) => {
+ await visit(page, {
+ id: story.id,
+ globals: {
+ colorScheme: theme,
+ featureFlags: {
+ primer_react_css_modules: true,
+ },
+ },
+ })
+ await expect(page).toHaveNoViolations()
+ })
+
+ test('default (styled-component) @vrt', async ({page}) => {
+ await visit(page, {
+ id: story.id,
+ globals: {
+ colorScheme: theme,
+ },
+ })
+
+ // Default state
+ expect(await page.screenshot()).toMatchSnapshot(`Link.${story.title}.${theme}.png`)
+
+ // Hover state
+ await page.getByRole('link').hover()
+ expect(await page.screenshot()).toMatchSnapshot(`Link.${story.title}.${theme}.hover.png`)
+
+ // Focus state
+ await page.keyboard.press('Tab')
+ expect(await page.screenshot()).toMatchSnapshot(`Link.${story.title}.${theme}.focus.png`)
+ })
+
+ test('axe (styled-component) @aat', async ({page}) => {
await visit(page, {
id: story.id,
globals: {
diff --git a/examples/nextjs/.gitignore b/examples/nextjs/.gitignore
deleted file mode 100644
index a680367ef56..00000000000
--- a/examples/nextjs/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-.next
diff --git a/examples/nextjs/next-env.d.ts b/examples/nextjs/next-env.d.ts
deleted file mode 100644
index 4f11a03dc6c..00000000000
--- a/examples/nextjs/next-env.d.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-///
Tab: {tab}
-} - -export default Tabs diff --git a/examples/nextjs/tsconfig.json b/examples/nextjs/tsconfig.json deleted file mode 100644 index 7455936a76c..00000000000 --- a/examples/nextjs/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "strict": false, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "incremental": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve" - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx" - ], - "exclude": [ - "node_modules" - ] -} diff --git a/package-lock.json b/package-lock.json index 79399125e11..0904d9bc007 100644 --- a/package-lock.json +++ b/package-lock.json @@ -292,7 +292,7 @@ "name": "example-app-router", "version": "0.0.0", "dependencies": { - "@primer/react": "37.0.0-rc.0", + "@primer/react": "37.0.0-rc.1", "next": "^14.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -311,7 +311,7 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@primer/react": "37.0.0-rc.0", + "@primer/react": "37.0.0-rc.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^7.11.0", @@ -329,7 +329,7 @@ "name": "example-consumer-test", "version": "0.0.0", "dependencies": { - "@primer/react": "37.0.0-rc.0", + "@primer/react": "37.0.0-rc.1", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.19", "@types/styled-components": "^5.1.11", @@ -350,18 +350,6 @@ "node": ">=4.2.0" } }, - "examples/nextjs": { - "name": "example-nextjs", - "version": "0.0.0", - "dependencies": { - "@primer/octicons-react": "19.x", - "@primer/react": "37.0.0-rc.0", - "next": "^14.1.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "styled-components": "5.x" - } - }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "license": "MIT", @@ -30907,10 +30895,6 @@ "resolved": "examples/consumer-test", "link": true }, - "node_modules/example-nextjs": { - "resolved": "examples/nextjs", - "link": true - }, "node_modules/exec-sh": { "version": "0.3.6", "license": "MIT" @@ -62418,7 +62402,7 @@ }, "packages/react": { "name": "@primer/react", - "version": "37.0.0-rc.0", + "version": "37.0.0-rc.1", "license": "MIT", "dependencies": { "@github/combobox-nav": "^2.1.5", @@ -63219,4 +63203,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/react/src/Link/Link.module.css b/packages/react/src/Link/Link.module.css new file mode 100644 index 00000000000..6fe46242c04 --- /dev/null +++ b/packages/react/src/Link/Link.module.css @@ -0,0 +1,40 @@ +.Link { + color: var(--fgColor-accent); + text-decoration: none; + + /* Reset for button tags */ + &:is(button) { + display: inline-block; + padding: 0; + font-size: inherit; + white-space: nowrap; + cursor: pointer; + user-select: none; + background-color: transparent; + border: 0; + appearance: none; + } + + &:hover { + text-decoration: underline; + } + + /* Deprecated: but need to support backwards compatibility */ + &[data-underline='true'], + /* + Inline links (inside a text block), however, should have underline based on accessibility setting set in data-attribute + Note: setting underline={false} does not override this + */ + [data-a11y-link-underlines='true'] &[data-inline='true'] { + text-decoration: underline; + } + + &[data-muted='true'] { + color: var(--fgColor-muted); + + &:hover { + color: var(--fgColor-accent); + text-decoration: none; + } + } +} diff --git a/packages/react/src/Link/Link.tsx b/packages/react/src/Link/Link.tsx index 36366762dbd..cdc7743addb 100644 --- a/packages/react/src/Link/Link.tsx +++ b/packages/react/src/Link/Link.tsx @@ -1,3 +1,4 @@ +import cx from 'clsx' import React, {forwardRef, useEffect} from 'react' import styled from 'styled-components' import {system} from 'styled-system' @@ -5,6 +6,9 @@ import {get} from '../constants' import {useRefObjectAsForwardedRef} from '../hooks' import type {SxProp} from '../sx' import sx from '../sx' +import classes from './Link.module.css' +import {useFeatureFlag} from '../FeatureFlags' +import Box from '../Box' import type {ComponentProps} from '../utils/types' import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' @@ -57,7 +61,9 @@ const StyledLink = styled.a