├── src ├── table-object │ ├── index.scss │ └── table-object.scss ├── header │ ├── index.scss │ ├── README.md │ └── header.scss ├── toasts │ ├── index.scss │ └── README.md ├── box │ ├── index.scss │ ├── README.md │ └── box-overlay.scss ├── loaders │ ├── index.scss │ ├── loaders.scss │ └── README.md ├── marketing │ ├── utilities │ │ ├── filters.scss │ │ ├── borders.scss │ │ ├── index.scss │ │ ├── animations.scss │ │ └── layout.scss │ ├── support │ │ └── index.scss │ ├── links │ │ ├── index.scss │ │ └── link.scss │ ├── buttons │ │ └── index.scss │ ├── type │ │ └── index.scss │ ├── index.scss │ └── README.md ├── tooltips │ ├── index.scss │ └── README.md ├── color-modes │ ├── themes │ │ ├── dark.scss │ │ ├── light.scss │ │ ├── dark_dimmed.scss │ │ ├── dark_colorblind.scss │ │ ├── dark_tritanopia.scss │ │ ├── light_colorblind.scss │ │ ├── light_tritanopia.scss │ │ ├── dark_high_contrast.scss │ │ └── light_high_contrast.scss │ └── index.scss ├── autocomplete │ ├── index.scss │ ├── README.md │ └── suggester.scss ├── branch-name │ ├── index.scss │ ├── README.md │ └── branch-name.scss ├── pagination │ ├── index.scss │ └── README.md ├── select-menu │ ├── index.scss │ └── README.md ├── labels │ ├── index.scss │ ├── issue-labels.scss │ ├── README.md │ └── mixins.scss ├── buttons │ ├── index.scss │ ├── README.md │ └── button-group.scss ├── avatars │ ├── index.scss │ ├── avatar-parent-child.scss │ ├── README.md │ └── circle-badge.scss ├── base │ ├── octicons.scss │ ├── index.scss │ ├── README.md │ ├── native-colors.scss │ ├── kbd.scss │ └── typography-base.scss ├── navigation │ ├── index.scss │ ├── README.md │ ├── filter-list.scss │ └── sidenav.scss ├── forms │ ├── index.scss │ ├── README.md │ ├── form-select.scss │ ├── input-group.scss │ └── radio-group.scss ├── layout │ ├── index.scss │ ├── container.scss │ ├── README.md │ └── grid-offset.scss ├── markdown │ ├── index.scss │ ├── README.md │ ├── blob-csv.scss │ ├── tables.scss │ ├── footnotes.scss │ ├── code.scss │ ├── lists.scss │ ├── headings.scss │ └── markdown-body.scss ├── support │ ├── index.scss │ ├── variables │ │ ├── misc.scss │ │ └── typography.scss │ ├── README.md │ └── mixins │ │ ├── layout.scss │ │ └── misc.scss ├── docs.scss ├── primitives │ ├── index.scss │ └── temp-typography-tokens.scss ├── utilities │ ├── index.scss │ ├── README.md │ ├── box-shadow.scss │ ├── padding.scss │ ├── margin.scss │ ├── details.scss │ ├── flexbox.scss │ └── visibility-display.scss ├── index.scss ├── product │ ├── index.scss │ └── README.md └── core │ ├── README.md │ └── index.scss ├── prettier.config.cjs ├── .vercelignore ├── script ├── .eslintrc.json ├── pretest ├── prepublish ├── build-docs ├── primer-css-compiler.js ├── selector-diff-report ├── build-css.js ├── check-for-changeset ├── stylelint-add-disables.js ├── stylelint-remove-disables.js └── analyze-variables.js ├── .npmrc ├── docs ├── stories │ ├── static │ │ ├── typography-image.png │ │ ├── objects-image.svg │ │ ├── utilities-image.svg │ │ ├── spacing-image.svg │ │ ├── color-image.svg │ │ ├── components-image.svg │ │ └── components-image copy.svg │ ├── utilities │ │ ├── MarketingFilters.stories.jsx │ │ ├── MarketingFilters.mdx │ │ ├── MarketingLayout.stories.jsx │ │ ├── MarketingLayout.mdx │ │ ├── MarketingTypography.mdx │ │ ├── Details.stories.jsx │ │ ├── Padding.stories.jsx │ │ ├── Border.stories.jsx │ │ └── Shadow.stories.jsx │ ├── deprecated-components │ │ ├── BranchName │ │ │ ├── BranchName.mdx │ │ │ └── BranchName.stories.tsx │ │ ├── FilterList │ │ │ ├── FilterList.mdx │ │ │ └── FilterList.stories.tsx │ │ ├── Loaders │ │ │ ├── Loaders.mdx │ │ │ └── Loaders.stories.tsx │ │ ├── IssueLabel │ │ │ ├── IssueLabel.mdx │ │ │ └── IssueLabel.stories.tsx │ │ ├── SubNav │ │ │ └── SubNav.mdx │ │ ├── BoxOverlay │ │ │ └── BoxOverlay.mdx │ │ ├── Marketing │ │ │ └── Marketing.mdx │ │ ├── SideNav │ │ │ └── SideNav.mdx │ │ ├── Pagination │ │ │ ├── Pagination.mdx │ │ │ └── Pagination.stories.tsx │ │ ├── Toast │ │ │ └── Toast.mdx │ │ ├── Header │ │ │ └── Header.mdx │ │ └── Tooltip │ │ │ └── Tooltip.mdx │ ├── support │ │ ├── Prototyping.mdx │ │ └── Deprecations.mdx │ ├── helpers │ │ └── pageLayoutBehavior.jsx │ ├── principles │ │ └── SCSS.mdx │ └── components │ │ └── Layout │ │ └── StackExamples.stories.jsx ├── .storybook │ ├── preview-head.html │ ├── manager.js │ ├── main.js │ ├── preview.css │ ├── storybook.css │ └── theme.js ├── .babelrc ├── postcss.config.js ├── .eslintrc.json ├── script │ └── build-storybook └── package.json ├── .changeset ├── wet-moose-beg.md ├── config.json └── README.md ├── eslint.config.cjs ├── stylelint.config.cjs ├── __tests__ ├── .eslintrc.json ├── docs.test.js ├── css.test.js ├── utils │ ├── docs.js │ └── css.js └── build.test.js ├── .github ├── CODEOWNERS ├── release_template.md ├── workflows │ ├── release_tracking.yml │ ├── stale.yml │ ├── ci.yml │ ├── deploy_production.yml │ ├── axe.yml │ ├── deploy_preview.yml │ └── codeql.yml ├── ISSUE_TEMPLATE │ ├── primer-feature-request.md │ ├── style-guide-bug-report.md │ └── primer-bug-report.md ├── dependabot.yml └── pull_request_template.md ├── .npmignore ├── .gitignore ├── postcss.config.cjs ├── deprecations.js ├── LICENSE ├── RELEASING.md └── README.md /src/table-object/index.scss: -------------------------------------------------------------------------------- 1 | @import './table-object.scss'; 2 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('@github/prettier-config') 2 | -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | .*.sw? 2 | .changelog/ 3 | dist/ 4 | docs/dist 5 | docs/public/ 6 | -------------------------------------------------------------------------------- /script/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": 0 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/header/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | @import './header.scss'; 3 | -------------------------------------------------------------------------------- /src/toasts/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | @import './toasts.scss'; 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | git-tag-version=false 2 | legacy-peer-deps=true 3 | no-audit=true 4 | no-fund=true 5 | -------------------------------------------------------------------------------- /src/box/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | @import './box-overlay.scss'; 3 | -------------------------------------------------------------------------------- /src/loaders/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | @import './loaders.scss'; 3 | -------------------------------------------------------------------------------- /src/marketing/utilities/filters.scss: -------------------------------------------------------------------------------- 1 | .grayscale { 2 | filter: grayscale(100%); 3 | } 4 | -------------------------------------------------------------------------------- /src/tooltips/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | @import './tooltips.scss'; 3 | -------------------------------------------------------------------------------- /src/color-modes/themes/dark.scss: -------------------------------------------------------------------------------- 1 | @import '@primer/primitives/dist/css/functional/themes/dark'; 2 | -------------------------------------------------------------------------------- /src/color-modes/themes/light.scss: -------------------------------------------------------------------------------- 1 | @import '@primer/primitives/dist/css/functional/themes/light'; 2 | -------------------------------------------------------------------------------- /src/marketing/support/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../support/index.scss'; 2 | @import './variables.scss'; 3 | -------------------------------------------------------------------------------- /src/color-modes/themes/dark_dimmed.scss: -------------------------------------------------------------------------------- 1 | @import '@primer/primitives/dist/css/functional/themes/dark-dimmed'; 2 | -------------------------------------------------------------------------------- /src/autocomplete/index.scss: -------------------------------------------------------------------------------- 1 | // support files 2 | @import '../support/index.scss'; 3 | @import './suggester.scss'; 4 | -------------------------------------------------------------------------------- /src/branch-name/index.scss: -------------------------------------------------------------------------------- 1 | // support files 2 | @import '../support/index.scss'; 3 | @import './branch-name.scss'; 4 | -------------------------------------------------------------------------------- /src/marketing/links/index.scss: -------------------------------------------------------------------------------- 1 | // support files 2 | @import '../support/index.scss'; 3 | @import './link.scss'; 4 | -------------------------------------------------------------------------------- /src/pagination/index.scss: -------------------------------------------------------------------------------- 1 | // support files 2 | @import '../support/index.scss'; 3 | @import './pagination.scss'; 4 | -------------------------------------------------------------------------------- /src/select-menu/index.scss: -------------------------------------------------------------------------------- 1 | // support files 2 | @import '../support/index.scss'; 3 | @import './select-menu.scss'; 4 | -------------------------------------------------------------------------------- /src/color-modes/themes/dark_colorblind.scss: -------------------------------------------------------------------------------- 1 | @import '@primer/primitives/dist/css/functional/themes/dark-colorblind'; 2 | -------------------------------------------------------------------------------- /src/color-modes/themes/dark_tritanopia.scss: -------------------------------------------------------------------------------- 1 | @import '@primer/primitives/dist/css/functional/themes/dark-tritanopia'; 2 | -------------------------------------------------------------------------------- /src/color-modes/themes/light_colorblind.scss: -------------------------------------------------------------------------------- 1 | @import '@primer/primitives/dist/css/functional/themes/light-colorblind'; 2 | -------------------------------------------------------------------------------- /src/color-modes/themes/light_tritanopia.scss: -------------------------------------------------------------------------------- 1 | @import '@primer/primitives/dist/css/functional/themes/light-tritanopia'; 2 | -------------------------------------------------------------------------------- /src/labels/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | @import './mixins.scss'; 3 | @import './issue-labels.scss'; 4 | -------------------------------------------------------------------------------- /src/marketing/buttons/index.scss: -------------------------------------------------------------------------------- 1 | // support files 2 | @import '../support/index.scss'; 3 | @import './button.scss'; 4 | -------------------------------------------------------------------------------- /src/marketing/type/index.scss: -------------------------------------------------------------------------------- 1 | // support files 2 | @import '../support/index.scss'; 3 | @import './typography.scss'; 4 | -------------------------------------------------------------------------------- /docs/stories/static/typography-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primer/css/HEAD/docs/stories/static/typography-image.png -------------------------------------------------------------------------------- /src/color-modes/themes/dark_high_contrast.scss: -------------------------------------------------------------------------------- 1 | @import '@primer/primitives/dist/css/functional/themes/dark-high-contrast'; 2 | -------------------------------------------------------------------------------- /src/color-modes/themes/light_high_contrast.scss: -------------------------------------------------------------------------------- 1 | @import '@primer/primitives/dist/css/functional/themes/light-high-contrast'; 2 | -------------------------------------------------------------------------------- /.changeset/wet-moose-beg.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@primer/css": minor 3 | --- 4 | 5 | Change `contrast` input background to `bgColor-default` 6 | -------------------------------------------------------------------------------- /docs/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /eslint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | "languageOptions": { 3 | "sourceType": "module", 4 | "ecmaVersion": "latest" 5 | } 6 | }] 7 | -------------------------------------------------------------------------------- /src/buttons/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | @import './button.scss'; 3 | @import './button-group.scss'; 4 | @import './misc.scss'; 5 | -------------------------------------------------------------------------------- /src/avatars/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | 3 | // Avatars 4 | @import './avatar-parent-child.scss'; 5 | @import './circle-badge.scss'; 6 | -------------------------------------------------------------------------------- /stylelint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@primer/stylelint-config'], 3 | ignoreFiles: ['**/*.js', '**/*.cjs'], 4 | rules: {} 5 | } 6 | -------------------------------------------------------------------------------- /__tests__/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:jest/recommended" 4 | ], 5 | "rules": { 6 | "eslint-comments/no-use": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import {addons} from '@storybook/manager-api' 2 | import theme from './theme' 3 | 4 | addons.setConfig({ 5 | theme: theme, 6 | }) 7 | -------------------------------------------------------------------------------- /src/base/octicons.scss: -------------------------------------------------------------------------------- 1 | .octicon { 2 | display: inline-block; 3 | overflow: visible !important; 4 | vertical-align: text-bottom; 5 | fill: currentColor; 6 | } 7 | -------------------------------------------------------------------------------- /src/navigation/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | // Navigation 3 | @import './filter-list.scss'; 4 | @import './sidenav.scss'; 5 | @import './subnav.scss'; 6 | -------------------------------------------------------------------------------- /script/pretest: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | mkdir -p tmp 5 | cd tmp 6 | echo "{\"package\":\"tmp\",\"version\":\"0.0.0\"}" > package.json 7 | npm i @primer/css@latest --force 8 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @primer/design-reviewers 2 | package.json @primer/design-reviewers @primer/engineer-reviewers 3 | package-lock.json @primer/design-reviewers @primer/engineer-reviewers 4 | -------------------------------------------------------------------------------- /src/marketing/utilities/borders.scss: -------------------------------------------------------------------------------- 1 | // Marketing border utilities 2 | 3 | // XXX If you're looking for responsive border utilities, they've moved to 4 | // ../../utilities/borders.scss 5 | -------------------------------------------------------------------------------- /src/marketing/utilities/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | // utilities 3 | @import './animations.scss'; 4 | @import './borders.scss'; 5 | @import './filters.scss'; 6 | @import './layout.scss'; 7 | -------------------------------------------------------------------------------- /src/forms/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | @import './form-control.scss'; 3 | @import './form-select.scss'; 4 | @import './form-group.scss'; 5 | @import './input-group.scss'; 6 | @import './radio-group.scss'; 7 | -------------------------------------------------------------------------------- /src/base/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | @import './native-colors.scss'; 3 | @import './normalize.scss'; 4 | @import './base.scss'; 5 | @import './kbd.scss'; 6 | @import './typography-base.scss'; 7 | @import './octicons.scss'; 8 | -------------------------------------------------------------------------------- /src/layout/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | @import './app-frame.scss'; 3 | @import './container.scss'; 4 | @import './grid.scss'; 5 | @import './grid-offset.scss'; 6 | @import './page-layout.scss'; 7 | @import './stack.scss'; 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | __tests__/ 2 | .changeset/ 3 | .github/ 4 | docs/ 5 | script/ 6 | # we ignore this because everything in src/ is copied out in script/prepublish 7 | src/ 8 | tmp/ 9 | docs.scss 10 | .* 11 | *.log 12 | vercel.json 13 | *.config.js 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.tgz 3 | .DS_Store 4 | .cache/ 5 | .changelog/ 6 | .next/ 7 | .sass-cache 8 | .storybuild/ 9 | .stylelintcache 10 | _site 11 | dist/ 12 | docs/dist 13 | node_modules/ 14 | public/ 15 | searchIndex.js 16 | tmp 17 | yarn.lock 18 | -------------------------------------------------------------------------------- /docs/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "unambiguous", 3 | "presets": [ 4 | "@babel/preset-react", 5 | [ 6 | "@babel/preset-env", 7 | { 8 | "targets": { 9 | "chrome": 100 10 | } 11 | } 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /script/prepublish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # generate the build directory 5 | npm run dist > /dev/null 6 | 7 | files=$(git ls-files src | sed -e 's#^src/##' | sed -e 's#/.*$##' | sort -u) 8 | echo $files > publish-files.log 9 | cd src 10 | cp -rv $files .. 11 | cd - 12 | -------------------------------------------------------------------------------- /docs/stories/utilities/MarketingFilters.stories.jsx: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Utilities/Marketing/Filters', 3 | } 4 | 5 | export const Grayscale = ({}) => ( 6 | <> 7 | 8 | > 9 | ) 10 | -------------------------------------------------------------------------------- /src/markdown/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | @import './markdown-body.scss'; 3 | @import './headings.scss'; 4 | @import './lists.scss'; 5 | @import './tables.scss'; 6 | @import './images.scss'; 7 | @import './code.scss'; 8 | @import './blob-csv.scss'; 9 | @import './footnotes.scss'; 10 | -------------------------------------------------------------------------------- /src/support/index.scss: -------------------------------------------------------------------------------- 1 | // variables 2 | @import './variables/typography.scss'; 3 | @import './variables/layout.scss'; 4 | @import './variables/misc.scss'; 5 | 6 | // mixins 7 | @import './mixins/color-modes.scss'; 8 | @import './mixins/typography.scss'; 9 | @import './mixins/layout.scss'; 10 | @import './mixins/misc.scss'; 11 | -------------------------------------------------------------------------------- /script/build-docs: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Build the base project so we can pull out the JSON data 4 | npm run dist 5 | cp -rf dist docs 6 | 7 | # Now build the docs site using that data 8 | cd docs 9 | 10 | if [ -n "$1" ]; then 11 | CI=true npm run build:storybook preview 12 | else 13 | CI=true npm run build:storybook 14 | fi 15 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.5.0/schema.json", 3 | "changelog": ["@changesets/changelog-github", {"repo": "primer/css"}], 4 | "commit": false, 5 | "linked": [], 6 | "access": "public", 7 | "baseBranch": "main", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [] 10 | } 11 | -------------------------------------------------------------------------------- /.github/release_template.md: -------------------------------------------------------------------------------- 1 | Preparing for a release. 2 | 3 | ### Checklist 4 | 5 | Make sure these items are checked before merging. 6 | 7 | - [ ] Preview the docs change. 8 | - [ ] Preview npm release candidate. 9 | - [ ] CI passes on the release PR. 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /script/primer-css-compiler.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss' 2 | import postCssConfig from '../postcss.config.cjs' 3 | 4 | export default async function compiler(css, options) { 5 | const { plugins, ...config } = postCssConfig 6 | 7 | const result = await postcss(plugins).process(css, { 8 | ...config, 9 | ...options 10 | }) 11 | return result 12 | } 13 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/BranchName/BranchName.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as BranchNameStories from './BranchName.stories' 4 | 5 | 6 | 7 | # BranchName 8 | 9 | Branch names can be a link, span, and include an octicon before the branch name. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/docs.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Primer CSS 3 | * https://primer.style 4 | * 5 | * Released under MIT license. 6 | */ 7 | 8 | // Docs styles 9 | // Only meant for the docs at https://primer.style/css 10 | 11 | // CSS color variables 12 | @import './color-modes/index.scss'; 13 | 14 | // Global requirements 15 | @import './core/index.scss'; 16 | @import './product/index.scss'; 17 | @import './marketing/index.scss'; 18 | -------------------------------------------------------------------------------- /src/marketing/index.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * @primer/css/marketing 3 | * http://primer.style/css 4 | * 5 | * Released under MIT license. Copyright (c) 2019 GitHub Inc. 6 | */ 7 | 8 | // Global requirements 9 | @import './support/index.scss'; 10 | 11 | // marketing specific css modules 12 | @import './type/index.scss'; 13 | @import './buttons/index.scss'; 14 | @import './links/index.scss'; 15 | @import './utilities/index.scss'; 16 | -------------------------------------------------------------------------------- /src/color-modes/index.scss: -------------------------------------------------------------------------------- 1 | // All themes 2 | 3 | @import './themes/light.scss'; 4 | @import './themes/light_colorblind.scss'; 5 | @import './themes/light_high_contrast.scss'; 6 | @import './themes/light_tritanopia.scss'; 7 | @import './themes/dark.scss'; 8 | @import './themes/dark_dimmed.scss'; 9 | @import './themes/dark_high_contrast.scss'; 10 | @import './themes/dark_colorblind.scss'; 11 | @import './themes/dark_tritanopia.scss'; 12 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/FilterList/FilterList.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as FilterListStories from './FilterList.stories' 4 | 5 | 6 | 7 | # FilterList 8 | 9 | A vertical list of filters. Grey text on white background. Selecting a filter from the list will fill its background with blue and make the text white. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/loaders/loaders.scss: -------------------------------------------------------------------------------- 1 | // Loaders 2 | 3 | // Animated Ellipsis 4 | 5 | .AnimatedEllipsis { 6 | display: inline-block; 7 | overflow: hidden; 8 | vertical-align: bottom; 9 | 10 | &::after { 11 | display: inline-block; 12 | content: '...'; 13 | animation: AnimatedEllipsis-keyframes 1.2s steps(4, jump-none) infinite; 14 | } 15 | 16 | @keyframes AnimatedEllipsis-keyframes { 17 | 0% { transform: translateX(-100%); } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | // This config file is necessary to get postcss-loader to work in 2 | // ./src/@primer/gatsby-theme-doctocat/components/live-preview-wrapper.js. 3 | const path = require('path') 4 | 5 | module.exports = { 6 | parser: 'postcss-scss', 7 | plugins: [ 8 | require('postcss-node-sass')({ 9 | includePaths: [path.join(__dirname, 'node_modules')], 10 | outputStyle: 'compressed' 11 | }), 12 | require('autoprefixer') 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/Loaders/Loaders.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as LoaderStories from './Loaders.stories' 4 | 5 | 6 | 7 | # Loaders 8 | 9 | Loaders inform users that an action is still in progress and might take a while to complete. 10 | 11 | Add an animated ellipsis at the end of text with ``. 12 | 13 | 14 | -------------------------------------------------------------------------------- /.github/workflows/release_tracking.yml: -------------------------------------------------------------------------------- 1 | name: Release Event Tracking 2 | # Measure a datadog event every time a release occurs 3 | 4 | on: 5 | pull_request: 6 | types: 7 | - closed 8 | - opened 9 | - reopened 10 | 11 | release: 12 | types: [published] 13 | 14 | jobs: 15 | release-tracking: 16 | name: Release Tracking 17 | uses: primer/.github/.github/workflows/release_tracking.yml@v2.4.0 18 | secrets: 19 | datadog_api_key: ${{ secrets.DATADOG_API_KEY }} 20 | -------------------------------------------------------------------------------- /docs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:react/recommended", 4 | "plugin:jsx-a11y/recommended", 5 | "plugin:storybook/recommended" 6 | ], 7 | "rules": { 8 | "import/no-namespace": 0, 9 | "no-unused-vars": [ 10 | "error", 11 | { 12 | "ignoreRestSiblings": true 13 | } 14 | ] 15 | }, 16 | "settings": { 17 | "react": { 18 | "version": "detect" 19 | } 20 | }, 21 | "globals": { 22 | "__DEV__": "readonly" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/script/build-storybook: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | # Add base url to be able to serve static files 4 | if [ -n "$1" ]; then 5 | echo '' >> .storybook/manager-head.html 6 | else 7 | echo '' >> .storybook/manager-head.html 8 | fi 9 | 10 | # Build storybook inside docs 11 | npx storybook build -o public/storybook public/static 12 | 13 | # Remove manager-head after build to not interfere with dev builds 14 | rm .storybook/manager-head.html 15 | -------------------------------------------------------------------------------- /src/primitives/index.scss: -------------------------------------------------------------------------------- 1 | @import '@primer/primitives/dist/css/base/size/size'; 2 | @import '@primer/primitives/dist/css/base/typography/typography'; 3 | @import '@primer/primitives/dist/css/functional/size/border'; 4 | @import '@primer/primitives/dist/css/functional/size/breakpoints'; 5 | @import '@primer/primitives/dist/css/functional/size/size'; 6 | @import '@primer/primitives/dist/css/functional/size/viewport'; 7 | @import '@primer/primitives/dist/css/functional/typography/typography'; 8 | @import './temp-typography-tokens.scss'; 9 | -------------------------------------------------------------------------------- /docs/stories/utilities/MarketingFilters.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta} from '@storybook/blocks' 2 | 3 | import * as MarketingFiltersStories from './MarketingFilters.stories' 4 | 5 | 6 | 7 | # Marketing filters 8 | 9 | Filter utility classes can be applied to divs or images to apply visual effects. 10 | 11 | ## Grayscale 12 | 13 | Applying `.grayscale` to an element will remove all of its colors, and make it render in black and white. 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/IssueLabel/IssueLabel.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as IssueLabelStories from './IssueLabel.stories' 4 | 5 | 6 | 7 | # IssueLabel 8 | 9 | Issue labels are used for adding labels to issues and pull requests. They also come with emoji support. 10 | 11 | 12 | 13 | If an issue label needs to be bigger, add the `.IssueLabel--big` modifier. 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/utilities/index.scss: -------------------------------------------------------------------------------- 1 | @import '../support/index.scss'; 2 | // utilities 3 | @import './animations.scss'; 4 | @import './borders.scss'; 5 | @import './box-shadow.scss'; 6 | @import './colors.scss'; 7 | @import './details.scss'; 8 | @import './flexbox.scss'; 9 | @import './layout.scss'; 10 | @import './margin.scss'; 11 | @import './padding.scss'; 12 | @import './typography.scss'; 13 | // Visibility and display should always come last in the imports so that they override other utilities with !important 14 | @import './visibility-display.scss'; 15 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /script/selector-diff-report: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | function log() { 5 | echo "$@" 1>&2 6 | } 7 | 8 | function warn() { 9 | echo "$@" 1>&2 10 | } 11 | 12 | pkg="@primer/css" 13 | path="dist/stats/primer.json" 14 | cp "tmp/node_modules/@primer/css/dist/stats/primer.json" before.json 15 | 16 | cp $path after.json 17 | 18 | key=".selectors.values[]" 19 | jq -r $key before.json | sort | uniq > before.txt 20 | jq -r $key after.json | sort | uniq > after.txt 21 | 22 | diff -U 1 {before,after}.txt 23 | 24 | rm {before,after}.{json,txt} 25 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Primer CSS 3 | * https://primer.style 4 | * 5 | * Released under MIT license. 6 | */ 7 | 8 | // Primer main file 9 | // 10 | // Imports all Primer files in their intended order for easy mass-inclusion. 11 | // Should you need specific files, you can easily use separate `@import`s. 12 | 13 | // CSS color variables 14 | @import './color-modes/index.scss'; 15 | @import './primitives/index.scss'; 16 | 17 | // Global requirements 18 | @import './core/index.scss'; 19 | @import './product/index.scss'; 20 | @import './marketing/index.scss'; 21 | -------------------------------------------------------------------------------- /src/avatars/avatar-parent-child.scss: -------------------------------------------------------------------------------- 1 | // .avatar-parent-child is when you see a small avatar at the bottom right 2 | // corner of a larger avatar. 3 | // 4 | // No Styleguide version 5 | .avatar-parent-child { 6 | position: relative; 7 | } 8 | 9 | .avatar-child { 10 | position: absolute; 11 | right: -15%; 12 | bottom: -9%; 13 | background-color: var(--bgColor-default, var(--color-canvas-default)); // For transparent backgrounds 14 | // stylelint-disable-next-line primer/borders 15 | border-radius: $border-radius-1; 16 | box-shadow: var(--avatar-shadow, var(--color-avatar-child-shadow)); 17 | } 18 | -------------------------------------------------------------------------------- /src/product/index.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * @primer/css/product 3 | * http://primer.style/css 4 | * 5 | * Released under MIT license. Copyright (c) 2019 GitHub Inc. 6 | */ 7 | 8 | // Global requirements 9 | @import '../support/index.scss'; 10 | 11 | // Product specific css modules 12 | @import '../autocomplete/index.scss'; 13 | @import '../avatars/index.scss'; 14 | @import '../branch-name/index.scss'; 15 | @import '../header/index.scss'; 16 | @import '../labels/index.scss'; 17 | @import '../loaders/index.scss'; 18 | @import '../markdown/index.scss'; 19 | @import '../select-menu/index.scss'; 20 | @import '../toasts/index.scss'; 21 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer') 2 | const sass = require('@csstools/postcss-sass') 3 | const scss = require('postcss-scss') 4 | const scssImport = require('postcss-import') 5 | const { join } = require('path') 6 | 7 | module.exports = { 8 | map: { 9 | sourcesContent: false, 10 | annotation: true 11 | }, 12 | customSyntax: scss, 13 | parser: scss, 14 | plugins: [ 15 | scssImport, 16 | sass({ 17 | includePaths: [join(__dirname, 'node_modules')], 18 | outputStyle: process.env.CSS_MINIFY === '0' ? 'expanded' : 'compressed' 19 | }), 20 | autoprefixer 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/labels/issue-labels.scss: -------------------------------------------------------------------------------- 1 | // Issue Labels 2 | 3 | // TODO: Replace with .Label once solid backgrounds are supported 4 | 5 | .IssueLabel { 6 | @include labels-base; 7 | 8 | .g-emoji { 9 | position: relative; 10 | // stylelint-disable-next-line primer/spacing 11 | top: -0.05em; 12 | display: inline-block; 13 | // stylelint-disable-next-line primer/typography 14 | font-size: 1em; 15 | // stylelint-disable-next-line primer/typography 16 | line-height: $lh-condensed-ultra; 17 | } 18 | 19 | &:hover { 20 | text-decoration: none; 21 | } 22 | } 23 | 24 | .IssueLabel--big { 25 | @include labels-large; 26 | } 27 | -------------------------------------------------------------------------------- /src/primitives/temp-typography-tokens.scss: -------------------------------------------------------------------------------- 1 | // Temporary typography vars in rem units variables 2 | :root { 3 | // Heading sizes - mobile 4 | // h4-h6 remain the same size on both mobile & desktop 5 | --h00-size-mobile: 2.5rem; 6 | --h0-size-mobile: 2rem; 7 | --h1-size-mobile: 1.625rem; 8 | --h2-size-mobile: 1.375rem; 9 | --h3-size-mobile: 1.125rem; 10 | 11 | // Heading sizes - desktop 12 | --h00-size: 3rem; 13 | --h0-size: 2.5rem; 14 | --h1-size: 2rem; 15 | --h2-size: 1.5rem; 16 | --h3-size: 1.25rem; 17 | --h4-size: 1rem; 18 | --h5-size: 0.875rem; 19 | --h6-size: 0.75rem; 20 | --body-font-size: 0.875rem; 21 | --font-size-small: 0.75rem; 22 | } 23 | -------------------------------------------------------------------------------- /src/table-object/table-object.scss: -------------------------------------------------------------------------------- 1 | // Deprecated 2 | // TODO: Replace TableObject with flexbox or a new Table component 3 | 4 | // TableObject is a module for creating dynamically resizable elements that 5 | // always sit on the same horizontal line (e.g., they never wrap). Using 6 | // tables means it's cross browser friendly. 7 | 8 | .TableObject { 9 | display: table; 10 | } 11 | 12 | // Place this on every "cell" 13 | .TableObject-item { 14 | display: table-cell; 15 | width: 1%; 16 | white-space: nowrap; 17 | vertical-align: middle; 18 | } 19 | 20 | // Place this on the largest or most important "cell" 21 | .TableObject-item--primary { 22 | width: 99%; 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/primer-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Primer CSS feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /src/support/variables/misc.scss: -------------------------------------------------------------------------------- 1 | // Miscellaneous variables 2 | 3 | // Border 4 | $border-width: 1px !default; 5 | $border-style: solid !default; 6 | $border: $border-width $border-style var(--borderColor-default, var(--color-border-default)) !default; 7 | $border-rem: var(--borderWidth-thin, 1px) solid var(--borderColor-default, var(--color-border-default)) !default; 8 | 9 | // Border Radius 10 | $border-radius-1: 4px !default; 11 | $border-radius-2: 6px !default; 12 | $border-radius-3: 8px !default; 13 | $border-radius: $border-radius-2 !default; 14 | 15 | // Tooltips 16 | $tooltip-max-width: 250px !default; 17 | $tooltip-delay: 0.4s !default; 18 | $tooltip-duration: 0.1s !default; 19 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/SubNav/SubNav.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as SubNavStories from './SubNav.stories' 4 | 5 | 6 | 7 | # SubNav 8 | 9 | `.subnav` is navigation that is typically used when on a dashboard type interface with another set of navigation above it. This helps distinguish navigation hierarchy. 10 | 11 | 12 | 13 | You can have `subnav-search` in the subnav bar. 14 | 15 | 16 | 17 | You can also use a `subnav-search-context` to display search help in a select menu. 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/layout/container.scss: -------------------------------------------------------------------------------- 1 | // Fixed-width, centered column for site content. 2 | // Handy container styles that match our breakpoints 3 | 4 | // 544px 5 | .container-sm { 6 | max-width: $width-sm; 7 | margin-right: auto; 8 | margin-left: auto; 9 | } 10 | 11 | // 768px 12 | .container-md { 13 | max-width: $container-md; 14 | margin-right: auto; 15 | margin-left: auto; 16 | } 17 | 18 | // 1004px - this matches the current fixed width: 980px + padding: px-3 19 | .container-lg { 20 | max-width: $container-lg; 21 | margin-right: auto; 22 | margin-left: auto; 23 | } 24 | 25 | // 1280px 26 | .container-xl { 27 | max-width: $container-xl; 28 | margin-right: auto; 29 | margin-left: auto; 30 | } 31 | -------------------------------------------------------------------------------- /src/box/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "box" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `box` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/box/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/box.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/base/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "base" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `base` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/base/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/base.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/core/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "core" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `core` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/core/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/core.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/forms/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "forms" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `forms` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/forms/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/forms.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/core/index.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * @primer/css/core 3 | * http://primer.style/css 4 | * 5 | * Released under MIT license. Copyright (c) 2019 GitHub Inc. 6 | */ 7 | 8 | // Global requirements 9 | @import '../support/index.scss'; 10 | 11 | // Color modes 12 | 13 | // Core modules 14 | @import '../base/index.scss'; 15 | @import '../box/index.scss'; 16 | @import '../buttons/index.scss'; 17 | @import '../table-object/index.scss'; 18 | @import '../forms/index.scss'; 19 | @import '../layout/index.scss'; 20 | @import '../navigation/index.scss'; 21 | @import '../pagination/index.scss'; 22 | @import '../tooltips/index.scss'; 23 | 24 | // Utilities always go last so that they can override components 25 | @import '../utilities/index.scss'; 26 | -------------------------------------------------------------------------------- /src/header/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "header" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `header` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/header/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/header.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/labels/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "labels" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `labels` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/labels/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/labels.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/layout/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "layout" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `layout` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/layout/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/layout.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/toasts/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "toasts" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `toasts` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/toasts/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/toasts.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/avatars/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "avatars" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `avatars` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/avatars/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/avatars.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/buttons/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "buttons" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `buttons` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/buttons/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/buttons.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/loaders/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "loaders" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `loaders` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/loaders/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/loaders.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/product/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "product" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `product` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/product/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/product.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/support/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "support" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `support` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/support/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/support.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/markdown/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "markdown" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `markdown` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/markdown/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/markdown.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/tooltips/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "tooltips" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `tooltips` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/tooltips/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/tooltips.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/marketing/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "marketing" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `marketing` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/marketing/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/marketing.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/utilities/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "utilities" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `utilities` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/utilities/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/utilities.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/navigation/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "navigation" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `navigation` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/navigation/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/navigation.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/pagination/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "pagination" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `pagination` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/pagination/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/pagination.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/branch-name/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "branch-name" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `branch-name` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/branch-name/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/branch-name.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/select-menu/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "select-menu" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `select-menu` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/select-menu/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/select-menu.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /src/autocomplete/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | bundle: "autocomplete" 3 | generated: true 4 | --- 5 | 6 | # Primer CSS: `autocomplete` bundle 7 | 8 | ## Usage 9 | 10 | Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: 11 | 12 | ```scss 13 | @import "@primer/css/autocomplete/index.scss"; 14 | ``` 15 | 16 | ## Build 17 | 18 | The `@primer/css` npm package includes a standalone CSS build of this module in `dist/autocomplete.css`. 19 | 20 | ## License 21 | 22 | [MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) 23 | 24 | 25 | [scss]: https://sass-lang.com/documentation/syntax#scss 26 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/BoxOverlay/BoxOverlay.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as BoxOverlayStories from './BoxOverlay.stories' 4 | 5 | 6 | 7 | # BoxOverlay 8 | 9 | Use the `Box--overlay` with the `` and [``](https://github.com/github/details-dialog), and add the `details-overlay-dark` utility if you wish to apply a dark transparent background. 10 | 11 | Box overlays come in three widths. The default `Box--overlay` is 440px wide, `Box-overlay--narrow` is 320px wide, and `Box-overlay--wide` is 640px wide. 12 | 13 | See [aria attributes](https://www.w3.org/TR/html-aria/#allowed-aria-roles-states-and-properties) 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/stories/static/objects-image.svg: -------------------------------------------------------------------------------- 1 | objects 2 | -------------------------------------------------------------------------------- /src/base/native-colors.scss: -------------------------------------------------------------------------------- 1 | // color-scheme 2 | // Enables color modes for native elements 3 | 4 | @include color-mode(light) { color-scheme: light; } 5 | 6 | @include color-mode(dark) { color-scheme: dark; } 7 | 8 | [data-color-mode] { 9 | color: var(--fgColor-default, var(--color-fg-default)); 10 | background-color: var(--bgColor-default, var(--color-canvas-default)); 11 | } 12 | 13 | // Windows High Contrast mode 14 | 15 | // Improves focus state for various components when Windows High Contrast mode is enabled 16 | // stylelint-disable selector-max-type 17 | @media (forced-colors: active) { 18 | body { 19 | --color-accent-emphasis: Highlight; 20 | --color-fg-on-emphasis: LinkText; 21 | --fgColor-onEmphasis: LinkText; 22 | --fgColor-accent: Highlight; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utilities/box-shadow.scss: -------------------------------------------------------------------------------- 1 | // Box shadow utilities 2 | 3 | // Box shadows 4 | 5 | .color-shadow-small { 6 | box-shadow: var(--shadow-resting-small, var(--color-shadow-small)) !important; 7 | } 8 | 9 | .color-shadow-medium { 10 | box-shadow: var(--shadow-resting-medium, var(--color-shadow-medium)) !important; 11 | } 12 | 13 | .color-shadow-large { 14 | box-shadow: var(--shadow-floating-large, var(--color-shadow-large)) !important; 15 | } 16 | 17 | .color-shadow-extra-large { 18 | box-shadow: var(--shadow-floating-xlarge, var(--color-shadow-extra-large)) !important; 19 | } 20 | 21 | .shadow-floating-small { 22 | box-shadow: var(--shadow-floating-small, var(--color-overlay-shadow)) !important; 23 | } 24 | 25 | // Turn off box shadow 26 | 27 | .box-shadow-none { 28 | box-shadow: none !important; 29 | } 30 | -------------------------------------------------------------------------------- /docs/stories/utilities/MarketingLayout.stories.jsx: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Utilities/Marketing/Layout', 3 | } 4 | 5 | export const Positions = ({}) => ( 6 | <> 7 | 8 | .top-2 9 | .right-md-4 10 | .left-lg-1 11 | 12 | > 13 | ) 14 | 15 | export const Offset = ({}) => ( 16 | <> 17 | 18 | .offset-n1 19 | .offset-n2 20 | 21 | > 22 | ) 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Maintain dependencies for GitHub Actions 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | groups: 14 | all: 15 | patterns: 16 | - "*" 17 | 18 | # Maintain dependencies for npm 19 | - package-ecosystem: "npm" 20 | directory: "/" 21 | schedule: 22 | interval: "daily" 23 | groups: 24 | all: 25 | patterns: 26 | - "*" 27 | -------------------------------------------------------------------------------- /docs/.storybook/main.js: -------------------------------------------------------------------------------- 1 | /** @type { import('@storybook/react-webpack5').StorybookConfig } */ 2 | const config = { 3 | stories: ['../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|ts|tsx)'], 4 | addons: [ 5 | '@storybook/addon-links', 6 | '@storybook/addon-essentials', 7 | '@storybook/addon-interactions', 8 | 'storybook-addon-pseudo-states', 9 | '@storybook/addon-storysource', 10 | '@geometricpanda/storybook-addon-badges', 11 | { 12 | name: '@storybook/addon-styling', 13 | options: { 14 | sass: { 15 | implementation: require('sass'), 16 | }, 17 | }, 18 | }, 19 | ], 20 | framework: { 21 | name: '@storybook/react-webpack5', 22 | options: {}, 23 | }, 24 | docs: { 25 | autodocs: 'tag', 26 | }, 27 | staticDirs: ['../stories/static'], 28 | } 29 | export default config 30 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### What are you trying to accomplish? 2 | 3 | 4 | 5 | ### What approach did you choose and why? 6 | 7 | 8 | 9 | ### What should reviewers focus on? 10 | 11 | 12 | 13 | ### Can these changes ship as is? 14 | 15 | 16 | 17 | - [ ] Yes, this PR does not depend on additional changes. 🚢 18 | -------------------------------------------------------------------------------- /script/build-css.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {globby} from 'globby' 3 | import compiler from './primer-css-compiler.js' 4 | import {dirname, join} from 'path' 5 | 6 | import fsExtra from 'fs-extra' 7 | const {mkdirp, readFile, writeFile} = fsExtra 8 | 9 | 10 | const inDir = 'src' 11 | const outDir = 'dist' 12 | const bundleNames = { 13 | 'index.scss': 'primer' 14 | } 15 | 16 | const files = await globby([`${inDir}/**/index.scss`]) 17 | await mkdirp(outDir) 18 | const inPattern = new RegExp(`^${inDir}/`) 19 | const tasks = files.map(async from => { 20 | const path = from.replace(inPattern, '') 21 | const name = bundleNames[path] || dirname(path).replace(/\//g, '-') 22 | 23 | const to = join(outDir, `${name}.css`) 24 | 25 | const result = await compiler(await readFile(from, 'utf8'), {from, to}) 26 | 27 | await Promise.all([ 28 | writeFile(to, result.css, 'utf8'), 29 | ]) 30 | }) 31 | 32 | await Promise.all(tasks) 33 | -------------------------------------------------------------------------------- /__tests__/docs.test.js: -------------------------------------------------------------------------------- 1 | import {getNavigationLinks, getContentFrontmatter} from './utils/docs' 2 | 3 | let navLinks, contentFM 4 | 5 | beforeAll(async () => { 6 | contentFM = await getContentFrontmatter() 7 | navLinks = getNavigationLinks() 8 | }) 9 | 10 | describe('frontmatter', () => { 11 | it('page title matches link title', () => { 12 | for (const link of navLinks) { 13 | const content = contentFM[link['url']] 14 | expect(content).not.toBeNull() 15 | expect(content['title']).toBe(link['title']) 16 | } 17 | }) 18 | 19 | it('contains path attribute', () => { 20 | for (const v of Object.values(contentFM)) { 21 | expect(v['path']).not.toBeNull() 22 | } 23 | }) 24 | }) 25 | 26 | describe('navigation', () => { 27 | it('has a file for each nav item', () => { 28 | for (const link of navLinks) { 29 | const content = contentFM[link['url']] 30 | expect(content).not.toBeNull() 31 | } 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /deprecations.js: -------------------------------------------------------------------------------- 1 | /** 2 | Deprecated Selectors 3 | ------------------------- 4 | These are deprecated selectors and should not be used. They include a replacement value, 5 | which can be an array or null. 6 | 7 | * 'deprecated-selector': 'replacement-selector' <-- Replace with this selector. 8 | * 'deprecated-selector': ['replacement-1', 'replacement-2'] <-- Replace with one of these selectors. 9 | * 'deprecated-selector': null <-- No option available, remove selector. 10 | */ 11 | 12 | import fs from 'fs' 13 | 14 | const deprecations = JSON.parse(fs.readFileSync('./dist/deprecations.json')) 15 | 16 | const deprecatedSelectors = deprecations['selectors'] 17 | const deprecatedSassVariables = deprecations['variables'] 18 | const deprecatedSassMixins = deprecations['mixins'] 19 | 20 | export { 21 | deprecatedSelectors, 22 | deprecatedSassVariables, 23 | deprecatedSassMixins 24 | } 25 | -------------------------------------------------------------------------------- /src/markdown/blob-csv.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable selector-max-type 2 | .markdown-body .csv-data { 3 | td, 4 | th { 5 | // stylelint-disable-next-line primer/spacing 6 | padding: 5px; 7 | overflow: hidden; 8 | // stylelint-disable-next-line primer/typography 9 | font-size: $font-size-small; 10 | // stylelint-disable-next-line primer/typography 11 | line-height: $lh-condensed-ultra; 12 | text-align: left; 13 | white-space: nowrap; 14 | } 15 | 16 | .blob-num { 17 | // stylelint-disable-next-line primer/spacing 18 | padding: 10px var(--base-size-8) 9px; 19 | text-align: right; 20 | background: var(--bgColor-default, var(--color-canvas-default)); 21 | border: 0; 22 | } 23 | 24 | tr { border-top: 0; } 25 | 26 | th { 27 | // stylelint-disable-next-line primer/typography 28 | font-weight: $font-weight-bold; 29 | background: var(--bgColor-muted, var(--color-canvas-subtle)); 30 | border-top: 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/Marketing/Marketing.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as MarketingStories from './Marketing.stories' 4 | 5 | 6 | 7 | # Marketing links 8 | 9 | Marketing links can be produced by combining the base class `link-mktg` with a set of modifier classes to control the size and color. 10 | 11 | 12 | 13 | ## Link sizes 14 | 15 | The marketing link size is defined with utility classes and come in large (`.f3-mktg`) and small (`.f4-mktg`): 16 | 17 | ## Link with emphasis 18 | 19 | Add class `link-emphasis-mktg` to the link, to give it a pale underline, that will fill up on hover. 20 | 21 | 22 | 23 | ## Link colors 24 | 25 | The link color is controlled with the [Primer color classes](/utilities/colors), while the symbol and underline styling will follow: 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/FilterList/FilterList.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {BADGE} from '@geometricpanda/storybook-addon-badges' 3 | 4 | export default { 5 | title: 'Deprecated/FilterList', 6 | parameters: { 7 | storyType: 'banner', 8 | controls: {hideNoControlsWarning: true}, 9 | badges: [BADGE.DEPRECATED], 10 | }, 11 | } 12 | 13 | export const Default = () => { 14 | return ( 15 | 16 | 17 | 18 | First filter 19 | 21 20 | 21 | 22 | 23 | 24 | Second filter 25 | 3 26 | 27 | 28 | 29 | 30 | Third filter 31 | 32 | 33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/style-guide-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Style guide bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/primer-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Primer CSS bug report 3 | about: Create a report to help us improve Primer CSS 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /src/base/kbd.scss: -------------------------------------------------------------------------------- 1 | // Keyboard shortcuts 2 | // stylelint-disable selector-max-type 3 | 4 | kbd { 5 | display: inline-block; 6 | padding: var(--base-size-4); 7 | // stylelint-disable-next-line primer/typography, declaration-property-value-no-unknown 8 | font: 11px $mono-font; 9 | // stylelint-disable-next-line primer/typography 10 | line-height: 10px; 11 | color: var(--fgColor-default, var(--color-fg-default)); 12 | vertical-align: middle; 13 | background-color: var(--bgColor-muted, var(--color-canvas-subtle)); 14 | // stylelint-disable-next-line primer/borders, primer/colors 15 | border: $border-style $border-width var(--borderColor-neutral-muted, var(--color-neutral-muted)); 16 | border-bottom-color: var(--borderColor-neutral-muted, var(--color-neutral-muted)); 17 | // stylelint-disable-next-line primer/borders 18 | border-radius: $border-radius; 19 | // stylelint-disable-next-line primer/box-shadow 20 | box-shadow: inset 0 -1px 0 var(--borderColor-neutral-muted, var(--color-neutral-muted)); 21 | } 22 | -------------------------------------------------------------------------------- /__tests__/css.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | getCurrentVersion, 3 | getPackageStats, 4 | getSelectorDiff, 5 | getVariableDiff, 6 | currentVersionDeprecations 7 | } from './utils/css' 8 | import semver from 'semver' 9 | 10 | let selectorsDiff, variablesDiff, version 11 | 12 | beforeAll(async () => { 13 | selectorsDiff = getSelectorDiff() 14 | variablesDiff = getVariableDiff() 15 | version = getCurrentVersion() 16 | }) 17 | 18 | describe('css', () => { 19 | it('The support.css file contains no compiled css', () => { 20 | const supportStats = getPackageStats('support') 21 | expect(supportStats.size).toEqual(0) 22 | }) 23 | }) 24 | 25 | describe('deprecations', () => { 26 | it('expects deprecations and their replacement to not be equal.', () => { 27 | const deprecations = currentVersionDeprecations() 28 | Object.keys(deprecations["selectors"]).forEach(deprecation => { 29 | const replacement = deprecations["selectors"][deprecation] 30 | expect(deprecation).not.toEqual(replacement) 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/Loaders/Loaders.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {BADGE} from '@geometricpanda/storybook-addon-badges' 3 | 4 | export default { 5 | title: 'Deprecated/Loaders', 6 | parameters: { 7 | storyType: 'banner', 8 | controls: {hideNoControlsWarning: true}, 9 | badges: [BADGE.DEPRECATED], 10 | }, 11 | } 12 | 13 | export const Default = () => { 14 | return ( 15 | <> 16 | 17 | Loading 18 | 19 | 20 | 21 | Loading 22 | 23 | 24 | 25 | Loading 26 | 27 | 28 | 29 | Loading 30 | 31 | 32 | > 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /docs/stories/static/utilities-image.svg: -------------------------------------------------------------------------------- 1 | utilities 2 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "docs", 4 | "repository": "primer/css", 5 | "scripts": { 6 | "storybook": "NODE_ENV=test storybook dev -p 6006", 7 | "build:storybook": "script/build-storybook" 8 | }, 9 | "engines": { 10 | "node": ">= 16.x" 11 | }, 12 | "dependencies": { 13 | "@babel/core": "^7.16.7", 14 | "@geometricpanda/storybook-addon-badges": "^2.0.0", 15 | "@storybook/addon-essentials": "^7.0.26", 16 | "@storybook/addon-interactions": "^7.0.26", 17 | "@storybook/addon-links": "^7.0.26", 18 | "@storybook/addon-storysource": "^7.6.20", 19 | "@storybook/addon-styling": "^1.3.2", 20 | "@storybook/blocks": "^7.0.26", 21 | "@storybook/react": "^7.0.26", 22 | "@storybook/react-webpack5": "^7.0.26", 23 | "@storybook/testing-library": "^0.0.14-next.2", 24 | "babel-loader": "^8.2.5", 25 | "clsx": "^1.2.1", 26 | "eslint-plugin-storybook": "^0.6.12", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "storybook": "^7.0.26", 30 | "storybook-addon-pseudo-states": "^2.1.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/stories/utilities/MarketingLayout.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta} from '@storybook/blocks' 2 | 3 | import * as MarketingLayoutStories from './MarketingLayout.stories' 4 | 5 | 6 | 7 | # Marketing layout 8 | 9 | ## Position elements with spacing utilities 10 | 11 | Position elements from all four element edges (`top`, `right`, `bottom`, and `left`) using any spacing utility from the global spacing scale and the marketing spacing scale (from `$spacer-1` to `$spacer-12`), including negative and 0 values. 12 | 13 | Use these with `.position-absolute` to position decorative assets and shapes on marketing sites. 14 | 15 | In an effort to reduce the size of our CSS, responsive breakpoints are only supported for `md` and `lg` breakpoints. **There is no support for `sm` and `xl` breakpoints.** 16 | 17 | 18 | 19 | ## Negative offset columns 20 | 21 | Using column offset classes can pull a div over X number of columns to the left. Negative offsets are available in spacings from 1 to 7. 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/stories/utilities/MarketingTypography.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as MarketingTypographyStories from './MarketingTypography.stories' 4 | 5 | 6 | 7 | # Marketing typography 8 | 9 | The typography for our marketing pages differs from Primer CSS's core. It is responsive, on a slightly different scale, and headlines are set in the [defined marketing font](https://github.com/primer/css/blob/main/src/marketing/support/variables.scss). 10 | 11 | ## Heading utilities 12 | 13 | 14 | 15 | Use `.h0-mktg` to `.h6-mktg` to change an element's font, size, and weight on marketing pages. 16 | 17 | ## Body content utilities 18 | 19 | 20 | 21 | Use `.f0-mktg` to `.f6-mktg` to change an element's body font, size, and weight on marketing pages. 22 | 23 | ## Typographic utilities 24 | 25 | These utilities are meant to be used in addition to Primer CSS's core utilities. 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/layout/grid-offset.scss: -------------------------------------------------------------------------------- 1 | // Optional offset options to work with grid.scss 2 | 3 | // Offset Columns 4 | 5 | @each $breakpoint, $variant in $responsive-variants { 6 | @include breakpoint($breakpoint) { 7 | .offset#{$variant}-1 { margin-left: (1 * 0.0833333333 * 100%) !important; } 8 | .offset#{$variant}-2 { margin-left: (2 * 0.0833333333 * 100%) !important; } 9 | .offset#{$variant}-3 { margin-left: (3 * 0.0833333333 * 100%) !important; } 10 | .offset#{$variant}-4 { margin-left: (4 * 0.0833333333 * 100%) !important; } 11 | .offset#{$variant}-5 { margin-left: (5 * 0.0833333333 * 100%) !important; } 12 | .offset#{$variant}-6 { margin-left: (6 * 0.0833333333 * 100%) !important; } 13 | .offset#{$variant}-7 { margin-left: (7 * 0.0833333333 * 100%) !important; } 14 | .offset#{$variant}-8 { margin-left: (8 * 0.0833333333 * 100%) !important; } 15 | .offset#{$variant}-9 { margin-left: (9 * 0.0833333333 * 100%) !important; } 16 | .offset#{$variant}-10 { margin-left: (10 * 0.0833333333 * 100%) !important; } 17 | .offset#{$variant}-11 { margin-left: (11 * 0.0833333333 * 100%) !important; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 GitHub Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/marketing/links/link.scss: -------------------------------------------------------------------------------- 1 | .link-mktg { 2 | position: relative; 3 | display: inline-block; 4 | 5 | &:hover { 6 | text-decoration: none; 7 | } 8 | 9 | &::after, 10 | &.link-emphasis-mktg::before { 11 | position: absolute; 12 | // stylelint-disable-next-line primer/spacing 13 | bottom: -0.15em; 14 | left: 0; 15 | width: calc(100% - 1em); 16 | height: 2px; 17 | pointer-events: none; 18 | content: ''; 19 | background-color: currentColor; 20 | transform: scaleX(0); 21 | transform-origin: 0 0; 22 | 23 | @media screen and (prefers-reduced-motion: no-preference) { 24 | transition: transform 0.3s ease; 25 | } 26 | } 27 | 28 | &.link-emphasis-mktg::before { 29 | opacity: 0.2; 30 | transform: scaleX(1); 31 | } 32 | 33 | &:hover, 34 | &:active { 35 | &::after { 36 | transform: scaleX(1); 37 | } 38 | } 39 | 40 | &:focus, 41 | &:focus-visible { 42 | outline-offset: 2px; 43 | } 44 | 45 | &.arrow-target-mktg { 46 | .arrow-symbol-mktg { 47 | // stylelint-disable-next-line primer/spacing 48 | margin-left: -$em-spacer-3; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/branch-name/branch-name.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable selector-max-type, selector-no-qualifying-type 2 | 3 | // A nice way to display branch names inside the UI. Can be a link or not. 4 | 5 | .branch-name { 6 | display: inline-block; 7 | // stylelint-disable-next-line primer/spacing 8 | padding: 2px 6px; 9 | // stylelint-disable-next-line primer/typography, declaration-property-value-no-unknown 10 | font: 12px $mono-font; 11 | color: var(--fgColor-muted, var(--color-fg-muted)); 12 | word-break: break-all; 13 | background-color: var(--bgColor-accent-muted, var(--color-accent-subtle)); 14 | // stylelint-disable-next-line primer/borders 15 | border-radius: $border-radius; 16 | 17 | .octicon { 18 | // stylelint-disable-next-line primer/spacing 19 | margin: 1px -2px 0 0; 20 | color: var(--fgColor-muted, var(--color-fg-muted)); 21 | } 22 | } 23 | 24 | // When a branch name is a link 25 | 26 | a.branch-name { 27 | color: var(--fgColor-accent, var(--color-accent-fg)); 28 | background-color: var(--bgColor-accent-muted, var(--color-accent-subtle)); 29 | 30 | .octicon { 31 | color: var(--fgColor-accent, var(--color-accent-fg)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale 2 | on: 3 | schedule: 4 | - cron: '0 * * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v10 11 | with: 12 | 13 | # General settings 14 | days-before-stale: 60 15 | days-before-close: 7 16 | enable-statistics: true 17 | operations-per-run: 100 18 | remove-stale-when-updated: true 19 | 20 | # PR specific settings 21 | delete-branch: true 22 | stale-pr-message: "Hi! This pull request has been marked as stale because it has been open with no activity for 60 days. You can comment on the pull request or remove the stale label to keep it open. If you do nothing, this pull request will be closed in 7 days." 23 | 24 | # Issue specific settings 25 | days-before-issue-stale: 180 26 | stale-issue-message: "Hi! This issue has been marked as stale because it has been open with no activity for 180 days. You can comment on the issue or remove the stale label to keep it open. If you do nothing, this issue will be closed in 7 days." 27 | 28 | -------------------------------------------------------------------------------- /docs/stories/static/spacing-image.svg: -------------------------------------------------------------------------------- 1 | 2 | Group 3 | Created using Figma 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /script/check-for-changeset: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | DEEPEN_LENGTH=${DEEPEN_LENGTH:-10} 4 | MAX_DEPTH=${MAX_DEPTH:-300} 5 | 6 | depth=0 7 | 8 | # Fetch the base ref, i.e. main 9 | git fetch --no-tags --progress --depth=1 origin "+refs/heads/$GITHUB_BASE_REF:refs/heads/$GITHUB_BASE_REF" 10 | 11 | # Keep fetching more commits until a merge base can be found 12 | while [ -z "$(git merge-base "$GITHUB_BASE_REF" "origin/$GITHUB_HEAD_REF")" ]; do 13 | git fetch --no-tags -q --deepen="$DEEPEN_LENGTH" origin "$GITHUB_BASE_REF" "$GITHUB_HEAD_REF" > /dev/null 14 | depth=$(( depth + $DEEPEN_LENGTH )) 15 | 16 | # Make sure we don't end up in an infinite loop 17 | if [[ "$depth" -ge "$MAX_DEPTH" ]]; then 18 | echo "Could not find merge base, max depth exceeded." 19 | exit 1 20 | fi 21 | done 22 | 23 | # Check for added .md files in the .changeset directory 24 | git diff --name-only origin/${GITHUB_BASE_REF}...origin/${GITHUB_HEAD_REF} | grep '.changeset/.*.md' > /dev/null || ( 25 | exit_code=$? 26 | echo "No changeset found. If these changes should not result in a new version, apply the ${SKIP_LABEL} label to this pull request. If these changes should result in a version bump, please add a changeset https://git.io/J6QvQ" 27 | exit "${exit_code}" 28 | ) 29 | -------------------------------------------------------------------------------- /docs/stories/utilities/Details.stories.jsx: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Utilities/Details' 3 | } 4 | 5 | export const Overlay = ({}) => ( 6 | 7 | More 8 | Hidden text 9 | 10 | ) 11 | 12 | export const OverlayDark = ({}) => ( 13 | 14 | More 15 | 16 | Hidden text 17 | 18 | 19 | ) 20 | 21 | export const Caret = ({}) => ( 22 | 23 | More 24 | Hidden text 25 | 26 | ) 27 | 28 | export const Summary = ({}) => ( 29 | 30 | 31 | More 32 | Hidden text 33 | 34 | 35 | 36 | More 37 | Hidden text 38 | 39 | 40 | ) 41 | -------------------------------------------------------------------------------- /src/avatars/circle-badge.scss: -------------------------------------------------------------------------------- 1 | // Circle badge icon with drop shadow for icons and logos 2 | 3 | .CircleBadge { 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | background-color: var(--bgColor-default, var(--color-canvas-default)); 8 | border-radius: 50%; 9 | box-shadow: var(--shadow-resting-medium, var(--color-shadow-medium)); 10 | } 11 | 12 | .CircleBadge-icon { 13 | max-width: 60% !important; 14 | height: auto !important; 15 | max-height: 55% !important; 16 | } 17 | 18 | // Small badge 19 | .CircleBadge--small { 20 | width: 56px; 21 | height: 56px; 22 | } 23 | 24 | // Medium badge 25 | .CircleBadge--medium { 26 | width: 96px; 27 | height: 96px; 28 | } 29 | 30 | // Large badge 31 | .CircleBadge--large { 32 | width: 128px; 33 | height: 128px; 34 | } 35 | 36 | // Dashed line that connects badges.. 37 | // Wrap around 2 or more badges to create a horizonal line: 38 | 39 | .DashedConnection { 40 | position: relative; 41 | 42 | &::before { 43 | position: absolute; 44 | top: 50%; 45 | left: 0; 46 | width: 100%; 47 | content: ''; 48 | // stylelint-disable-next-line primer/borders 49 | border-bottom: 2px dashed var(--borderColor-default, var(--color-border-default)); 50 | } 51 | 52 | .CircleBadge { 53 | position: relative; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/box/box-overlay.scss: -------------------------------------------------------------------------------- 1 | .Box--overlay { 2 | // stylelint-disable-next-line primer/responsive-widths 3 | width: 448px; 4 | margin-right: auto; 5 | margin-left: auto; 6 | background-color: var(--bgColor-default, var(--color-canvas-default)); 7 | background-clip: padding-box; 8 | border-color: var(--borderColor-default, var(--color-border-default)); 9 | // stylelint-disable-next-line primer/box-shadow 10 | box-shadow: 0 0 18px rgb(0, 0, 0, 0.4); 11 | 12 | .Box-header { 13 | margin: 0; 14 | border-width: 0; 15 | // stylelint-disable-next-line primer/borders 16 | border-bottom-width: $border-width; 17 | // stylelint-disable-next-line primer/borders 18 | border-top-left-radius: $border-radius; 19 | // stylelint-disable-next-line primer/borders 20 | border-top-right-radius: $border-radius; 21 | } 22 | } 23 | 24 | .Box-overlay--narrow { 25 | width: 320px; 26 | } 27 | 28 | .Box-overlay--wide { 29 | // stylelint-disable-next-line primer/responsive-widths 30 | width: 640px; 31 | } 32 | 33 | .Box-body { 34 | &.scrollable-overlay { 35 | max-height: 400px; 36 | overflow-y: scroll; 37 | } 38 | 39 | .help { 40 | padding-top: var(--base-size-8); 41 | margin: 0; 42 | color: var(--fgColor-muted, var(--color-fg-muted)); 43 | text-align: center; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /__tests__/utils/docs.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import frontMatter from 'front-matter' 3 | import yaml from 'js-yaml' 4 | import {globby} from 'globby' 5 | import { fileURLToPath } from 'url' 6 | import {join, dirname} from 'path' 7 | 8 | const __dirname = dirname(fileURLToPath(import.meta.url)) 9 | 10 | const docsPath = join(__dirname, '../../docs') 11 | 12 | function collectNavLinks(links) { 13 | let foundLinks = [] 14 | for (const link of links) { 15 | foundLinks.push({ 16 | title: link['title'], 17 | url: link['url'] 18 | }) 19 | if (link['children']) { 20 | foundLinks = foundLinks.concat(collectNavLinks(link['children'])) 21 | } 22 | } 23 | return foundLinks 24 | } 25 | 26 | export function getNavigationLinks() { 27 | const nav = yaml.load(fs.readFileSync(join(docsPath, './src/@primer/gatsby-theme-doctocat/nav.yml'), 'utf8')) 28 | return collectNavLinks(nav) 29 | } 30 | 31 | export async function getContentFrontmatter() { 32 | const fm = {} 33 | 34 | const paths = await globby(join(docsPath, './content/**/*.md*')) 35 | for (const path of paths) { 36 | const contents = fs.readFileSync(path, 'utf8') 37 | const fmContents = frontMatter(contents) 38 | fm[path.replace(join(docsPath, './content'), '').replace(/(\/index)?\.mdx?/, '')] = fmContents['attributes'] 39 | } 40 | return fm 41 | } 42 | -------------------------------------------------------------------------------- /src/marketing/utilities/animations.scss: -------------------------------------------------------------------------------- 1 | // Animation utilities for marketing 2 | 3 | .hover-grow-mktg { 4 | // stylelint-disable-next-line declaration-property-value-no-unknown 5 | transition: transform 0.4s $ease-mktg; 6 | 7 | &:hover { 8 | transform: scale3d(1.025, 1.025, 1.025); 9 | } 10 | } 11 | 12 | // Animated arrow symbol, used in marketing links, buttons, etc. 13 | .btn-mktg, 14 | .link-mktg, 15 | .arrow-target-mktg { 16 | .octicon { 17 | width: 1em; 18 | height: 1em; 19 | } 20 | 21 | .arrow-symbol-mktg { 22 | transition: transform 0.2s; 23 | transform: translateX(0); 24 | 25 | // stylelint-disable-next-line selector-max-type 26 | path:last-child { 27 | stroke-dasharray: 10; 28 | stroke-dashoffset: 10; 29 | transition: stroke-dashoffset 0.2s; 30 | } 31 | } 32 | 33 | @media screen and (prefers-reduced-motion: no-preference) { 34 | &:hover, 35 | &:focus { 36 | .arrow-symbol-mktg { 37 | transform: translateX(4px); 38 | 39 | // stylelint-disable-next-line selector-max-type, selector-max-specificity, max-nesting-depth 40 | path:last-child { 41 | stroke-dashoffset: 20; 42 | } 43 | } 44 | } 45 | 46 | &:active { 47 | .arrow-symbol-mktg { 48 | transform: translateX(6px); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/labels/mixins.scss: -------------------------------------------------------------------------------- 1 | // Label mixins 2 | 3 | // Default: 20px 4 | // Large: 24px 5 | // Inline: em based 6 | 7 | @mixin labels-base { 8 | display: inline-block; 9 | // stylelint-disable-next-line primer/spacing 10 | padding: 0 7px; 11 | // stylelint-disable-next-line primer/typography 12 | font-size: $font-size-small; 13 | // stylelint-disable-next-line primer/typography 14 | font-weight: $font-weight-semibold; 15 | // stylelint-disable-next-line primer/typography 16 | line-height: 18px; 17 | white-space: nowrap; 18 | // stylelint-disable-next-line primer/borders, primer/colors 19 | border: $border-width $border-style transparent; 20 | // stylelint-disable-next-line primer/borders 21 | border-radius: 2em; 22 | } 23 | 24 | @mixin labels-large { 25 | // stylelint-disable-next-line primer/spacing 26 | padding-right: 10px; 27 | // stylelint-disable-next-line primer/spacing 28 | padding-left: 10px; 29 | // stylelint-disable-next-line primer/typography 30 | line-height: 22px; 31 | } 32 | 33 | // Inline 34 | // 35 | // Doesn't increase height of parent element 36 | // Can be used with different font-sizes 37 | 38 | @mixin labels--inline { 39 | display: inline; 40 | // stylelint-disable-next-line primer/spacing 41 | padding: 0.12em $em-spacer-5; 42 | // stylelint-disable-next-line primer/typography 43 | font-size: 85%; 44 | } 45 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/SideNav/SideNav.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as SideNavStories from './SideNav.stories' 4 | 5 | 6 | 7 | # SideNav 8 | 9 | The Side Nav is a vertical list of navigational links, typically used on the left side of a page. For maximum flexibility, **Side Nav elements have no default width or positioning**. We suggest using [column grid](../utilities/grid) classes or an inline `width` style for sizing, and [flexbox utilities](../utilities/flexbox) for positioning alongside content. 10 | 11 | - You can use a **border** if the parent element doesn't have it already. 12 | - Add `aria-current="page"` to show a link as selected. Selected button elements in tab-like UIs should instead have `aria-selected="true"`. 13 | 14 | 15 | 16 | Different kind of content can be added inside a Side Nav item. Use utility classes to further style them if needed. 17 | 18 | 19 | 20 | The `.SideNav-subItem` is an alternative, more lightweight version without borders and more condensed. It can be used stand-alone. 21 | 22 | 23 | 24 | Or also appear nested, as a sub navigation. Use margin/padding utility classes to add indentation. 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/.storybook/preview.css: -------------------------------------------------------------------------------- 1 | @import '@primer/primitives/dist/css/base/size/size.css'; 2 | @import '@primer/primitives/dist/css/base/typography/typography.css'; 3 | @import '@primer/primitives/dist/css/functional/size/border.css'; 4 | @import '@primer/primitives/dist/css/functional/size/breakpoints.css'; 5 | @import '@primer/primitives/dist/css/functional/size/size-coarse.css'; 6 | @import '@primer/primitives/dist/css/functional/size/size-fine.css'; 7 | @import '@primer/primitives/dist/css/functional/size/size.css'; 8 | @import '@primer/primitives/dist/css/functional/size/viewport.css'; 9 | @import '@primer/primitives/dist/css/functional/typography/typography.css'; 10 | 11 | /* color */ 12 | @import '@primer/primitives/dist/css/functional/themes/light.css'; 13 | @import '@primer/primitives/dist/css/functional/themes/light-tritanopia.css'; 14 | @import '@primer/primitives/dist/css/functional/themes/light-high-contrast.css'; 15 | @import '@primer/primitives/dist/css/functional/themes/light-colorblind.css'; 16 | @import '@primer/primitives/dist/css/functional/themes/dark.css'; 17 | @import '@primer/primitives/dist/css/functional/themes/dark-colorblind.css'; 18 | @import '@primer/primitives/dist/css/functional/themes/dark-dimmed.css'; 19 | @import '@primer/primitives/dist/css/functional/themes/dark-high-contrast.css'; 20 | @import '@primer/primitives/dist/css/functional/themes/dark-tritanopia.css'; 21 | -------------------------------------------------------------------------------- /src/markdown/tables.scss: -------------------------------------------------------------------------------- 1 | // Needs refactoring 2 | // stylelint-disable selector-max-type, selector-max-compound-selectors 3 | .markdown-body { 4 | // Tables 5 | table { 6 | display: block; 7 | width: 100%; // keep for backwards compatibility 8 | width: max-content; 9 | max-width: 100%; 10 | overflow: auto; 11 | font-variant: tabular-nums; 12 | 13 | th { 14 | // stylelint-disable-next-line primer/typography 15 | font-weight: $font-weight-bold; 16 | } 17 | 18 | th, 19 | td { 20 | // stylelint-disable-next-line primer/spacing 21 | padding: 6px 13px; 22 | // stylelint-disable-next-line primer/borders, primer/colors 23 | border: $border-width $border-style var(--borderColor-default, var(--color-border-default)); 24 | } 25 | 26 | td { 27 | > :last-child { 28 | margin-bottom: 0; 29 | } 30 | } 31 | 32 | tr { 33 | background-color: var(--bgColor-default, var(--color-canvas-default)); 34 | // stylelint-disable-next-line primer/borders, primer/colors 35 | border-top: $border-width $border-style var(--borderColor-muted, var(--color-border-muted)); 36 | 37 | &:nth-child(2n) { 38 | background-color: var(--bgColor-muted, var(--color-canvas-subtle)); 39 | } 40 | } 41 | 42 | img { 43 | background-color: transparent; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/BranchName/BranchName.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {BADGE} from '@geometricpanda/storybook-addon-badges' 3 | 4 | export default { 5 | title: 'Deprecated/BranchName', 6 | parameters: { 7 | storyType: 'banner', 8 | controls: {hideNoControlsWarning: true}, 9 | badges: [BADGE.DEPRECATED], 10 | }, 11 | } 12 | 13 | export const Default = () => { 14 | return ( 15 | 16 | a_new_feature_branch 17 | 18 | a_new_feature_branch 19 | 20 | 21 | 28 | 32 | 33 | a_new_feature_branch 34 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /docs/stories/static/color-image.svg: -------------------------------------------------------------------------------- 1 | 2 | Group 3 | Created using Figma 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/Pagination/Pagination.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as PaginationStories from './Pagination.stories' 4 | 5 | 6 | 7 | # Pagination 8 | 9 | Use the pagination component to apply button styles to a connected set of links that go to related pages (for example, previous, next, or page numbers). 10 | 11 | ## Previous/next pagination 12 | 13 | You can make a very simple pagination container with just the Previous and Next buttons. Add the `aria-disabled="true"` attribute to the `Previous` button if there isn't a preceding page, or to the `Next` button if there isn't a succeeding page. 14 | 15 | 16 | 17 | ## Numbered pagination 18 | 19 | For pagination across multiple pages, make sure it's clear to the user where they are in a set of pages. 20 | 21 | To do this, add anchor links to the `pagination` element. Previous and Next buttons should always be present. Add the `aria-disabled="true"` attribute to the Previous button if you're on the first page. Apply the `aria-current="page"` attribute to the current numbered page. 22 | 23 | As always, make sure to include the appropriate `aria` attributes to make the element accessible. 24 | 25 | - Add `aria-label="Pagination"` to the `paginate-container` element. 26 | - Add `aria-label="Page X"` to each anchor link. 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/stories/static/components-image.svg: -------------------------------------------------------------------------------- 1 | styles 2 | -------------------------------------------------------------------------------- /docs/stories/static/components-image copy.svg: -------------------------------------------------------------------------------- 1 | styles 2 | -------------------------------------------------------------------------------- /src/support/mixins/layout.scss: -------------------------------------------------------------------------------- 1 | // Responsive media queries 2 | 3 | @mixin breakpoint($breakpoint) { 4 | @if $breakpoint == '' { 5 | @content; 6 | } 7 | 8 | @else { 9 | // Retrieves the value from the key 10 | $value: map-get($breakpoints, $breakpoint); 11 | 12 | // If the key exists in the map 13 | @if $value != null { 14 | // Prints a media query based on the value 15 | @media (min-width: $value) { 16 | @content; 17 | } 18 | } 19 | 20 | // If the key doesn't exist in the map 21 | @else { 22 | @warn 'Unfortunately, no value could be retrieved from `#{$breakpoint}`. ' 23 | + 'Please make sure it is defined in `$breakpoints` map.'; 24 | } 25 | } 26 | } 27 | 28 | // Retina media query 29 | 30 | @mixin retina-media-query { 31 | @media 32 | only screen and (-webkit-min-device-pixel-ratio: 2), 33 | only screen and (min--moz-device-pixel-ratio: 2), 34 | only screen and (-moz-min-device-pixel-ratio: 2), 35 | only screen and (-o-min-device-pixel-ratio: 2/1), 36 | only screen and (min-device-pixel-ratio: 2), 37 | only screen and (min-resolution: 192dpi), 38 | only screen and (min-resolution: 2dppx) { 39 | @content; 40 | } 41 | } 42 | 43 | // Clearfix 44 | // 45 | // Clears floats via mixin. 46 | 47 | @mixin clearfix { 48 | &::before { 49 | display: table; 50 | content: ''; 51 | } 52 | 53 | &::after { 54 | display: table; 55 | clear: both; 56 | content: ''; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/IssueLabel/IssueLabel.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {BADGE} from '@geometricpanda/storybook-addon-badges' 3 | 4 | export default { 5 | title: 'Deprecated/IssueLabel', 6 | parameters: { 7 | storyType: 'banner', 8 | controls: {hideNoControlsWarning: true}, 9 | badges: [BADGE.DEPRECATED], 10 | }, 11 | } 12 | 13 | export const Default = () => { 14 | return ( 15 | <> 16 | Primer 17 | bug 🐛 18 | help wanted 19 | 🚂 deploy: train 20 | > 21 | ) 22 | } 23 | 24 | export const Big = () => { 25 | return ( 26 | <> 27 | Primer 28 | bug 🐛 29 | 30 | help wanted 31 | 32 | 33 | 🚂 deploy: train 34 | 35 | > 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /__tests__/build.test.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | 3 | let distDir 4 | 5 | beforeAll(() => { 6 | distDir = fs.readdirSync('./dist') 7 | }) 8 | 9 | describe('./dist/ folder', () => { 10 | let distCSS, distMap, distJS 11 | 12 | beforeAll(() => { 13 | distCSS = distDir.filter(i => i.match(/\.css$/)) 14 | distMap = distDir.filter(i => i.match(/\.map$/)) 15 | distJS = distDir.filter(i => i.match(/\.js$/)) 16 | }) 17 | 18 | it('is not empty', () => { 19 | expect(distDir).not.toBeNull() 20 | expect(distDir.length).not.toBe(0) 21 | }) 22 | 23 | it('contains source maps', () => { 24 | for (const file of distCSS) { 25 | if (file.includes('support')) { continue } 26 | expect(distMap).toContain(`${file}.map`) 27 | } 28 | }) 29 | 30 | it('contains stats export files', () => { 31 | for (const file of distCSS) { 32 | expect(distJS).toContain(file.replace('.css', '.js')) 33 | } 34 | }) 35 | 36 | it('contains stats/ folder', () => { 37 | expect(distDir).toContain('stats') 38 | }) 39 | }) 40 | 41 | describe('./dist/stats/ folder', () => { 42 | let statsDir 43 | 44 | beforeAll(() => { 45 | statsDir = fs.readdirSync('./dist/stats') 46 | }) 47 | 48 | it('is not empty', () => { 49 | expect(statsDir).not.toBeNull() 50 | expect(statsDir.length).not.toBe(0) 51 | }) 52 | 53 | it('contains a css file for each stat file', () => { 54 | for (const file of statsDir) { 55 | expect(distDir).toContain(file.replace('.json', '.css')) 56 | } 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | stylelint: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v5 12 | - uses: actions/setup-node@v6 13 | with: 14 | node-version: 20 15 | cache: 'npm' 16 | - run: npm ci 17 | - run: npm run dist 18 | - name: Lint source files 19 | run: npm run stylelint:fix 20 | - name: Push up any fixes 21 | if: ${{ github.event_name == 'pull_request' }} 22 | uses: stefanzweifel/git-auto-commit-action@v7 23 | with: 24 | commit_message: Fixing stylelint issues 25 | commit_user_name: GitHub Design Engineering Bot 26 | commit_user_email: primer-css@users.noreply.github.com 27 | commit_author: primer-css 28 | file_pattern: src/**/*.scss 29 | 30 | eslint: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v5 34 | - uses: actions/setup-node@v6 35 | with: 36 | node-version: 20 37 | cache: 'npm' 38 | - run: npm ci 39 | - name: Lint workflow files 40 | run: npm run eslint 41 | 42 | test: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v5 46 | - uses: actions/setup-node@v6 47 | with: 48 | node-version: 20 49 | cache: 'npm' 50 | - run: npm ci 51 | - name: Jest 52 | run: npm test 53 | -------------------------------------------------------------------------------- /docs/stories/support/Prototyping.mdx: -------------------------------------------------------------------------------- 1 | # Prototyping 2 | 3 | You're welcome to use whatever prototyping tool suits your needs, however we've set up some options that will help you get started quickly. 4 | 5 | The power of prototyping in code is that you can create clickable mocks that can be shared via a URL. This can be useful for exploring designs and interactions or for user research sessions. Prototypes can be throw-away, or part of your process for building out new features since you can work with the same CSS we use in production. 6 | 7 | ## Simple HTML prototype with Primer CSS 8 | 9 | Copy the code below and paste it in a HTML file. The CDN link is always linked to the most up to date version of Primer CSS and includes all of the modules in the core, product, and marketing packages. 10 | 11 | This method requires no dev environment set up and is useful for when you want to create simple prototypes using Primer CSS. 12 | 13 | ```html 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ``` 24 | 25 | Note that the above loads a specific version of Primer CSS. You can also load the "latest" version with `https://unpkg.com/@primer/css/dist/primer.css`. Just be aware that it could include breaking changes. So for prototypes meant to last for a long time it's better to lock Primer CSS to a specific version. 26 | -------------------------------------------------------------------------------- /.github/workflows/deploy_production.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | pages: write 11 | id-token: write 12 | deployments: write 13 | issues: write 14 | statuses: write 15 | checks: write 16 | 17 | jobs: 18 | guard: 19 | name: Guard 20 | runs-on: ubuntu-latest 21 | outputs: 22 | # To avoid deploying documentation for unrelease changes, we check the number of changeset files. 23 | # If it's 0, we deploy. 24 | should_deploy: ${{ steps.changeset-count.outputs.change_count == 0 }} 25 | steps: 26 | - uses: actions/checkout@v5 27 | 28 | - id: changeset-count 29 | run: echo "::set-output name=change_count::$(ls .changeset/*.md | grep -v README | wc -l | xargs)" 30 | 31 | # Log changeset count for debugging purposes 32 | - name: Log changeset count 33 | run: echo ${{ steps.changeset-count.outputs.change_count }} 34 | 35 | # Log guard output for debugging purposes 36 | - name: Log guard output 37 | run: echo ${{ needs.guard.outputs.should_deploy }} 38 | deploy: 39 | if: ${{ github.repository == 'primer/css' }} 40 | name: Production 41 | needs: [guard] 42 | uses: primer/.github/.github/workflows/deploy.yml@v2.4.0 43 | secrets: 44 | gh_token: ${{ secrets.GITHUB_TOKEN }} 45 | with: 46 | node_version: 20 47 | install: npm i && cd docs && npm i && cd .. 48 | build: npm run build:docs 49 | output_dir: docs/public 50 | -------------------------------------------------------------------------------- /docs/.storybook/storybook.css: -------------------------------------------------------------------------------- 1 | .story-wrap { 2 | font-family: var(--fontStack-system); 3 | color: var(--fgColor-default); 4 | } 5 | 6 | #storybook-preview-wrapper { 7 | background-color: var(--bgColor-default) !important; 8 | width: 100% !important; 9 | height: 100% !important; 10 | } 11 | 12 | .theme-wrap-grid { 13 | display: grid; 14 | grid-template-columns: repeat(4, minmax(var(--breakpoint-xsmall, 20rem), auto)); 15 | grid-gap: 1px; 16 | height: 100vh; 17 | } 18 | 19 | .story-wrap-grid { 20 | outline: 1px solid #d4d4d8; 21 | padding-bottom: 40px; 22 | position: relative; 23 | } 24 | 25 | @media (max-width: calc(20rem * 4)) { 26 | .theme-wrap-grid { 27 | grid-template-columns: repeat(3, minmax(var(--breakpoint-xsmall, 20rem), auto)); 28 | } 29 | } 30 | 31 | @media (max-width: calc(20rem * 3)) { 32 | .theme-wrap-grid { 33 | grid-template-columns: repeat(2, minmax(var(--breakpoint-xsmall, 20rem), auto)); 34 | } 35 | } 36 | 37 | @media (max-width: calc(20rem * 2)) { 38 | .theme-wrap-grid { 39 | display: block; 40 | } 41 | } 42 | 43 | .theme-name { 44 | position: absolute; 45 | bottom: 0; 46 | right: 0; 47 | background-color: var(--bgColor-muted); 48 | padding: var(--base-size-4) var(--base-size-8); 49 | font: var(--text-caption-shorthand); 50 | margin: 0; 51 | } 52 | 53 | code { 54 | padding: 0.2em 0.4em; 55 | font-family: var(--fontStack-monospace); 56 | font-size: var(--text-codeInline-size); 57 | background-color: var(--bgColor-muted); 58 | border-radius: var(--borderRadius-small); 59 | font-weight: var(--base-text-weight-normal); 60 | } 61 | -------------------------------------------------------------------------------- /src/header/header.scss: -------------------------------------------------------------------------------- 1 | .Header { 2 | z-index: 32; // TODO: Figure out z-index system 3 | display: flex; 4 | padding: var(--base-size-16); 5 | // stylelint-disable-next-line primer/typography 6 | font-size: $h5-size; 7 | // stylelint-disable-next-line primer/typography 8 | line-height: $lh-default; 9 | color: var(--header-fgColor-default, var(--color-header-text)); 10 | background-color: var(--header-bgColor, var(--color-header-bg)); 11 | align-items: center; 12 | flex-wrap: nowrap; 13 | } 14 | 15 | .Header-item { 16 | display: flex; 17 | margin-right: var(--base-size-16); 18 | align-self: stretch; 19 | align-items: center; 20 | flex-wrap: nowrap; 21 | } 22 | 23 | .Header-item--full { 24 | flex: auto; 25 | } 26 | 27 | .Header-link { 28 | // stylelint-disable-next-line primer/typography 29 | font-weight: $font-weight-bold; 30 | color: var(--header-fgColor-logo, var(--color-header-logo)); 31 | white-space: nowrap; 32 | 33 | &:hover, 34 | &:focus { 35 | color: var(--header-fgColor-default, var(--color-header-text)); 36 | text-decoration: none; 37 | } 38 | } 39 | 40 | .Header-input { 41 | color: var(--header-fgColor-default, var(--color-header-text)); 42 | background-color: var(--headerSearch-bgColor, var(--color-header-search-bg)); 43 | // stylelint-disable-next-line primer/borders, primer/colors 44 | border: $border-width $border-style var(--headerSearch-borderColor, var(--color-header-search-border)); 45 | box-shadow: none; 46 | 47 | &::placeholder { 48 | // stylelint-disable-next-line primer/colors 49 | color: rgb(255, 255, 255, 0.75); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/axe.yml: -------------------------------------------------------------------------------- 1 | name: axe 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | axe: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v5 14 | with: 15 | fetch-depth: 0 16 | - name: Get changed files 17 | id: changed-files 18 | uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 19 | with: 20 | files: | 21 | docs/content/components/**/*.md 22 | docs/content/utilities/**/*.md 23 | files_ignore: | 24 | docs/content/components/index.md 25 | docs/content/utilities/index.md 26 | - name: Save changed files 27 | run: | 28 | echo "STRING_OF_PATHS_WE_CARE_ABOUT=${{ steps.changed-files.outputs.all_changed_files }}" >> $GITHUB_ENV 29 | - name: Use Node.js 30 | if: steps.changed-files.outputs.any_changed == 'true' 31 | uses: actions/setup-node@v6 32 | with: 33 | node-version: 20 34 | cache: 'npm' 35 | - run: npm ci 36 | if: steps.changed-files.outputs.any_changed == 'true' 37 | - run: npm run dist 38 | if: steps.changed-files.outputs.any_changed == 'true' 39 | - name: Run docs site 40 | if: steps.changed-files.outputs.any_changed == 'true' 41 | run: | 42 | npm run dev & npx wait-on http://localhost:8000 43 | - name: Run axe script 44 | if: steps.changed-files.outputs.any_changed == 'true' 45 | run: | 46 | script/axe-docs $STRING_OF_PATHS_WE_CARE_ABOUT 47 | -------------------------------------------------------------------------------- /docs/stories/helpers/pageLayoutBehavior.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | export default function PageLayoutBehavior() { 3 | 4 | const pageLayoutSelector = '.PageLayout.PageLayout--responsive-separateRegions'; 5 | const primaryRegionSelector = 'PageLayout--responsive-primary'; 6 | 7 | const detectPageLayoutHash = () => { 8 | 9 | const pageLayout = document.querySelector(pageLayoutSelector); 10 | 11 | let dest; 12 | if (location.hash === '') { 13 | dest = pageLayout.getAttribute('data-primary-region'); 14 | } else if (location.hash === '#pane') { 15 | dest = 'pane'; 16 | } else if (location.hash === '#content') { 17 | dest = 'content'; 18 | } else { 19 | return; 20 | } 21 | 22 | pageLayout.setAttribute('data-current-region', dest); 23 | 24 | if (dest === 'pane') { 25 | pageLayout.classList.replace(primaryRegionSelector + '-content', primaryRegionSelector + '-pane'); 26 | } else { 27 | pageLayout.classList.replace(primaryRegionSelector + '-pane', primaryRegionSelector + '-content'); 28 | } 29 | }; 30 | 31 | window.addEventListener("hashchange", () => { 32 | detectPageLayoutHash(); 33 | }); 34 | 35 | document.addEventListener('DOMContentLoaded', (event) => { 36 | const pageLayout = document.querySelector(pageLayoutSelector); 37 | const primaryRegion = pageLayout.classList.contains(primaryRegionSelector + '-pane') ? 'pane' : 'content'; 38 | 39 | if (pageLayout.getAttribute('data-primary-region') === null) { 40 | pageLayout.setAttribute('data-primary-region', primaryRegion); 41 | } 42 | 43 | detectPageLayoutHash(); 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /src/support/variables/typography.scss: -------------------------------------------------------------------------------- 1 | // Typography variables 2 | 3 | // Heading sizes - mobile 4 | // h4-h6 remain the same size on both mobile & desktop 5 | $h00-size-mobile: 40px !default; 6 | $h0-size-mobile: 32px !default; 7 | $h1-size-mobile: 26px !default; 8 | $h2-size-mobile: 22px !default; 9 | $h3-size-mobile: 18px !default; 10 | 11 | // Heading sizes - desktop 12 | $h00-size: 48px !default; 13 | $h0-size: 40px !default; 14 | $h1-size: 32px !default; 15 | $h2-size: 24px !default; 16 | $h3-size: 20px !default; 17 | $h4-size: 16px !default; 18 | $h5-size: 14px !default; 19 | $h6-size: 12px !default; 20 | 21 | $font-size-small: 12px !default; 22 | 23 | // Font weights 24 | $font-weight-bold: var(--base-text-weight-semibold, 600) !default; 25 | $font-weight-semibold: var(--base-text-weight-medium, 500) !default; 26 | $font-weight-normal: var(--base-text-weight-normal, 400) !default; 27 | $font-weight-light: var(--base-text-weight-light, 300) !default; 28 | 29 | // Line heights 30 | $lh-condensed-ultra: 1 !default; 31 | $lh-condensed: 1.25 !default; 32 | $lh-default: 1.5 !default; 33 | 34 | // Font stacks 35 | $body-font: var(--fontStack-sansSerif, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji') !default; 36 | 37 | // Monospace font stack 38 | // Note: SFMono-Regular needs to come before SF Mono to fix an older version of the font in Chrome 39 | $mono-font: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace) !default; 40 | 41 | // The base body size 42 | $body-font-size: 14px !default; 43 | $body-line-height: $lh-default !default; 44 | -------------------------------------------------------------------------------- /docs/.storybook/theme.js: -------------------------------------------------------------------------------- 1 | import {create} from '@storybook/theming' 2 | import packageJson from '../../package.json' 3 | 4 | export default create({ 5 | brandTitle: ` 6 | 7 | 8 | 9 | 10 | ${packageJson.name}@${packageJson.version} 11 | 12 | `, 13 | }) 14 | -------------------------------------------------------------------------------- /src/markdown/footnotes.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable selector-max-type 2 | // stylelint-disable selector-max-compound-selectors 3 | 4 | .markdown-body { 5 | [data-footnote-ref] { 6 | &::before { 7 | content: '['; 8 | } 9 | 10 | &::after { 11 | content: ']'; 12 | } 13 | } 14 | 15 | .footnotes { 16 | // stylelint-disable-next-line primer/typography 17 | font-size: $h6-size; 18 | color: var(--fgColor-muted, var(--color-fg-muted)); 19 | // stylelint-disable-next-line primer/borders, primer/colors 20 | border-top: $border; 21 | 22 | ol { 23 | padding-left: var(--base-size-16); 24 | 25 | ul { 26 | display: inline-block; 27 | padding-left: var(--base-size-16); 28 | margin-top: var(--base-size-16); 29 | } 30 | } 31 | 32 | li { 33 | position: relative; 34 | } 35 | 36 | li:target::before { 37 | position: absolute; 38 | top: calc(var(--base-size-8) * -1); 39 | right: calc(var(--base-size-8) * -1); 40 | bottom: calc(var(--base-size-8) * -1); 41 | left: calc(var(--base-size-24) * -1); 42 | pointer-events: none; 43 | content: ''; 44 | // stylelint-disable-next-line primer/borders, primer/colors, declaration-property-value-no-unknown 45 | border: 2px $border-style var(--borderColor-accent-emphasis, var(--color-accent-emphasis)); 46 | // stylelint-disable-next-line primer/borders 47 | border-radius: $border-radius; 48 | } 49 | 50 | li:target { 51 | color: var(--fgColor-default, var(--color-fg-default)); 52 | } 53 | 54 | .data-footnote-backref g-emoji { 55 | // stylelint-disable-next-line primer/typography 56 | font-family: monospace; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/Pagination/Pagination.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {BADGE} from '@geometricpanda/storybook-addon-badges' 3 | 4 | export default { 5 | title: 'Deprecated/Pagination', 6 | parameters: { 7 | storyType: 'banner', 8 | controls: {hideNoControlsWarning: true}, 9 | badges: [BADGE.DEPRECATED], 10 | }, 11 | } 12 | 13 | export const Default = () => { 14 | return ( 15 | <> 16 | 17 | 18 | 19 | Previous 20 | 21 | 22 | Next 23 | 24 | 25 | 26 | > 27 | ) 28 | } 29 | 30 | export const Numbered = () => { 31 | return ( 32 | 33 | 34 | 35 | Previous 36 | 37 | 1 38 | 39 | 2 40 | 41 | 42 | 3 43 | 44 | … 45 | 46 | 8 47 | 48 | 49 | 9 50 | 51 | 52 | 10 53 | 54 | 55 | Next 56 | 57 | 58 | 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/Toast/Toast.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as ToastStories from './Toast.stories' 4 | 5 | 6 | 7 | # Toast 8 | 9 | Toasts are used to show live, time-sensitive feedback to users. 10 | 11 | ## Default 12 | 13 | To create a default toast, use `.Toast`. Always use the `info` icon for default messages. 14 | 15 | 16 | 17 | The Toast content is formattable. We recommend keeping your message under 140 characters. 18 | 19 | ## Variations 20 | 21 | Use the success, warning, and error modifiers to communicate different states. 22 | 23 | Always use the `check` octicon for success states. 24 | 25 | 26 | 27 | Always use the `alert` octicon for warning states. 28 | 29 | 30 | 31 | Always use the `stop` octicon for error states. 32 | 33 | 34 | 35 | ## Toast with dismiss 36 | 37 | Use `.Toast-dismissButton` to allow a user to explicitly dismiss a Toast. 38 | 39 | 40 | 41 | ## Toast with link 42 | 43 | Include a link to allow users to take actions within a Toast. 44 | 45 | 46 | 47 | ## Toast animation in 48 | 49 | The `Toast--animateIn` and `Toast--animateOut` modifier classes can be used to animate the toast in and out from the bottom. 50 | 51 | 52 | 53 | ## Toast with loading animation 54 | 55 | Add the `Toast--spinner` modifier class on the `Toast-icon` `svg` to communicate a loading state. 56 | 57 | 58 | 59 | ## Toast position 60 | 61 | Use the `position-fixed bottom-0 left-0` utility classes on a wrapper element to position Toasts at the **bottom left** of the viewport. 62 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/Header/Header.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as HeaderStories from './Header.stories' 4 | 5 | 6 | 7 | # Header 8 | 9 | Use the Header component to create a header that has all of its items aligned vertically with consistent horizontal spacing. 10 | 11 | ## Header 12 | 13 | The `.Header` class is the wrapping class that aligns all the items properly and gives the header its dark background. Each direct child of the `.Header` component is expected to be a `.Header-item` component. The component utilizes flexbox CSS to align all these items properly and applies spacing scale margin. 14 | 15 | 16 | 17 | ## Header-item 18 | 19 | All items directly under the `.Header` component should be a `.Header-item` component. Inside these components can be anything (text, forms, images...), and the `.Header-item` component will make sure these elements vertically align with each other. 20 | 21 | `.Header-item` elements have a built-in margin that will need to be overridden with the `mr-0` utility class for the last element in the container. We relied on the utility classes here instead of `:last-child` because the last child isn't always the item visible. On responsive pages, there's a mobile menu that gets presented to the user at smaller breakpoints. 22 | 23 | 24 | 25 | ### Header-item--full 26 | 27 | The `.Header-item` element has a modifier class, `.Header-item--full`, that stretches it to fill the available space and push any remaining items to the right. 28 | 29 | 30 | 31 | ## Header-link 32 | 33 | Add the `.Header-link` class to any anchor tags in the header to give them consistent styling and hover opacity. This class makes the links white, bold and 70% fade on hover. 34 | 35 | 36 | -------------------------------------------------------------------------------- /.github/workflows/deploy_preview.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | pull_request: 4 | 5 | permissions: 6 | contents: write 7 | pages: write 8 | id-token: write 9 | deployments: write 10 | issues: write 11 | statuses: write 12 | checks: write 13 | 14 | jobs: 15 | deploy: 16 | if: ${{ github.repository == 'primer/css' }} 17 | uses: primer/.github/.github/workflows/deploy_preview.yml@v2.4.0 18 | name: Deploy preview 19 | secrets: 20 | gh_token: ${{ secrets.GITHUB_TOKEN }} 21 | with: 22 | node_version: 20 23 | install: npm i && cd docs && npm i && cd .. 24 | build: npm run build:docs:preview 25 | output_dir: docs/public 26 | 27 | deploy-storybook: 28 | if: ${{ github.repository == 'primer/css' }} 29 | name: Preview Storybook 30 | runs-on: ubuntu-latest 31 | needs: deploy 32 | steps: 33 | - uses: actions/checkout@v5 34 | 35 | - uses: chrnorm/deployment-action@v2.0.7 36 | name: Create GitHub deployment for storybook 37 | id: storybook 38 | with: 39 | token: ${{ secrets.GITHUB_TOKEN }} 40 | environment: Storybook Preview 41 | environment_url: '${{ needs.deploy.outputs.deployment_url }}/storybook' 42 | 43 | - name: Update storybook deployment status (success) 44 | if: success() 45 | uses: chrnorm/deployment-status@v2.0.3 46 | with: 47 | token: ${{ secrets.GITHUB_TOKEN }} 48 | environment-url: '${{ needs.deploy.outputs.deployment_url }}/storybook' 49 | state: 'success' 50 | deployment-id: ${{ steps.storybook.outputs.deployment_id }} 51 | 52 | - name: Update storybook deployment status (failure) 53 | if: failure() 54 | uses: chrnorm/deployment-status@v2.0.3 55 | with: 56 | token: ${{ secrets.GITHUB_TOKEN }} 57 | state: 'failure' 58 | deployment-id: ${{ steps.storybook.outputs.deployment_id }} 59 | -------------------------------------------------------------------------------- /src/base/typography-base.scss: -------------------------------------------------------------------------------- 1 | // Headings 2 | // -------------------------------------------------- 3 | // stylelint-disable selector-max-type 4 | h1, 5 | h2, 6 | h3, 7 | h4, 8 | h5, 9 | h6 { 10 | margin-top: 0; 11 | margin-bottom: 0; 12 | } 13 | 14 | h1 { @include h1; } 15 | h2 { @include h2; } 16 | h3 { @include h3; } 17 | h4 { @include h4; } 18 | h5 { @include h5; } 19 | h6 { @include h6; } 20 | 21 | // Body text 22 | // -------------------------------------------------- 23 | 24 | p { 25 | margin-top: 0; 26 | // stylelint-disable-next-line primer/spacing 27 | margin-bottom: 10px; 28 | } 29 | 30 | small { 31 | // stylelint-disable-next-line primer/typography 32 | font-size: 90%; 33 | } 34 | 35 | blockquote { 36 | margin: 0; 37 | } 38 | 39 | // Lists 40 | // -------------------------------------------------- 41 | 42 | ul, 43 | ol { 44 | padding-left: 0; 45 | margin-top: 0; 46 | margin-bottom: 0; 47 | } 48 | 49 | ol ol, 50 | ul ol { 51 | list-style-type: lower-roman; 52 | } 53 | 54 | ul ul ol, 55 | ul ol ol, 56 | ol ul ol, 57 | ol ol ol { 58 | list-style-type: lower-alpha; 59 | } 60 | 61 | dd { 62 | margin-left: 0; 63 | } 64 | 65 | // Monospaced 66 | // -------------------------------------------------- 67 | 68 | tt, 69 | code, 70 | samp { 71 | // stylelint-disable-next-line primer/typography 72 | font-family: $mono-font; 73 | // stylelint-disable-next-line primer/typography 74 | font-size: $font-size-small; 75 | } 76 | 77 | pre { 78 | margin-top: 0; 79 | margin-bottom: 0; 80 | // stylelint-disable-next-line primer/typography 81 | font-family: $mono-font; 82 | // stylelint-disable-next-line primer/typography 83 | font-size: $font-size-small; 84 | } 85 | 86 | // Octicons 87 | // -------------------------------------------------- 88 | 89 | // Move this over here as a temporary override to the octicons source repo 90 | // instead of updating that upstream. 91 | .octicon { 92 | vertical-align: text-bottom; 93 | } 94 | -------------------------------------------------------------------------------- /src/utilities/padding.scss: -------------------------------------------------------------------------------- 1 | // Padding spacer utilities 2 | // stylelint-disable primer/spacing 3 | 4 | // Responsive padding spacer utilities 5 | @each $breakpoint, $variant in $responsive-variants { 6 | @include breakpoint($breakpoint) { 7 | // Loop through the spacer values 8 | @each $scale, $size in $spacer-map-rem-extended { 9 | @if ($scale < length($spacer-map-rem)) { 10 | /* Set a $size padding to all sides at $breakpoint */ 11 | .p#{$variant}-#{$scale} { padding: $size !important; } 12 | } 13 | 14 | /* Set a $size padding to the top at $breakpoint */ 15 | .pt#{$variant}-#{$scale} { padding-top: $size !important; } 16 | /* Set a $size padding to the right at $breakpoint */ 17 | .pr#{$variant}-#{$scale} { padding-right: $size !important; } 18 | /* Set a $size padding to the bottom at $breakpoint */ 19 | .pb#{$variant}-#{$scale} { padding-bottom: $size !important; } 20 | /* Set a $size padding to the left at $breakpoint */ 21 | .pl#{$variant}-#{$scale} { padding-left: $size !important; } 22 | 23 | @if ($scale < length($spacer-map-rem)) { 24 | /* Set a $size padding to the left & right at $breakpoint */ 25 | .px#{$variant}-#{$scale} { 26 | padding-right: $size !important; 27 | padding-left: $size !important; 28 | } 29 | } 30 | 31 | /* Set a $size padding to the top & bottom at $breakpoint */ 32 | .py#{$variant}-#{$scale} { 33 | padding-top: $size !important; 34 | padding-bottom: $size !important; 35 | } 36 | } 37 | } 38 | } 39 | 40 | // responsive padding for containers 41 | .p-responsive { 42 | padding-right: var(--base-size-16) !important; 43 | padding-left: var(--base-size-16) !important; 44 | 45 | @include breakpoint(sm) { 46 | padding-right: var(--base-size-40) !important; 47 | padding-left: var(--base-size-40) !important; 48 | } 49 | 50 | @include breakpoint(lg) { 51 | padding-right: var(--base-size-16) !important; 52 | padding-left: var(--base-size-16) !important; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/stories/support/Deprecations.mdx: -------------------------------------------------------------------------------- 1 | # Deprecations 2 | 3 | As of version 12.7.0, we publish CSS selector and SCSS variable deprecation data (as of 14.0.0) with `@primer/css`. You can access the data via the [Node API](#node) or as [JSON](#json). 4 | 5 | To deprecate a class, variable, or mixin, add the element to the [deprecations.js](https://github.com/primer/css/blob/main/deprecations.js) file with it's replacement value. 6 | 7 | The replacement can be: 8 | 9 | - A `String` for a direct replacement. 10 | - An `Array` for multiple replacement options. 11 | - `null` to indicate there is no replacement. 12 | 13 | This could look something like this: 14 | 15 | ```js 16 | const deprecations = { 17 | 'deprecated-1': 'replacement', 18 | 'deprecated-2': ['replacement-1', 'replacement-2'], 19 | 'deprecated-3': null, 20 | } 21 | ``` 22 | 23 | ## JSON 24 | 25 | The JSON data is available in the unpacked node module's `dist/deprecations.json`, and is an object with the following structure: 26 | 27 | ```json 28 | { 29 | "selectors" {...}, 30 | "variables": {...}, 31 | "mixins": {...} 32 | } 33 | ``` 34 | 35 | `selectors` is an object mapping CSS selectors to their replacements. If the replacement is an Array, then there's multiple options. If the replacement is null then there are no replacements. 36 | 37 | ```json 38 | { 39 | "selectors": { 40 | "deprecated-class": "replacement-class" 41 | } 42 | } 43 | ``` 44 | 45 | `variables` is an object mapping SCSS variables to their replacement SCSS variable. 46 | 47 | ```json 48 | { 49 | "variables": { 50 | "$deprecated-variable": "$replacement-variable" 51 | } 52 | } 53 | ``` 54 | 55 | `mixins` is an object mapping SCSS mixins to their replacement SCSS mixins. 56 | 57 | ```json 58 | { 59 | "mixins": { 60 | "deprecated-mixin": "replacement-mixin" 61 | } 62 | } 63 | ``` 64 | 65 | ## Node 66 | 67 | The Node API for selector deprecations is available at 68 | `@primer/css/deprecations`. 69 | 70 | ### Example: 71 | 72 | ```js 73 | const {deprecatedSelectors, deprecatedSassVariables, deprecatedSassMixins} = require('@primer/css/deprecations') 74 | ``` 75 | -------------------------------------------------------------------------------- /src/forms/form-select.scss: -------------------------------------------------------------------------------- 1 | // Custom select 2 | // 3 | // Apply `.select` to any `` element for custom styles. 4 | .form-select { 5 | display: inline-block; 6 | max-width: 100%; 7 | height: $size-5; 8 | padding-right: var(--base-size-24); 9 | background-color: var(--bgColor-default, var(--color-canvas-default)); 10 | // SVG with fill: #586069 (--color-icon-secondary) 11 | background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzU4NjA2OSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNC40MjcgOS40MjdsMy4zOTYgMy4zOTZhLjI1MS4yNTEgMCAwMC4zNTQgMGwzLjM5Ni0zLjM5NkEuMjUuMjUgMCAwMDExLjM5NiA5SDQuNjA0YS4yNS4yNSAwIDAwLS4xNzcuNDI3ek00LjQyMyA2LjQ3TDcuODIgMy4wNzJhLjI1LjI1IDAgMDEuMzU0IDBMMTEuNTcgNi40N2EuMjUuMjUgMCAwMS0uMTc3LjQyN0g0LjZhLjI1LjI1IDAgMDEtLjE3Ny0uNDI3eiIgLz48L3N2Zz4='); 12 | background-repeat: no-repeat; 13 | background-position: right 4px center; 14 | background-size: 16px; 15 | appearance: none; 16 | 17 | // Hides the default caret in IE11 18 | &::-ms-expand { 19 | opacity: 0; 20 | } 21 | 22 | &[multiple] { 23 | height: auto; 24 | } 25 | } 26 | 27 | @include color-mode(dark) { 28 | .form-select { 29 | // SVG with fill: #6e7681 (--color-icon-secondary) 30 | background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iIzZlNzY4MSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNC40MjcgOS40MjdsMy4zOTYgMy4zOTZhLjI1MS4yNTEgMCAwMC4zNTQgMGwzLjM5Ni0zLjM5NkEuMjUuMjUgMCAwMDExLjM5NiA5SDQuNjA0YS4yNS4yNSAwIDAwLS4xNzcuNDI3ek00LjQyMyA2LjQ3TDcuODIgMy4wNzJhLjI1LjI1IDAgMDEuMzU0IDBMMTEuNTcgNi40N2EuMjUuMjUgMCAwMS0uMTc3LjQyN0g0LjZhLjI1LjI1IDAgMDEtLjE3Ny0uNDI3eiIgLz48L3N2Zz4='); 31 | } 32 | } 33 | 34 | .select-sm { 35 | height: $size-4; 36 | // stylelint-disable-next-line primer/spacing 37 | padding-top: 3px; 38 | // stylelint-disable-next-line primer/spacing 39 | padding-bottom: 3px; 40 | // stylelint-disable-next-line primer/typography 41 | font-size: $font-size-small; 42 | 43 | &[multiple] { 44 | height: auto; 45 | min-height: 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/markdown/code.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable selector-max-type 2 | .markdown-body { 3 | // Inline code snippets 4 | code, 5 | tt { 6 | // stylelint-disable-next-line primer/spacing 7 | padding: 0.2em 0.4em; 8 | margin: 0; 9 | // stylelint-disable-next-line primer/typography 10 | font-size: 85%; 11 | white-space: break-spaces; // keeps rendering spaces, but breaks them onto multiple lines 12 | background-color: var(--bgColor-neutral-muted, var(--color-neutral-muted)); 13 | // stylelint-disable-next-line primer/borders 14 | border-radius: $border-radius; 15 | 16 | br { display: none; } 17 | } 18 | 19 | del code { text-decoration: inherit; } 20 | 21 | samp { 22 | // stylelint-disable-next-line primer/typography 23 | font-size: 85%; 24 | } 25 | 26 | pre { 27 | word-wrap: normal; 28 | 29 | code { 30 | // stylelint-disable-next-line primer/typography 31 | font-size: 100%; 32 | } 33 | 34 | // Code tags within code blocks (s) 35 | > code { 36 | padding: 0; 37 | margin: 0; 38 | word-break: normal; 39 | white-space: pre; 40 | background: transparent; 41 | border: 0; 42 | } 43 | } 44 | 45 | .highlight { 46 | margin-bottom: var(--base-size-16); 47 | 48 | pre { 49 | margin-bottom: 0; 50 | word-break: normal; 51 | } 52 | } 53 | 54 | .highlight pre, 55 | pre { 56 | padding: var(--base-size-16); 57 | overflow: auto; 58 | // stylelint-disable-next-line primer/typography 59 | font-size: 85%; 60 | // stylelint-disable-next-line primer/typography 61 | line-height: 1.45; 62 | color: var(--fgColor-default, var(--color-fg-default)); 63 | background-color: var(--bgColor-muted, var(--color-canvas-subtle)); 64 | // stylelint-disable-next-line primer/borders 65 | border-radius: $border-radius; 66 | } 67 | 68 | pre code, 69 | pre tt { 70 | display: inline; 71 | padding: 0; 72 | margin: 0; 73 | overflow: visible; 74 | line-height: inherit; 75 | word-wrap: normal; 76 | background-color: transparent; 77 | border: 0; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/forms/input-group.scss: -------------------------------------------------------------------------------- 1 | .input-group { 2 | display: table; 3 | 4 | .form-control { 5 | position: relative; 6 | width: 100%; 7 | 8 | &:focus { 9 | z-index: 2; 10 | } 11 | 12 | + .btn { 13 | margin-left: 0; 14 | } 15 | } 16 | 17 | // For when you want the input group to behave like inline-block. 18 | &.inline { 19 | display: inline-table; 20 | } 21 | 22 | // within input group, if button exists change focus styles to match input (no offset) 23 | &:focus-within { 24 | // stylelint-disable-next-line selector-max-type 25 | button { 26 | outline-offset: 0; 27 | } 28 | } 29 | 30 | // Autocomplete with embedded icon 31 | .form-control.autocomplete-embedded-icon-wrap { 32 | display: inline-flex; 33 | padding: calc(var(--base-size-4) * 1.25) var(--base-size-8); 34 | } 35 | } 36 | 37 | .input-group .form-control, 38 | .input-group-button { 39 | display: table-cell; 40 | } 41 | 42 | .input-group-button { 43 | width: 1%; 44 | vertical-align: middle; // Match the inputs 45 | } 46 | 47 | .input-group-button--autocomplete-embedded-icon { 48 | vertical-align: bottom; 49 | } 50 | 51 | .input-group .form-control:first-child, 52 | .input-group-button:first-child .btn { 53 | border-top-right-radius: 0; 54 | border-bottom-right-radius: 0; 55 | } 56 | 57 | .input-group .form-control:first-child, 58 | .input-group-button:first-child .btn:not(.btn-primary) { 59 | border-color: var(--control-borderColor-rest, var(--color-border-default)); 60 | } 61 | 62 | .input-group-button:first-child .btn { 63 | // stylelint-disable-next-line primer/spacing 64 | margin-right: -1px; 65 | } 66 | 67 | .input-group .form-control:last-child, 68 | .input-group-button:last-child .btn { 69 | border-top-left-radius: 0; 70 | border-bottom-left-radius: 0; 71 | } 72 | 73 | .input-group .form-control:last-child, 74 | .input-group-button:last-child .btn:not(.btn-primary) { 75 | border-color: var(--control-borderColor-rest, var(--color-border-default)); 76 | } 77 | 78 | .input-group-button:last-child .btn { 79 | // stylelint-disable-next-line primer/spacing 80 | margin-left: -1px; 81 | } 82 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing a new version of Primer CSS 🎉 2 | 3 | ## Prepare the release (in `primer/css`) 4 | 5 | The release process is automated by [changesets]. After you familiarize yourself with how they work. We use an [action workflow integrated in ci](https://github.com/atlassian/changesets#integrating-with-ci). 6 | 7 | 1. Visit the pull requests page and find the [latest Release tracking pr from primer-css](https://github.com/primer/css/pulls/primer-css). If there isn't one, we'll need to build the next release by merging in PRs with changeset files. 8 | 9 | ## Test the release candidate (in `github/github`): 10 | 11 | 1. Create a new branch in the `github/github` repo, name it `primer-`. 12 | 13 | 1. Update the Primer CSS version to the published release candidate with: 14 | 15 | ```sh 16 | bin/npm install @primer/css@-rc. 17 | ``` 18 | 19 | Then commit and push the changes to `package.json`, `package-lock.json`, `LICENSE` and `vendor/npm`. 20 | 21 | 1. If you need to make changes to github/github due to the Primer CSS release, do them in a branch and merge _that_ into your release branch after testing. 22 | 23 | 1. Add or re-request reviewers and fix any breaking tests. 24 | 25 | 1. Test on review-lab. 26 | 27 | 28 | ## Publish the release (in `primer/css`) 29 | 30 | 1. If the release PR got approved and you've done necessary testing, merge it. 31 | 32 | After tests run, the docs site will be deployed and `@primer/css` will be published with your changes to the `latest` dist-tag. You can check [npm](https://www.npmjs.com/package/@primer/css?activeTab=versions) to see if actions has finished. 33 | 34 | 2. Done! 🎉 35 | 36 | 37 | ## Update github.com (in `github/github`): 38 | 39 | 1. Install the latest published version in the same `primer-` branch created earlier with: 40 | 41 | ``` 42 | bin/npm install @primer/css@ -w ./npm-workspaces/primer/ 43 | ``` 44 | 45 | Then commit and push the changes to `package.json`, `package-lock.json`, `LICENSE` and `vendor/npm`. 46 | 47 | 1. Fix any breaking tests. 48 | 49 | 1. Deploy! :rocket: 50 | 51 | [changelog]: ../CHANGELOG.md 52 | [changesets]: https://github.com/atlassian/changesets 53 | -------------------------------------------------------------------------------- /src/forms/radio-group.scss: -------------------------------------------------------------------------------- 1 | // Tab like radio group 2 | 3 | .radio-group { 4 | @include clearfix; 5 | } 6 | 7 | .radio-label { 8 | float: left; 9 | // stylelint-disable-next-line primer/spacing 10 | padding: 6px var(--base-size-16) 6px calc(var(--base-size-16) + 12px + var(--base-size-8)); // 12px is the size of the radio-input 11 | // stylelint-disable-next-line primer/spacing 12 | margin-left: -1px; 13 | // stylelint-disable-next-line primer/typography 14 | font-size: $body-font-size; 15 | // stylelint-disable-next-line primer/typography 16 | line-height: 20px; // Specifically not inherit our `` default 17 | color: var(--fgColor-default, var(--color-fg-default)); 18 | cursor: pointer; 19 | // stylelint-disable-next-line primer/borders, primer/colors 20 | border: $border-width $border-style var(--borderColor-default, var(--color-border-default)); 21 | 22 | :checked + & { 23 | position: relative; 24 | z-index: 1; 25 | border-color: var(--borderColor-accent-emphasis, var(--color-accent-emphasis)); 26 | } 27 | 28 | &:first-of-type { 29 | margin-left: 0; 30 | // stylelint-disable-next-line primer/borders 31 | border-top-left-radius: $border-radius; 32 | // stylelint-disable-next-line primer/borders 33 | border-bottom-left-radius: $border-radius; 34 | } 35 | 36 | &:last-of-type { 37 | // stylelint-disable-next-line primer/borders 38 | border-top-right-radius: $border-radius; 39 | // stylelint-disable-next-line primer/borders 40 | border-bottom-right-radius: $border-radius; 41 | } 42 | 43 | .octicon { 44 | margin-left: var(--base-size-4); 45 | color: var(--fgColor-muted, var(--color-fg-subtle)); 46 | } 47 | } 48 | 49 | .radio-input { 50 | z-index: 3; 51 | float: left; 52 | // stylelint-disable-next-line primer/spacing 53 | margin: 10px calc(var(--base-size-32) * -1) 0 var(--base-size-16); 54 | 55 | &:disabled { 56 | position: relative; // enables z-index 57 | 58 | + .radio-label { 59 | color: var(--fgColor-disabled, var(--color-primer-fg-disabled)); 60 | cursor: default; 61 | background-color: var(--bgColor-neutral-muted, var(--color-neutral-subtle)); 62 | 63 | .octicon { 64 | color: inherit; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/stories/principles/SCSS.mdx: -------------------------------------------------------------------------------- 1 | # SCSS 2 | 3 | ## Spacing 4 | 5 | - Use soft-tabs with a two space indent. Spaces are the only way to guarantee code renders the same in any person's environment. 6 | - Put spaces after `:` in property declarations. 7 | - Put spaces before `{` in rule declarations. 8 | - Put line breaks between rulesets. 9 | - When grouping selectors, keep individual selectors to a single line. 10 | - Place closing braces of declaration blocks on a new line. 11 | - Each declaration should appear on its own line for more accurate error reporting. 12 | 13 | ## Formatting 14 | 15 | - Use hex color codes `#000` unless using `rgba()` in raw CSS (the SCSS `rgba()` function is overloaded to accept hex colors, as in `rgba(#000, .5)`). 16 | - Use `//` for comment blocks (instead of `/* */`). 17 | - Avoid specifying units for zero values, e.g., `margin: 0;` instead of `margin: 0px;`. 18 | - Strive to limit use of shorthand declarations to instances where you must explicitly set all the available values. 19 | 20 | ## Guidelines for using Sass features (WIP) 21 | 22 | _When and when not to create:_ 23 | 24 | - Variables 25 | - Mixins 26 | - Functions 27 | 28 | ## Lint rules (WIP) 29 | 30 | As a rule of thumb, avoid unnecessary nesting in SCSS. At most, aim for three levels. If you cannot help it, step back and rethink your overall strategy (either the specificity needed, or the layout of the nesting). 31 | 32 | - Nesting depth: 3 33 | - Extends: no 34 | - Duplicated properties: no 35 | - Final new lines: yes 36 | - Hex length: 6 37 | - ID selectors: no 38 | - Leading zero: yes 39 | - Naming format: lowercase-with-hyphens 40 | - Property order: see [HTML property order](./html) 41 | - Selector depth: 3 42 | - Single line properties: yes 43 | 44 | ## Examples 45 | 46 | Here are some good examples that apply the above guidelines: 47 | 48 | ```css 49 | // Example of good basic formatting practices 50 | .styleguide-format { 51 | color: #000; 52 | background-color: rgba(0, 0, 0, 0.5); 53 | border: 1px solid #0f0; 54 | } 55 | 56 | // Example of individual selectors getting their own lines (for error reporting) 57 | .multiple, 58 | .classes, 59 | .get-new-lines { 60 | display: block; 61 | } 62 | 63 | // Avoid unnecessary shorthand declarations 64 | .not-so-good { 65 | margin: 0 0 20px; 66 | } 67 | .good { 68 | margin-bottom: 20px; 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /script/stylelint-add-disables.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S NODE_NO_WARNINGS=1 node 2 | // Disables stylelint rules in SASS/CSS files with next-line comments. This is 3 | // useful when introducing a new rule that causes many failures. The comments 4 | // can be fixed and removed at while updating the file later. 5 | // 6 | // Usage: 7 | // 8 | // script/stylelint-add-disables.js src/**/*.scss 9 | 10 | import fs from 'fs' 11 | import {execFile} from 'child_process' 12 | 13 | execFile('stylelint', ['--quiet', '--formatter', 'json', process.argv[2]], (error, stdout, stderr) => { 14 | for (const result of JSON.parse(stdout || stderr || '[]')) { 15 | const filename = result.source 16 | const jsLines = fs.readFileSync(filename, 'utf8').split('\n') 17 | const offensesByLine = {} 18 | let addedLines = 0 19 | 20 | // Produces {47: ['github/no-d-none', 'github/no-blur'], 83: ['github/no-blur']} 21 | for (const message of result.warnings) { 22 | if (offensesByLine[message.lin] && offensesByLine[message.line].includes(message.rule)) { 23 | continue 24 | } else if (offensesByLine[message.line]) { 25 | offensesByLine[message.line].push(message.rule) 26 | } else { 27 | offensesByLine[message.line] = [message.rule] 28 | } 29 | } 30 | 31 | for (const line of Object.keys(offensesByLine)) { 32 | const lineIndex = Number(line) - 1 + addedLines 33 | const previousLine = jsLines[lineIndex - 1] 34 | const ruleIds = Array.from(new Set(offensesByLine[line])).join(', ') 35 | if (isDisableComment(previousLine)) { 36 | if (previousLine.match(/\s?\*\/$/)) { 37 | jsLines[lineIndex - 1] = previousLine.replace(/\s?\*\/$/, `, ${ruleIds} */`) 38 | } else { 39 | jsLines[lineIndex - 1] = `${jsLines[lineIndex - 1]}, ${ruleIds}` 40 | } 41 | } else { 42 | const leftPad = ' '.repeat(jsLines[lineIndex].match(/^\s*/g)[0].length) 43 | jsLines.splice(lineIndex, 0, `${leftPad}// stylelint-disable-next-line ${ruleIds}`) 44 | addedLines += 1 45 | } 46 | } 47 | 48 | if (result.warnings.length !== 0) { 49 | fs.writeFileSync(filename, jsLines.join('\n'), 'utf8') 50 | } 51 | } 52 | }) 53 | 54 | function isDisableComment(line) { 55 | return line && line.match(/\/(\*|\/) stylelint-disable(-next-line)? .+(\*\/)?/) 56 | } 57 | -------------------------------------------------------------------------------- /__tests__/utils/css.js: -------------------------------------------------------------------------------- 1 | import {join, dirname} from 'path' 2 | import semver from 'semver' 3 | import { fileURLToPath } from 'url' 4 | import fs from 'fs' 5 | 6 | const __dirname = dirname(fileURLToPath(import.meta.url)) 7 | 8 | const currentPath = join(__dirname, '../../') 9 | const lastPath = join(__dirname, '../../tmp/node_modules/@primer/css') 10 | 11 | function diffLists(before, after) { 12 | const added = [...new Set(after.filter(value => !before.includes(value)))] 13 | const removed = [...new Set(before.filter(value => !after.includes(value)))] 14 | return { 15 | changed: added.length + removed.length, 16 | added, 17 | removed 18 | } 19 | } 20 | 21 | function getSelectors(versionPath) { 22 | const stats = JSON.parse(fs.readFileSync(join(versionPath, './stats/primer.json'))) 23 | return stats.selectors.values 24 | } 25 | 26 | function getVariables(versionPath) { 27 | const variables = JSON.parse(fs.readFileSync(join(versionPath, './variables.json'))) 28 | return Object.keys(variables) 29 | } 30 | 31 | export function getCurrentVersion() { 32 | const pkg = JSON.parse(fs.readFileSync(join(currentPath, './package.json'))) 33 | return semver.parse(pkg.version) 34 | } 35 | 36 | export function getPackageStats(packageName) { 37 | const stats = JSON.parse(fs.readFileSync(join(currentPath, './dist', `./stats/${packageName}.json`))) 38 | return stats 39 | } 40 | 41 | function getDeprecations(versionPath) { 42 | const deprecations = JSON.parse(fs.readFileSync(join(versionPath, './deprecations.json'))) 43 | return deprecations 44 | } 45 | 46 | function currentVersionSelectors() { 47 | return getSelectors(join(currentPath, './dist')) 48 | } 49 | 50 | function currentVersionVariables() { 51 | return getVariables(join(currentPath, './dist')) 52 | } 53 | 54 | export function currentVersionDeprecations() { 55 | return getDeprecations(join(currentPath, './dist')) 56 | } 57 | 58 | function lastVersionSelectors() { 59 | return getSelectors(join(lastPath, './dist')) 60 | } 61 | 62 | function lastVersionVariables() { 63 | return getVariables(join(lastPath, './dist')) 64 | } 65 | 66 | export function getSelectorDiff() { 67 | return diffLists(lastVersionSelectors(), currentVersionSelectors()) 68 | } 69 | 70 | export function getVariableDiff() { 71 | return diffLists(lastVersionVariables(), currentVersionVariables()) 72 | } 73 | -------------------------------------------------------------------------------- /src/marketing/utilities/layout.scss: -------------------------------------------------------------------------------- 1 | // Layout utilities 2 | 3 | // Responsive utilities to position content 4 | // No utilities for sm and xl breakpoints 5 | @each $breakpoint, $variant in $marketing-position-variants { 6 | @include breakpoint($breakpoint) { 7 | @each $scale, $size in $spacer-map-extended { 8 | @if ($size != 0 or $variant != '') { 9 | // stylelint-disable-next-line primer/spacing 10 | .top#{$variant}-#{$scale} { top: $size !important; } 11 | // stylelint-disable-next-line primer/spacing 12 | .right#{$variant}-#{$scale} { right: $size !important; } 13 | // stylelint-disable-next-line primer/spacing 14 | .bottom#{$variant}-#{$scale} { bottom: $size !important; } 15 | // stylelint-disable-next-line primer/spacing 16 | .left#{$variant}-#{$scale} { left: $size !important; } 17 | } 18 | 19 | @if ($size != 0) { 20 | // stylelint-disable-next-line primer/spacing 21 | .top#{$variant}-n#{$scale} { top: -$size !important; } 22 | // stylelint-disable-next-line primer/spacing 23 | .right#{$variant}-n#{$scale} { right: -$size !important; } 24 | // stylelint-disable-next-line primer/spacing 25 | .bottom#{$variant}-n#{$scale} { bottom: -$size !important; } 26 | // stylelint-disable-next-line primer/spacing 27 | .left#{$variant}-n#{$scale} { left: -$size !important; } 28 | } 29 | } 30 | } 31 | } 32 | 33 | // Negative offset columns 34 | @each $breakpoint, $variant in $responsive-variants { 35 | @include breakpoint($breakpoint) { 36 | @for $offset from 1 through 7 { 37 | // stylelint-disable-next-line primer/spacing 38 | .offset#{$variant}-n#{$offset} { margin-left: -($offset * 0.0833333333 * 100%); } 39 | } 40 | } 41 | } 42 | 43 | // Width and height utilities, especially needed when the 44 | // dimensions of an image are set in HTML 45 | .width-auto { width: auto !important; } 46 | .height-auto { height: auto !important; } 47 | 48 | // Make an object fill its parent 49 | .object-fit-cover { object-fit: cover !important; } 50 | 51 | // Handling z-index 52 | .z-1 { z-index: 1 !important; } 53 | .z-2 { z-index: 2 !important; } 54 | .z-3 { z-index: 3 !important; } 55 | 56 | // Negative z-index 57 | .z-n1 { z-index: -1 !important; } 58 | .z-n2 { z-index: -2 !important; } 59 | -------------------------------------------------------------------------------- /src/navigation/filter-list.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable selector-max-specificity 2 | 3 | // Filters list 4 | // 5 | // A vertical list of filters. 6 | .filter-list { 7 | list-style-type: none; 8 | 9 | &.small .filter-item { 10 | // stylelint-disable-next-line primer/spacing 11 | padding: 6px 12px; 12 | // stylelint-disable-next-line primer/typography 13 | font-size: $font-size-small; 14 | } 15 | 16 | &.pjax-active .filter-item { 17 | color: var(--fgColor-muted, var(--color-fg-muted)); 18 | background-color: transparent; 19 | 20 | &.pjax-active { 21 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 22 | background-color: var(--bgColor-accent-emphasis, var(--color-accent-emphasis)); 23 | } 24 | } 25 | } 26 | 27 | .filter-item { 28 | position: relative; 29 | display: block; 30 | padding: var(--base-size-8) var(--base-size-16); 31 | margin-bottom: var(--base-size-4); 32 | overflow: hidden; 33 | // stylelint-disable-next-line primer/typography 34 | font-size: $h5-size; 35 | color: var(--fgColor-muted, var(--color-fg-muted)); 36 | text-decoration: none; 37 | text-overflow: ellipsis; 38 | white-space: nowrap; 39 | cursor: pointer; 40 | // stylelint-disable-next-line primer/borders 41 | border-radius: $border-radius; 42 | 43 | &:hover { 44 | text-decoration: none; 45 | background-color: var(--bgColor-muted, var(--color-canvas-subtle)); 46 | } 47 | 48 | &.selected, 49 | &[aria-selected='true'], 50 | &[aria-current]:not([aria-current='false']) { 51 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 52 | background-color: var(--bgColor-accent-emphasis, var(--color-accent-emphasis)); 53 | 54 | // fallback :focus state 55 | &:focus { 56 | @include focusOutlineOnEmphasis; 57 | 58 | // remove fallback :focus if :focus-visible is supported 59 | &:not(:focus-visible) { 60 | outline: solid 1px transparent; 61 | box-shadow: none; 62 | } 63 | } 64 | 65 | // default focus state 66 | &:focus-visible { 67 | @include focusOutlineOnEmphasis; 68 | } 69 | } 70 | 71 | .count { 72 | float: right; 73 | // stylelint-disable-next-line primer/typography 74 | font-weight: $font-weight-bold; 75 | } 76 | 77 | .bar { 78 | position: absolute; 79 | top: var(--base-size-2); 80 | right: 0; 81 | bottom: var(--base-size-2); 82 | z-index: -1; 83 | display: inline-block; 84 | background-color: var(--bgColor-neutral-muted, var(--color-neutral-subtle)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Primer CSS 2 | The CSS implementation of GitHub's Primer Design System 3 | 4 | [](https://www.npmjs.com/package/@primer/css) 5 | [](https://github.com/primer/css/actions/workflows/ci.yml) 6 | [](https://github.com/primer/css/graphs/contributors) 7 | [](https://github.com/primer/css/commits/main) 8 | [](https://github.com/primer/css/blob/main/LICENSE) 9 | 10 | > :warning: **This project is in KTLO mode!** Use existing utility classes from this project where needed. For more complete patterns that include styling and markup, please use [primer/react](https://github.com/primer/react) or, if necessary, [primer/view_components](https://github.com/primer/view_components). 11 | 12 | ## Documentation 13 | Our documentation site lives at [primer.style/css](https://primer.style/css). You'll be able to find detailed documentation on getting started, all of the components, our theme, our principles, and more. 14 | 15 | ## Install 16 | This repository is distributed with [npm]. After [installing npm][install-npm], you can install `@primer/css` with this command: 17 | 18 | ```sh 19 | npm install --save @primer/css 20 | ``` 21 | 22 | ## Usage 23 | The included source files are written in [Sass] using SCSS syntax. Once installed, you can add your project's `node_modules` directory to your Sass [include paths](https://github.com/sass/node-sass#includepaths) (AKA [load paths](http://technology.customink.com/blog/2014/10/09/understanding-and-using-sass-load-paths/) in Ruby), then import it like this: 24 | 25 | ```scss 26 | @import "@primer/css/index.scss"; 27 | ``` 28 | 29 | You can import individual Primer modules directly from the `@primer/css` package: 30 | 31 | ```scss 32 | @import "@primer/css/core/index.scss"; 33 | @import "@primer/css/product/index.scss"; 34 | @import "@primer/css/marketing/index.scss"; 35 | ``` 36 | 37 | ## Development 38 | See [DEVELOP.md](DEVELOP.md) for development docs. 39 | 40 | ## Releasing (for GitHub staff) 41 | You can find docs about our release process in [RELEASING.md](RELEASING.md). 42 | 43 | ## License 44 | 45 | [MIT](./LICENSE) © [GitHub](https://github.com/) 46 | 47 | 48 | [install-npm]: https://docs.npmjs.com/getting-started/installing-node 49 | [npm]: https://www.npmjs.com/ 50 | [sass]: http://sass-lang.com/ 51 | -------------------------------------------------------------------------------- /src/markdown/lists.scss: -------------------------------------------------------------------------------- 1 | // Base styles 2 | // stylelint-disable selector-no-qualifying-type 3 | // stylelint-disable selector-max-type 4 | .markdown-body { 5 | // Lists, Blockquotes & Such 6 | ul, 7 | ol { 8 | // stylelint-disable-next-line primer/spacing 9 | padding-left: 2em; 10 | 11 | &.no-list { 12 | padding: 0; 13 | list-style-type: none; 14 | } 15 | } 16 | 17 | ol[type='a s'] { 18 | list-style-type: lower-alpha; 19 | } 20 | 21 | ol[type='A s'] { 22 | list-style-type: upper-alpha; 23 | } 24 | 25 | ol[type='i s'] { 26 | list-style-type: lower-roman; 27 | } 28 | 29 | ol[type='I s'] { 30 | list-style-type: upper-roman; 31 | } 32 | 33 | ol[type='1'] { 34 | list-style-type: decimal; 35 | } 36 | 37 | // Reset style to decimal (HTML default) specifically for AsciiDoc 38 | // construction (doesn't affect MarkDown) 39 | div > ol:not([type]) { 40 | list-style-type: decimal; 41 | } 42 | 43 | // Did someone complain about list spacing? Encourage them 44 | // to create the spacing with their markdown formatting. 45 | // List behavior should be controled by the markup, not the css. 46 | // 47 | // For lists with padding between items, use blank 48 | // lines between items. This will generate paragraphs with 49 | // padding to space things out. 50 | // 51 | // - item 52 | // 53 | // - item 54 | // 55 | // - item 56 | // 57 | // For list without padding, don't use blank lines. 58 | // 59 | // - item 60 | // - item 61 | // - item 62 | // 63 | // Modifying the css to emulate these behaviors merely brakes 64 | // one case in the process of solving another. Don't change 65 | // this unless it's really really a bug. 66 | ul ul, 67 | ul ol, 68 | ol ol, 69 | ol ul { 70 | margin-top: 0; 71 | margin-bottom: 0; 72 | } 73 | 74 | li > p { 75 | margin-top: var(--base-size-16); 76 | } 77 | 78 | li + li { 79 | // stylelint-disable-next-line primer/spacing 80 | margin-top: $em-spacer-3; 81 | } 82 | 83 | dl { 84 | padding: 0; 85 | 86 | dt { 87 | padding: 0; 88 | margin-top: var(--base-size-16); 89 | // stylelint-disable-next-line primer/typography 90 | font-size: 1em; 91 | font-style: italic; 92 | // stylelint-disable-next-line primer/typography 93 | font-weight: $font-weight-bold; 94 | } 95 | 96 | dd { 97 | padding: 0 var(--base-size-16); 98 | margin-bottom: var(--base-size-16); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main, next_major ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '34 14 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v5 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v4 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v4 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v4 71 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/Tooltip/Tooltip.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as TooltipStories from './Tooltip.stories' 4 | 5 | 6 | 7 | # Tooltip 8 | 9 | Please note that the `.tooltipped` component is **deprecated**. We recommend using the [Tooltip component](https://primer.style/view-components/components/alpha/tooltip) instead. 10 | 11 | Add tooltips built entirely in CSS to appropriate elements. 12 | 13 | ## Implementation and accessibility 14 | 15 | Tooltips as a UI pattern should be our last resort for conveying information because it is hidden by default and often with zero or little visual indicator of its existence. 16 | 17 | Before adding a tooltip, please consider: Is this information essential and necessary\* Can the UI be made clearer? Can the information be shown on the page by default? And check out [alternatives to Tooltips](https://primer.style/design/accessibility/tooltip-alternatives) to explore your options. 18 | 19 | ### Attention 20 | 21 | - **Never** use tooltips on static elements. They should only be used on interactive elements, because users cannot tab-focus into static elements, which may make the content inaccessible for keyboard-only users and screen readers. 22 | - we use `aria-label` for tooltip contents, because it is crucial that they are accessible to screen reader users. However, `aria-label` **replaces** the text content of an element in screen readers, so only use `.tooltipped` on elements with no existing text content such as an icon-only button. 23 | - Tooltip classes will conflict with Octicon styles, and as such, must be applied to the parent element instead of the icon. 24 | 25 | ## Tooltip direction 26 | 27 | Specify the direction of a tooltip with north, south, east, and west directions: 28 | 29 | 30 | 31 | ## Tooltip alignment 32 | 33 | Align tooltips to the left or right of an element, combined with a directional class to specify north or south. Use a modifier of `1` or `2` to choose how much the tooltip's arrow is indented. 34 | 35 | 36 | 37 | ## Tooltips with multiple lines 38 | 39 | Use `.tooltipped-multiline` when you have long content. This style has some limitations: you cannot pre-format the text with newlines, and tooltips are limited to a max-width of `250px`. 40 | 41 | 42 | 43 | ## Tooltips with no delay 44 | 45 | By default the tooltips have a slight delay before appearing. This is to keep multiple tooltips in the UI from being distracting. There is a `.tooltipped-no-delay` modifier class you can use to override this. 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/markdown/headings.scss: -------------------------------------------------------------------------------- 1 | // Needs refactoring 2 | // stylelint-disable selector-max-compound-selectors, selector-max-specificity 3 | // stylelint-disable selector-max-type 4 | .markdown-body { 5 | // Headings 6 | h1, 7 | h2, 8 | h3, 9 | h4, 10 | h5, 11 | h6 { 12 | margin-top: var(--base-size-24); 13 | margin-bottom: var(--base-size-16); 14 | // stylelint-disable-next-line primer/typography 15 | font-weight: $font-weight-bold; 16 | // stylelint-disable-next-line primer/typography 17 | line-height: $lh-condensed; 18 | 19 | .octicon-link { 20 | color: var(--fgColor-default, var(--color-fg-default)); 21 | vertical-align: middle; 22 | visibility: hidden; 23 | } 24 | 25 | &:hover .anchor { 26 | text-decoration: none; 27 | 28 | .octicon-link { 29 | visibility: visible; 30 | } 31 | } 32 | 33 | tt, 34 | code { 35 | // stylelint-disable-next-line primer/spacing 36 | padding: 0 0.2em; 37 | font-size: inherit; 38 | } 39 | } 40 | 41 | h1 { 42 | // stylelint-disable-next-line primer/spacing 43 | padding-bottom: 0.3em; 44 | // stylelint-disable-next-line primer/typography 45 | font-size: 2em; 46 | // stylelint-disable-next-line primer/borders, primer/colors 47 | border-bottom: $border-width $border-style var(--borderColor-muted, var(--color-border-muted)); 48 | } 49 | 50 | h2 { 51 | // stylelint-disable-next-line primer/spacing 52 | padding-bottom: 0.3em; 53 | // stylelint-disable-next-line primer/typography 54 | font-size: 1.5em; 55 | // stylelint-disable-next-line primer/borders, primer/colors 56 | border-bottom: $border-width $border-style var(--borderColor-muted, var(--color-border-muted)); 57 | } 58 | 59 | h3 { 60 | // stylelint-disable-next-line primer/typography 61 | font-size: 1.25em; 62 | } 63 | 64 | h4 { 65 | // stylelint-disable-next-line primer/typography 66 | font-size: 1em; 67 | } 68 | 69 | h5 { 70 | // stylelint-disable-next-line primer/typography 71 | font-size: 0.875em; 72 | } 73 | 74 | h6 { 75 | // stylelint-disable-next-line primer/typography 76 | font-size: 0.85em; 77 | color: var(--fgColor-muted, var(--color-fg-muted)); 78 | } 79 | 80 | summary { 81 | h1, 82 | h2, 83 | h3, 84 | h4, 85 | h5, 86 | h6 { 87 | display: inline-block; 88 | 89 | .anchor { 90 | // stylelint-disable-next-line primer/spacing 91 | margin-left: -40px; 92 | } 93 | } 94 | 95 | h1, 96 | h2 { 97 | padding-bottom: 0; 98 | border-bottom: 0; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /docs/stories/components/Layout/StackExamples.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | StackTemplate 4 | } from './Stack.stories' 5 | 6 | export default { 7 | title: 'Components/Layout/Stack/Examples' 8 | } 9 | 10 | export const ButtonInlineStack = StackTemplate.bind({}) 11 | ButtonInlineStack.args = { 12 | direction: "inline", 13 | gap: "condensed", 14 | children: ( 15 | <> 16 | Button 17 | Button 18 | Button 19 | > 20 | ) 21 | }; 22 | 23 | export const PageSections = StackTemplate.bind({}) 24 | PageSections.args = { 25 | direction: "block", 26 | gap: "normal", 27 | children: ( 28 | <> 29 | Section 1 30 | Section 2 31 | Section 3 32 | > 33 | ) 34 | }; 35 | 36 | export const Composition = StackTemplate.bind({}) 37 | Composition.args = { 38 | direction: "block", 39 | gap: "normal", 40 | children: ( 41 | <> 42 | 44 | 46 | Heading 47 | Lorem ipsum dolor sit amet avec consequer domulus sit lorem ipsum dolor sit amet. 48 | 50 | Inline labels set to wrap 51 | Label 2 52 | Label 3 53 | Label 4 54 | Label 5 55 | > 56 | )} /> 57 | > 58 | )} /> 59 | 60 | 62 | Heading 63 | Lorem ipsum dolor sit amet avec consequer domulus sit lorem ipsum dolor sit amet. 64 | 66 | Inline labels set to wrap 67 | Label 2 68 | Label 3 69 | Label 4 70 | Label 5 71 | > 72 | )} /> 73 | > 74 | )} /> 75 | > 76 | )} /> 77 | > 78 | ) 79 | }; -------------------------------------------------------------------------------- /src/utilities/margin.scss: -------------------------------------------------------------------------------- 1 | // Margin spacer utilities 2 | // stylelint-disable primer/spacing 3 | 4 | // Loop through the breakpoint values 5 | @each $breakpoint, $variant in $responsive-variants { 6 | @include breakpoint($breakpoint) { 7 | // Loop through the spacer values 8 | @each $scale, $size in $spacer-map-rem-extended { 9 | @if ($scale < length($spacer-map-rem)) { 10 | /* Set a $size margin to all sides at $breakpoint */ 11 | .m#{$variant}-#{$scale} { margin: $size !important; } 12 | } 13 | 14 | /* Set a $size margin on the top at $breakpoint */ 15 | .mt#{$variant}-#{$scale} { margin-top: $size !important; } 16 | /* Set a $size margin on the bottom at $breakpoint */ 17 | .mb#{$variant}-#{$scale} { margin-bottom: $size !important; } 18 | 19 | @if ($scale < length($spacer-map-rem)) { 20 | /* Set a $size margin on the right at $breakpoint */ 21 | .mr#{$variant}-#{$scale} { margin-right: $size !important; } 22 | /* Set a $size margin on the left at $breakpoint */ 23 | .ml#{$variant}-#{$scale} { margin-left: $size !important; } 24 | } 25 | 26 | @if ($size != 0) { 27 | /* Set a negative $size margin on top at $breakpoint */ 28 | .mt#{$variant}-n#{$scale} { margin-top: calc(-1 * $size) !important; } 29 | /* Set a negative $size margin on the bottom at $breakpoint */ 30 | .mb#{$variant}-n#{$scale} { margin-bottom: calc(-1 * $size) !important; } 31 | 32 | @if ($scale < length($spacer-map-rem)) { 33 | /* Set a negative $size margin on the right at $breakpoint */ 34 | .mr#{$variant}-n#{$scale} { margin-right : calc(-1 * $size) !important; } 35 | /* Set a negative $size margin on the left at $breakpoint */ 36 | .ml#{$variant}-n#{$scale} { margin-left : calc(-1 * $size) !important; } 37 | } 38 | } 39 | 40 | @if ($scale < length($spacer-map-rem)) { 41 | /* Set a $size margin on the left & right at $breakpoint */ 42 | .mx#{$variant}-#{$scale} { 43 | margin-right: $size !important; 44 | margin-left: $size !important; 45 | } 46 | } 47 | 48 | /* Set a $size margin on the top & bottom at $breakpoint */ 49 | .my#{$variant}-#{$scale} { 50 | margin-top: $size !important; 51 | margin-bottom: $size !important; 52 | } 53 | } 54 | 55 | /* responsive horizontal auto margins */ 56 | .mx#{$variant}-auto { 57 | margin-right: auto !important; 58 | margin-left: auto !important; 59 | } 60 | } 61 | } 62 | 63 | .m-auto { margin: auto !important; } 64 | 65 | .mt-auto { margin-top: auto !important; } 66 | .mr-auto { margin-right: auto !important; } 67 | .mb-auto { margin-bottom: auto !important; } 68 | .ml-auto { margin-left: auto !important; } 69 | -------------------------------------------------------------------------------- /src/support/mixins/misc.scss: -------------------------------------------------------------------------------- 1 | // Generate a two-color caret for any element. 2 | @mixin double-caret($background: var(--bgColor-default, var(--color-canvas-default)), $border: var(--borderColor-default, var(--color-border-default))) { 3 | &::after, 4 | &::before { 5 | position: absolute; 6 | // stylelint-disable-next-line primer/spacing 7 | top: 11px; 8 | right: 100%; 9 | left: calc(var(--base-size-8) * -1); 10 | display: block; 11 | width: 8px; 12 | height: 16px; 13 | pointer-events: none; 14 | content: ' '; 15 | clip-path: polygon(0 50%, 100% 0, 100% 100%); 16 | } 17 | 18 | &::after { 19 | // stylelint-disable-next-line primer/spacing 20 | margin-left: 2px; 21 | background-color: var(--bgColor-default, var(--color-canvas-default)); 22 | background-image: linear-gradient($background, $background); 23 | } 24 | 25 | &::before { 26 | // stylelint-disable-next-line primer/colors 27 | background-color: $border; 28 | } 29 | } 30 | 31 | // global focus styles 32 | 33 | // inset box-shadow for form controls 34 | @mixin focusBoxShadowInset($outlineWidth: 1px, $outlineColor: var(--focus-outlineColor, var(--color-accent-fg))) { 35 | // stylelint-disable-next-line primer/colors 36 | border-color: var(--focus-outlineColor, var(--color-accent-fg)); 37 | outline: none; 38 | // stylelint-disable-next-line primer/box-shadow 39 | box-shadow: inset 0 0 0 $outlineWidth $outlineColor; 40 | } 41 | 42 | // box-shadow for :target styles (no inset) 43 | // !important to override PCSS utilities 44 | @mixin targetBoxShadow($outlineWidth: 2px, $outlineColor: var(--focus-outlineColor, var(--color-accent-fg))) { 45 | outline: none !important; 46 | // stylelint-disable-next-line primer/box-shadow 47 | box-shadow: 0 0 0 $outlineWidth $outlineColor !important; 48 | } 49 | 50 | // outline 51 | @mixin focusOutline($outlineOffset: -2px, $outlineColor: var(--focus-outlineColor, var(--color-accent-fg))) { 52 | outline: 2px solid $outlineColor; 53 | outline-offset: $outlineOffset; 54 | box-shadow: none; 55 | } 56 | 57 | // outline with fg box-shadow for buttons 58 | @mixin focusOutlineOnEmphasis($outlineOffset: -2px, $outlineColor: var(--focus-outlineColor, var(--color-accent-fg))) { 59 | outline: 2px solid $outlineColor; 60 | outline-offset: $outlineOffset; 61 | // stylelint-disable-next-line primer/box-shadow 62 | box-shadow: inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 63 | } 64 | 65 | // if min-width is undefined, return only min-height 66 | @mixin minTouchTarget($min-height: 32px, $min-width: '') { 67 | position: absolute; 68 | top: 50%; 69 | left: 50%; 70 | width: 100%; 71 | height: 100%; 72 | min-height: $min-height; 73 | content: ''; 74 | transform: translateX(-50%) translateY(-50%); 75 | 76 | @if $min-width != '' { 77 | min-width: $min-width; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /docs/stories/utilities/Padding.stories.jsx: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Utilities/Padding' 3 | } 4 | 5 | export const Uniform = ({}) => ( 6 | 7 | .p-0 8 | .p-1 9 | .p-2 10 | .p-3 11 | .p-4 12 | .p-5 13 | .p-6 14 | 15 | ) 16 | 17 | export const Directional = ({}) => ( 18 | 19 | .pt-3 20 | .pr-3 21 | .pb-3 22 | .pl-3 23 | .py-3 24 | .px-3 25 | 26 | ) 27 | 28 | export const DirectionalExtended = ({}) => ( 29 | 30 | .pt-7 31 | .pt-8 32 | .pt-9 33 | .pt-10 34 | .pt-11 35 | .pt-12 36 | 37 | ) 38 | 39 | export const Responsive = ({}) => ( 40 | 41 | .px-sm-2 .px-md-4 42 | 43 | ) 44 | 45 | export const ResponsiveContainer = ({}) => ( 46 | 47 | .p-responsive 48 | 49 | ) 50 | -------------------------------------------------------------------------------- /script/stylelint-remove-disables.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {execFile} from 'child_process' 3 | import {readFileSync, writeFileSync} from 'fs' 4 | 5 | const files = process.argv.slice(2) 6 | if (files.length === 0) { 7 | files.push('app/assets/stylesheets') 8 | } 9 | 10 | // we use an empty "marker" to delineate removed lines 11 | const REMOVED = `===REMOVED@${Date.now()}===` 12 | 13 | execFile('stylelint', ['--rd', '--quiet', '--formatter', 'json', process.argv[2]], (error, stdout) => { 14 | 15 | // Filter to only needless disables comments 16 | const results = JSON.parse(stdout) 17 | .filter(result => result.warnings.length > 0) 18 | .map(({source, warnings}) => { 19 | return { 20 | source, 21 | warnings: warnings.filter(warning => warning.rule === '--report-needless-disables') 22 | } 23 | }) 24 | 25 | for (const {source, warnings} of results) { 26 | console.log(`--- ${source}\n+++ ${source}`) 27 | const lines = readFileSync(source, 'utf8').split(/\n/) 28 | for (const {text, line: lineNum} of warnings) { 29 | const ruleName = text.match(/^Needless disable for "(.+)"$/)[1] 30 | const line = lines[lineNum - 1] 31 | let disableComment = parseDisableComment(line) 32 | 33 | if (disableComment) { 34 | const rules = new Set(disableComment.rules) 35 | rules.delete(ruleName) 36 | let replacementLine = line 37 | if (rules.size === 0) { 38 | console.log(`@@ ${lineNum},${lineNum - 1} @@`) 39 | replacementLine = line.replace(`// ${disableComment.content}`, REMOVED) 40 | } else { 41 | console.log(`@@ ${lineNum},${lineNum} @@`) 42 | replacementLine = line.replace(disableComment.content, `${disableComment.type} ${Array.from(rules).join(', ')}`) 43 | } 44 | lines[lineNum - 1] = replacementLine 45 | 46 | if (lines[lineNum - 2]) { 47 | console.log(`${lines[lineNum - 2]}`) 48 | } 49 | console.log(`- ${line}`) 50 | if (!replacementLine.includes(REMOVED)) { 51 | console.log(`+ ${replacementLine}`) 52 | } 53 | if (lines[lineNum]) { 54 | console.log(`${lines[lineNum]}`) 55 | } 56 | } 57 | } 58 | console.log('') 59 | const output = lines.map(line => { 60 | if (line.trim() === REMOVED) { 61 | return null 62 | } else if (line.includes(REMOVED)) { 63 | line = line.replace(REMOVED, '').trimEnd() 64 | } 65 | 66 | return line 67 | }).filter(line => line !== null).join('\n') 68 | 69 | writeFileSync(source, output, 'utf8') 70 | } 71 | }) 72 | 73 | function parseDisableComment(str) { 74 | const match = str.match(/(stylelint-disable((-next)?-line)?)\s+(.+)$/) 75 | return match 76 | ? { 77 | content: match[0], 78 | type: match[1], 79 | rules: match[4].split(/,\s+/) 80 | } 81 | : false 82 | } 83 | -------------------------------------------------------------------------------- /src/buttons/button-group.scss: -------------------------------------------------------------------------------- 1 | // Button group 2 | // 3 | // A button group is a series of buttons laid out next to each other, all part 4 | // of one visual button, but separated by rules to be separate. 5 | 6 | .BtnGroup { 7 | display: inline-block; 8 | vertical-align: middle; 9 | 10 | @include clearfix(); 11 | 12 | // Proper spacing for multiple button groups (a la, gollum editor) 13 | + .BtnGroup, 14 | + .btn { 15 | margin-left: var(--base-size-4); 16 | } 17 | } 18 | 19 | .BtnGroup-item { 20 | position: relative; 21 | float: left; 22 | border-right-width: 0; 23 | border-radius: 0; 24 | 25 | &:first-child { 26 | // stylelint-disable-next-line primer/borders 27 | border-top-left-radius: $border-radius; 28 | // stylelint-disable-next-line primer/borders 29 | border-bottom-left-radius: $border-radius; 30 | } 31 | 32 | &:last-child { 33 | // stylelint-disable-next-line primer/borders 34 | border-right-width: $border-width; 35 | // stylelint-disable-next-line primer/borders 36 | border-top-right-radius: $border-radius; 37 | // stylelint-disable-next-line primer/borders 38 | border-bottom-right-radius: $border-radius; 39 | } 40 | 41 | &.selected, 42 | &[aria-selected='true'], 43 | &:focus, 44 | &:active, 45 | &:hover { 46 | // stylelint-disable-next-line primer/borders 47 | border-right-width: $border-width; 48 | 49 | + .BtnGroup-item, 50 | + .BtnGroup-parent .BtnGroup-item { 51 | border-left-width: 0; 52 | } 53 | } 54 | } 55 | 56 | .BtnGroup-parent { 57 | float: left; 58 | 59 | &:first-child .BtnGroup-item { 60 | // stylelint-disable-next-line primer/borders 61 | border-top-left-radius: $border-radius; 62 | // stylelint-disable-next-line primer/borders 63 | border-bottom-left-radius: $border-radius; 64 | } 65 | 66 | &:last-child .BtnGroup-item { 67 | // stylelint-disable-next-line primer/borders 68 | border-right-width: $border-width; 69 | // stylelint-disable-next-line primer/borders 70 | border-top-right-radius: $border-radius; 71 | // stylelint-disable-next-line primer/borders 72 | border-bottom-right-radius: $border-radius; 73 | } 74 | 75 | .BtnGroup-item { 76 | border-right-width: 0; 77 | border-radius: 0; 78 | } 79 | 80 | &.selected, 81 | &[aria-selected='true'], 82 | &:focus, 83 | &:active, 84 | &:hover { 85 | .BtnGroup-item { 86 | // stylelint-disable-next-line primer/borders 87 | border-right-width: $border-width; 88 | } 89 | 90 | + .BtnGroup-item, 91 | + .BtnGroup-parent .BtnGroup-item { 92 | border-left-width: 0; 93 | } 94 | } 95 | } 96 | 97 | // ensure that the focus ring sits above the adjacent buttons 98 | .BtnGroup-item, 99 | .BtnGroup-parent { 100 | &:focus, 101 | &:active { 102 | z-index: 1; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/utilities/details.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable selector-max-type, selector-max-specificity, max-nesting-depth, selector-no-qualifying-type 2 | 3 | .details-overlay[open] > summary::before { 4 | position: fixed; 5 | top: 0; 6 | right: 0; 7 | bottom: 0; 8 | left: 0; 9 | z-index: 80; 10 | display: block; 11 | cursor: default; 12 | content: ' '; 13 | background: transparent; 14 | } 15 | 16 | .details-overlay-dark[open] > summary::before { 17 | z-index: 111; 18 | background: var(--overlay-backdrop-bgColor, var(--color-primer-canvas-backdrop)); 19 | } 20 | 21 | .details-reset { 22 | // Remove marker added by the display: list-item browser default 23 | > summary { 24 | list-style: none; 25 | transition: 80ms cubic-bezier(0.33, 1, 0.68, 1); 26 | transition-property: color, background-color, box-shadow, border-color; 27 | // fallback :focus state 28 | &:focus { 29 | @include focusOutline; 30 | 31 | // remove fallback :focus if :focus-visible is supported 32 | &:not(:focus-visible) { 33 | outline: solid 1px transparent; 34 | } 35 | } 36 | 37 | // default focus state 38 | &:focus-visible { 39 | @include focusOutline; 40 | } 41 | 42 | &.btn-primary { 43 | // fallback :focus state 44 | &:focus { 45 | @include focusOutlineOnEmphasis; 46 | 47 | // remove fallback :focus if :focus-visible is supported 48 | &:not(:focus-visible) { 49 | outline: solid 1px transparent; 50 | box-shadow: none; 51 | } 52 | } 53 | 54 | // default focus state 55 | &:focus-visible { 56 | @include focusOutlineOnEmphasis; 57 | } 58 | } 59 | } 60 | // Remove marker added by details polyfill 61 | > summary::before { 62 | display: none; 63 | } 64 | // Remove marker added by Chrome 65 | > summary::-webkit-details-marker { 66 | display: none; 67 | } 68 | } 69 | 70 | .details-overlay > summary { 71 | transition: 80ms cubic-bezier(0.33, 1, 0.68, 1); 72 | transition-property: color, background-color, box-shadow, border-color; 73 | // fallback :focus state 74 | &:focus { 75 | @include focusOutline; 76 | 77 | // remove fallback :focus if :focus-visible is supported 78 | &:not(:focus-visible) { 79 | outline: solid 1px transparent; 80 | } 81 | } 82 | 83 | // default focus state 84 | &:focus-visible { 85 | @include focusOutline; 86 | } 87 | 88 | &.btn-primary { 89 | // fallback :focus state 90 | &:focus { 91 | @include focusOutlineOnEmphasis; 92 | 93 | // remove fallback :focus if :focus-visible is supported 94 | &:not(:focus-visible) { 95 | outline: solid 1px transparent; 96 | box-shadow: none; 97 | } 98 | } 99 | 100 | // default focus state 101 | &:focus-visible { 102 | @include focusOutlineOnEmphasis; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/utilities/flexbox.scss: -------------------------------------------------------------------------------- 1 | // Flex utility classes 2 | @each $breakpoint, $variant in $responsive-variants { 3 | @include breakpoint($breakpoint) { 4 | // Flexbox classes 5 | // Container 6 | .flex#{$variant}-row { flex-direction: row !important; } 7 | .flex#{$variant}-row-reverse { flex-direction: row-reverse !important; } 8 | .flex#{$variant}-column { flex-direction: column !important; } 9 | .flex#{$variant}-column-reverse { flex-direction: column-reverse !important; } 10 | 11 | .flex#{$variant}-wrap { flex-wrap: wrap !important; } 12 | .flex#{$variant}-nowrap { flex-wrap: nowrap !important; } 13 | .flex#{$variant}-wrap-reverse { flex-wrap: wrap-reverse !important; } 14 | 15 | .flex#{$variant}-justify-start { justify-content: flex-start !important; } 16 | .flex#{$variant}-justify-end { justify-content: flex-end !important; } 17 | .flex#{$variant}-justify-center { justify-content: center !important; } 18 | .flex#{$variant}-justify-between { justify-content: space-between !important; } 19 | .flex#{$variant}-justify-around { justify-content: space-around !important; } 20 | 21 | .flex#{$variant}-items-start { align-items: flex-start !important; } 22 | .flex#{$variant}-items-end { align-items: flex-end !important; } 23 | .flex#{$variant}-items-center { align-items: center !important; } 24 | .flex#{$variant}-items-baseline { align-items: baseline !important; } 25 | .flex#{$variant}-items-stretch { align-items: stretch !important; } 26 | 27 | .flex#{$variant}-content-start { align-content: flex-start !important; } 28 | .flex#{$variant}-content-end { align-content: flex-end !important; } 29 | .flex#{$variant}-content-center { align-content: center !important; } 30 | .flex#{$variant}-content-between { align-content: space-between !important; } 31 | .flex#{$variant}-content-around { align-content: space-around !important; } 32 | .flex#{$variant}-content-stretch { align-content: stretch !important; } 33 | 34 | // Item 35 | .flex#{$variant}-1 { flex: 1 !important; } 36 | .flex#{$variant}-auto { flex: auto !important; } 37 | .flex#{$variant}-grow-0 { flex-grow: 0 !important; } 38 | .flex#{$variant}-shrink-0 { flex-shrink: 0 !important; } 39 | 40 | .flex#{$variant}-self-auto { align-self: auto !important; } 41 | .flex#{$variant}-self-start { align-self: flex-start !important; } 42 | .flex#{$variant}-self-end { align-self: flex-end !important; } 43 | .flex#{$variant}-self-center { align-self: center !important; } 44 | .flex#{$variant}-self-baseline { align-self: baseline !important; } 45 | .flex#{$variant}-self-stretch { align-self: stretch !important; } 46 | 47 | .flex#{$variant}-order-1 { order: 1 !important; } 48 | .flex#{$variant}-order-2 { order: 2 !important; } 49 | .flex#{$variant}-order-none { order: inherit !important; } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/stories/utilities/Border.stories.jsx: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Utilities/Border' 3 | } 4 | 5 | export const Default = ({}) => .border 6 | 7 | export const Direction = ({}) => ( 8 | 9 | .border-left 10 | .border-top 11 | .border-bottom 12 | .border-right 13 | .border-x 14 | .border-y 15 | 16 | ) 17 | 18 | export const Hide = ({}) => .border-top-0 19 | 20 | export const Style = ({}) => .border-dashed 21 | 22 | export const Rounded = ({}) => ( 23 | 24 | .rounded-0 25 | .rounded-1 26 | .rounded-2 27 | .rounded-3 28 | 32 | .circle 33 | 34 | 35 | ) 36 | 37 | export const RoundedDirection = ({}) => ( 38 | 39 | .rounded-top-2 40 | .rounded-right-2 41 | .rounded-bottom-2 42 | .rounded-left-2 43 | 44 | ) 45 | 46 | export const RoundedResponsive = ({}) => ( 47 | 51 | .border-left 52 | .rounded-sm-1 53 | .rounded-md-2 54 | .rounded-lg-3 55 | 56 | ) 57 | 58 | export const Responsive = ({}) => ( 59 | 63 | .border-left 64 | .border-sm-top 65 | .border-md-right 66 | .border-lg-bottom 67 | 68 | ) 69 | -------------------------------------------------------------------------------- /src/markdown/markdown-body.scss: -------------------------------------------------------------------------------- 1 | // All of our block level items should have the same margin 2 | // stylelint-disable selector-max-type 3 | 4 | // This is styling for generic markdownized text. Anything you put in a 5 | // container with .markdown-body on it should render generally well. It also 6 | // includes some GitHub Flavored Markdown specific styling (like @mentions) 7 | .markdown-body { 8 | // stylelint-disable-next-line primer/typography 9 | font-family: $body-font; 10 | // stylelint-disable-next-line primer/typography 11 | font-size: $h4-size; 12 | // stylelint-disable-next-line primer/typography 13 | line-height: $body-line-height; 14 | word-wrap: break-word; 15 | 16 | // Clearfix on the markdown body 17 | &::before { 18 | display: table; 19 | content: ''; 20 | } 21 | 22 | &::after { 23 | display: table; 24 | clear: both; 25 | content: ''; 26 | } 27 | 28 | > *:first-child { 29 | margin-top: 0 !important; 30 | } 31 | 32 | > *:last-child { 33 | margin-bottom: 0 !important; 34 | } 35 | 36 | // Anchors like . These sometimes end up wrapped around 37 | // text when users mistakenly forget to close the tag or use self-closing tag 38 | // syntax. We don't want them to appear like links. 39 | // FIXME: a:not(:link):not(:visited) would be a little clearer here (and 40 | // possibly faster to match), but it breaks styling of elements due 41 | // to https://bugs.webkit.org/show_bug.cgi?id=142737. 42 | a:not([href]) { 43 | color: inherit; 44 | text-decoration: none; 45 | } 46 | 47 | // Link Colors 48 | .absent { 49 | color: var(--fgColor-danger, var(--color-danger-fg)); 50 | } 51 | 52 | .anchor { 53 | float: left; 54 | padding-right: var(--base-size-4); 55 | // stylelint-disable-next-line primer/spacing 56 | margin-left: -20px; 57 | // stylelint-disable-next-line primer/typography 58 | line-height: $lh-condensed-ultra; 59 | 60 | &:focus { 61 | outline: none; 62 | } 63 | } 64 | 65 | p, 66 | blockquote, 67 | ul, 68 | ol, 69 | dl, 70 | table, 71 | pre, 72 | details { 73 | margin-top: 0; 74 | margin-bottom: var(--base-size-16); 75 | } 76 | 77 | hr { 78 | height: $em-spacer-3; 79 | padding: 0; 80 | margin: var(--base-size-24) 0; 81 | // stylelint-disable-next-line primer/colors 82 | background-color: var(--borderColor-default, var(--color-border-default)); 83 | border: 0; 84 | } 85 | 86 | blockquote { 87 | // stylelint-disable-next-line primer/spacing 88 | padding: 0 1em; 89 | color: var(--fgColor-muted, var(--color-fg-muted)); 90 | // stylelint-disable-next-line primer/borders, primer/colors, declaration-property-value-no-unknown 91 | border-left: 0.25em $border-style var(--borderColor-default, var(--color-border-default)); 92 | 93 | > :first-child { 94 | margin-top: 0; 95 | } 96 | 97 | > :last-child { 98 | margin-bottom: 0; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /docs/stories/utilities/Shadow.stories.jsx: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Utilities/Shadow' 3 | } 4 | 5 | export const Small = ({}) => ( 6 | 7 | .color-shadow-small 8 | 9 | ) 10 | 11 | export const SmallExample = ({}) => ( 12 | 13 | 14 | Organization 15 | 16 | 17 | 18 | Taxidermy live-edge mixtape, keytar tumeric locavore meh selvage deep v letterpress vexillologist lo-fi tousled 19 | church-key thundercats. Brooklyn bicycle rights tousled, marfa actually. 20 | 21 | 22 | 23 | 24 | Create an organization 25 | 26 | 27 | 28 | ) 29 | 30 | export const Medium = ({}) => ( 31 | 32 | .color-shadow-medium 33 | 34 | ) 35 | 36 | export const MediumExample = ({}) => ( 37 | 38 | Serverless architecture 39 | 40 | Build powerful, event-driven, serverless architectures with these open-source libraries and frameworks. 41 | 42 | 43 | 44 | 45 | 12 Repositories 46 | 47 | 48 | 49 | 5 Languages 50 | 51 | 52 | 53 | ) 54 | 55 | export const Large = ({}) => ( 56 | 57 | .color-shadow-large 58 | 59 | ) 60 | 61 | export const LargeExample = ({}) => ( 62 | 63 | 64 | 65 | ) 66 | 67 | export const ExtraLarge = ({}) => ( 68 | 69 | .color-shadow-extra-large 70 | 71 | ) 72 | 73 | export const None = ({}) => ( 74 | 75 | .box-shadow-none 76 | 77 | ) 78 | 79 | -------------------------------------------------------------------------------- /src/navigation/sidenav.scss: -------------------------------------------------------------------------------- 1 | // Side Nav 2 | // 3 | // A vertical list of navigational links, typically used on the left side of a page. 4 | 5 | .SideNav { 6 | background-color: var(--bgColor-muted, var(--color-canvas-subtle)); 7 | } 8 | 9 | .SideNav-item { 10 | position: relative; 11 | display: block; 12 | width: 100%; 13 | // stylelint-disable-next-line primer/spacing 14 | padding: 12px var(--base-size-16); 15 | color: var(--fgColor-default, var(--color-fg-default)); 16 | text-align: left; 17 | background-color: transparent; 18 | border: 0; 19 | // stylelint-disable-next-line primer/borders, primer/colors 20 | border-top: $border-width $border-style var(--borderColor-muted, var(--color-border-muted)); 21 | 22 | &:first-child { 23 | border-top: 0; 24 | } 25 | 26 | &:last-child { 27 | // makes sure there is a "bottom border" in case the list is not long enough 28 | // stylelint-disable-next-line primer/box-shadow, declaration-property-value-no-unknown 29 | box-shadow: 0 $border-width 0 var(--borderColor-default, var(--color-border-default)); 30 | } 31 | 32 | // Bar on the left 33 | &::before { 34 | position: absolute; 35 | top: 0; 36 | bottom: 0; 37 | left: 0; 38 | z-index: 1; 39 | width: 2px; 40 | pointer-events: none; 41 | content: ''; 42 | } 43 | } 44 | 45 | // States 46 | 47 | .SideNav-item:hover { 48 | text-decoration: none; 49 | background-color: var(--bgColor-neutral-muted, var(--color-neutral-subtle)); 50 | } 51 | 52 | .SideNav-item:active { 53 | background-color: var(--bgColor-muted, var(--color-canvas-subtle)); 54 | } 55 | 56 | .SideNav-item[aria-current]:not([aria-current='false']), 57 | .SideNav-item[aria-selected='true'] { 58 | background-color: var(--sideNav-bgColor-selected, var(--color-sidenav-selected-bg)); 59 | 60 | // Bar on the left 61 | &::before { 62 | // stylelint-disable-next-line primer/colors 63 | background-color: var(--underlineNav-borderColor-active, var(--color-primer-border-active)); 64 | } 65 | } 66 | 67 | // Icon 68 | // 69 | // Makes sure multiple icons are vertically aligned 70 | 71 | .SideNav-icon { 72 | width: 16px; 73 | margin-right: var(--base-size-8); 74 | color: var(--fgColor-muted, var(--color-fg-muted)); 75 | } 76 | 77 | // Sub Nav 78 | // 79 | // A more lightweight version, suited as a sub nav 80 | 81 | .SideNav-subItem { 82 | position: relative; 83 | display: block; 84 | width: 100%; 85 | padding: var(--base-size-4) 0; 86 | color: var(--fgColor-accent, var(--color-accent-fg)); 87 | text-align: left; 88 | background-color: transparent; 89 | border: 0; 90 | } 91 | 92 | .SideNav-subItem:hover { 93 | color: var(--fgColor-default, var(--color-fg-default)); 94 | text-decoration: none; 95 | } 96 | 97 | .SideNav-subItem[aria-current]:not([aria-current='false']), 98 | .SideNav-subItem[aria-selected='true'] { 99 | // stylelint-disable-next-line primer/typography 100 | font-weight: $font-weight-semibold; 101 | color: var(--fgColor-default, var(--color-fg-default)); 102 | } 103 | -------------------------------------------------------------------------------- /src/utilities/visibility-display.scss: -------------------------------------------------------------------------------- 1 | // Visibility and display utilities 2 | 3 | // Responsive display utilities 4 | @each $breakpoint, $variant in $responsive-variants { 5 | @include breakpoint($breakpoint) { 6 | @each $display in $display-values { 7 | .d#{$variant}-#{$display} { display: $display !important; } 8 | } 9 | } 10 | } 11 | 12 | .v-hidden { visibility: hidden !important; } 13 | .v-visible { visibility: visible !important; } 14 | 15 | // Hide utilities for each breakpoint 16 | // Each hide utility only applies to one breakpoint range. 17 | @media (max-width: $width-sm - 0.02px) { 18 | .hide-sm { 19 | display: none !important; 20 | } 21 | } 22 | 23 | @media (min-width: $width-sm) and (max-width: $width-md - 0.02px) { 24 | .hide-md { 25 | display: none !important; 26 | } 27 | } 28 | 29 | @media (min-width: $width-md) and (max-width: $width-lg - 0.02px) { 30 | .hide-lg { 31 | display: none !important; 32 | } 33 | } 34 | 35 | @media (min-width: $width-lg) { 36 | .hide-xl { 37 | display: none !important; 38 | } 39 | } 40 | 41 | // Show/Hide Viewport range utilities 42 | 43 | .show-whenNarrow, 44 | .show-whenRegular, 45 | .show-whenWide, 46 | .show-whenRegular.hide-whenWide { 47 | display: none !important; 48 | } 49 | 50 | .hide-whenNarrow, 51 | .hide-whenRegular, 52 | .hide-whenWide { 53 | display: block !important; 54 | } 55 | 56 | @media (max-width: $width-md - 0.02px) { 57 | .show-whenNarrow { 58 | display: block !important; 59 | } 60 | 61 | .hide-whenNarrow { 62 | display: none !important; 63 | } 64 | } 65 | 66 | @media (min-width: $width-md) { 67 | .show-whenRegular, 68 | .show-whenRegular.hide-whenWide { 69 | display: block !important; 70 | } 71 | 72 | .hide-whenRegular { 73 | display: none !important; 74 | } 75 | } 76 | 77 | // The width of a `wide` viewport range may change as we're finalizing 78 | // the Primer primitives viewport ranges proposal 79 | @media (min-width: $width-xl) { 80 | .show-whenWide { 81 | display: block !important; 82 | } 83 | 84 | .hide-whenWide, 85 | .show-whenRegular.hide-whenWide { 86 | display: none !important; 87 | } 88 | } 89 | 90 | /* Set the table-layout to fixed */ 91 | .table-fixed { table-layout: fixed !important; } 92 | 93 | // Only display content to screen readers 94 | // 95 | // See: http://a11yproject.com/posts/how-to-hide-content/ 96 | .sr-only { 97 | position: absolute; 98 | width: 1px; 99 | height: 1px; 100 | padding: 0; 101 | overflow: hidden; 102 | clip-path: rect(0 0 0 0); 103 | // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1241631 104 | overflow-wrap: normal; 105 | border: 0; 106 | } 107 | 108 | // Only display content on focus 109 | .show-on-focus { 110 | position: absolute !important; 111 | 112 | &:not(:focus) { 113 | width: 1px !important; 114 | height: 1px !important; 115 | padding: 0 !important; 116 | overflow: hidden !important; 117 | clip: rect(1px, 1px, 1px, 1px) !important; 118 | border: 0 !important; 119 | } 120 | 121 | &:focus { 122 | z-index: 999; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /script/analyze-variables.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import postcss from 'postcss' 3 | import {join} from 'path' 4 | import fs from 'fs' 5 | import atImport from 'postcss-import' 6 | import syntax from 'postcss-scss' 7 | import calc from 'postcss-calc' 8 | import simpleVars from 'postcss-simple-vars' 9 | 10 | import { dirname } from 'path'; 11 | import { fileURLToPath } from 'url'; 12 | 13 | const __dirname = dirname(fileURLToPath(import.meta.url)); 14 | 15 | const processor = postcss([ 16 | atImport({path: ['src']}), 17 | collectVariables(), 18 | simpleVars({includePaths: [join(__dirname, '../src/support/variables')]}) 19 | ]) 20 | 21 | async function analyzeVariables(fileName) { 22 | const contents = fs.readFileSync(fileName, 'utf8') 23 | 24 | const result = await processor.process(contents, {from: fileName, map: false, syntax}) 25 | for (const message of result.messages) { 26 | if (message.plugin === 'postcss-simple-vars' && message.type === 'variable') { 27 | if (!result.variables[`$${message.name}`].values.includes(message.value)) { 28 | result.variables[`$${message.name}`].values.push(message.value) 29 | } 30 | let computed = message.value 31 | try { 32 | const c = `--temp-property: calc(${message.value})`.replace('round(', '(') 33 | computed = postcss().use(calc()).process(c).css 34 | computed = computed.replace('--temp-property: ', '') 35 | } catch (e) { 36 | // Couldn't calculate because value might not be a number 37 | } 38 | result.variables[`$${message.name}`].computed = computed 39 | } 40 | } 41 | return result.variables 42 | } 43 | 44 | function checkNode(node) { 45 | const allowedFuncts = ['var', 'round', 'cubic-bezier'] 46 | const funcMatch = node.value.match(/([^\s]*)\(/) 47 | let approvedMatch = true 48 | if (funcMatch && !allowedFuncts.includes(funcMatch[1])) { 49 | approvedMatch = false 50 | } 51 | return node.variable && approvedMatch 52 | } 53 | 54 | function collectVariables() { 55 | return { 56 | postcssPlugin: 'prepare-contents', 57 | prepare(result) { 58 | const variables = {} 59 | return { 60 | AtRule(atRule) { 61 | atRule.remove() 62 | }, 63 | Comment(comment) { 64 | comment.remove() 65 | }, 66 | Declaration(node) { 67 | if (checkNode(node)) { 68 | node.value = node.value.replace(' !default', '') 69 | const fileName = node.source.input.file.replace(`${process.cwd()}/`, '') 70 | variables[node.prop] = { 71 | // computed: value, 72 | values: [node.value], 73 | source: { 74 | path: fileName, 75 | line: node.source.start.line 76 | } 77 | } 78 | } else { 79 | node.remove() 80 | } 81 | }, 82 | OnceExit() { 83 | result.variables = variables 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | export default analyzeVariables 91 | 92 | ;(async () => { 93 | const args = process.argv.slice(2) 94 | const file = args.length ? args.shift() : 'src/support/index.scss' 95 | const variables = await analyzeVariables(file) 96 | JSON.stringify(variables, null, 2) 97 | })() 98 | -------------------------------------------------------------------------------- /src/autocomplete/suggester.scss: -------------------------------------------------------------------------------- 1 | // Needs refactoring 2 | // stylelint-disable selector-max-type, selector-no-qualifying-type 3 | 4 | .suggester { 5 | position: relative; 6 | top: 0; 7 | left: 0; 8 | min-width: 180px; 9 | padding: 0; 10 | margin: 0; 11 | margin-top: var(--base-size-24); 12 | list-style: none; 13 | cursor: pointer; 14 | background: var(--overlay-bgColor, var(--color-canvas-overlay)); 15 | // stylelint-disable-next-line primer/borders, primer/colors 16 | border: $border-width $border-style var(--borderColor-default, var(--color-border-default)); 17 | // stylelint-disable-next-line primer/borders 18 | border-radius: $border-radius; 19 | box-shadow: var(--shadow-resting-medium, var(--color-shadow-medium)); 20 | 21 | li { 22 | display: block; 23 | padding: var(--base-size-4) var(--base-size-8); 24 | // stylelint-disable-next-line primer/typography 25 | font-weight: $font-weight-semibold; 26 | // stylelint-disable-next-line primer/borders, primer/colors 27 | border-bottom: $border-width $border-style var(--borderColor-muted, var(--color-border-muted)); 28 | 29 | small { 30 | // stylelint-disable-next-line primer/typography 31 | font-weight: $font-weight-normal; 32 | color: var(--fgColor-muted, var(--color-fg-muted)); 33 | } 34 | 35 | &:last-child { 36 | border-bottom: 0; 37 | // stylelint-disable-next-line primer/borders 38 | border-bottom-right-radius: $border-radius; 39 | // stylelint-disable-next-line primer/borders 40 | border-bottom-left-radius: $border-radius; 41 | } 42 | 43 | &:first-child { 44 | // stylelint-disable-next-line primer/borders 45 | border-top-left-radius: $border-radius; 46 | // stylelint-disable-next-line primer/borders 47 | border-top-right-radius: $border-radius; 48 | } 49 | 50 | &:hover { 51 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 52 | text-decoration: none; 53 | background: var(--bgColor-accent-emphasis, var(--color-accent-emphasis)); 54 | 55 | small { 56 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 57 | } 58 | 59 | .octicon { 60 | color: inherit !important; 61 | } 62 | } 63 | 64 | &[aria-selected='true'], 65 | &.navigation-focus { 66 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 67 | text-decoration: none; 68 | background: var(--bgColor-accent-emphasis, var(--color-accent-emphasis)); 69 | 70 | small { 71 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 72 | } 73 | 74 | .octicon { 75 | color: inherit !important; 76 | } 77 | } 78 | } 79 | } 80 | 81 | .suggester-container { 82 | position: absolute; 83 | top: 0; 84 | left: 0; 85 | z-index: 30; 86 | } 87 | 88 | // Responsive 89 | 90 | .page-responsive { 91 | @media (max-width: $width-sm) { 92 | .suggester-container { 93 | right: var(--base-size-8) !important; 94 | left: var(--base-size-8) !important; 95 | } 96 | 97 | .suggester li { 98 | padding: var(--base-size-8) var(--base-size-16); 99 | } 100 | } 101 | } 102 | --------------------------------------------------------------------------------
s) 35 | > code { 36 | padding: 0; 37 | margin: 0; 38 | word-break: normal; 39 | white-space: pre; 40 | background: transparent; 41 | border: 0; 42 | } 43 | } 44 | 45 | .highlight { 46 | margin-bottom: var(--base-size-16); 47 | 48 | pre { 49 | margin-bottom: 0; 50 | word-break: normal; 51 | } 52 | } 53 | 54 | .highlight pre, 55 | pre { 56 | padding: var(--base-size-16); 57 | overflow: auto; 58 | // stylelint-disable-next-line primer/typography 59 | font-size: 85%; 60 | // stylelint-disable-next-line primer/typography 61 | line-height: 1.45; 62 | color: var(--fgColor-default, var(--color-fg-default)); 63 | background-color: var(--bgColor-muted, var(--color-canvas-subtle)); 64 | // stylelint-disable-next-line primer/borders 65 | border-radius: $border-radius; 66 | } 67 | 68 | pre code, 69 | pre tt { 70 | display: inline; 71 | padding: 0; 72 | margin: 0; 73 | overflow: visible; 74 | line-height: inherit; 75 | word-wrap: normal; 76 | background-color: transparent; 77 | border: 0; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/forms/input-group.scss: -------------------------------------------------------------------------------- 1 | .input-group { 2 | display: table; 3 | 4 | .form-control { 5 | position: relative; 6 | width: 100%; 7 | 8 | &:focus { 9 | z-index: 2; 10 | } 11 | 12 | + .btn { 13 | margin-left: 0; 14 | } 15 | } 16 | 17 | // For when you want the input group to behave like inline-block. 18 | &.inline { 19 | display: inline-table; 20 | } 21 | 22 | // within input group, if button exists change focus styles to match input (no offset) 23 | &:focus-within { 24 | // stylelint-disable-next-line selector-max-type 25 | button { 26 | outline-offset: 0; 27 | } 28 | } 29 | 30 | // Autocomplete with embedded icon 31 | .form-control.autocomplete-embedded-icon-wrap { 32 | display: inline-flex; 33 | padding: calc(var(--base-size-4) * 1.25) var(--base-size-8); 34 | } 35 | } 36 | 37 | .input-group .form-control, 38 | .input-group-button { 39 | display: table-cell; 40 | } 41 | 42 | .input-group-button { 43 | width: 1%; 44 | vertical-align: middle; // Match the inputs 45 | } 46 | 47 | .input-group-button--autocomplete-embedded-icon { 48 | vertical-align: bottom; 49 | } 50 | 51 | .input-group .form-control:first-child, 52 | .input-group-button:first-child .btn { 53 | border-top-right-radius: 0; 54 | border-bottom-right-radius: 0; 55 | } 56 | 57 | .input-group .form-control:first-child, 58 | .input-group-button:first-child .btn:not(.btn-primary) { 59 | border-color: var(--control-borderColor-rest, var(--color-border-default)); 60 | } 61 | 62 | .input-group-button:first-child .btn { 63 | // stylelint-disable-next-line primer/spacing 64 | margin-right: -1px; 65 | } 66 | 67 | .input-group .form-control:last-child, 68 | .input-group-button:last-child .btn { 69 | border-top-left-radius: 0; 70 | border-bottom-left-radius: 0; 71 | } 72 | 73 | .input-group .form-control:last-child, 74 | .input-group-button:last-child .btn:not(.btn-primary) { 75 | border-color: var(--control-borderColor-rest, var(--color-border-default)); 76 | } 77 | 78 | .input-group-button:last-child .btn { 79 | // stylelint-disable-next-line primer/spacing 80 | margin-left: -1px; 81 | } 82 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing a new version of Primer CSS 🎉 2 | 3 | ## Prepare the release (in `primer/css`) 4 | 5 | The release process is automated by [changesets]. After you familiarize yourself with how they work. We use an [action workflow integrated in ci](https://github.com/atlassian/changesets#integrating-with-ci). 6 | 7 | 1. Visit the pull requests page and find the [latest Release tracking pr from primer-css](https://github.com/primer/css/pulls/primer-css). If there isn't one, we'll need to build the next release by merging in PRs with changeset files. 8 | 9 | ## Test the release candidate (in `github/github`): 10 | 11 | 1. Create a new branch in the `github/github` repo, name it `primer-`. 12 | 13 | 1. Update the Primer CSS version to the published release candidate with: 14 | 15 | ```sh 16 | bin/npm install @primer/css@-rc. 17 | ``` 18 | 19 | Then commit and push the changes to `package.json`, `package-lock.json`, `LICENSE` and `vendor/npm`. 20 | 21 | 1. If you need to make changes to github/github due to the Primer CSS release, do them in a branch and merge _that_ into your release branch after testing. 22 | 23 | 1. Add or re-request reviewers and fix any breaking tests. 24 | 25 | 1. Test on review-lab. 26 | 27 | 28 | ## Publish the release (in `primer/css`) 29 | 30 | 1. If the release PR got approved and you've done necessary testing, merge it. 31 | 32 | After tests run, the docs site will be deployed and `@primer/css` will be published with your changes to the `latest` dist-tag. You can check [npm](https://www.npmjs.com/package/@primer/css?activeTab=versions) to see if actions has finished. 33 | 34 | 2. Done! 🎉 35 | 36 | 37 | ## Update github.com (in `github/github`): 38 | 39 | 1. Install the latest published version in the same `primer-` branch created earlier with: 40 | 41 | ``` 42 | bin/npm install @primer/css@ -w ./npm-workspaces/primer/ 43 | ``` 44 | 45 | Then commit and push the changes to `package.json`, `package-lock.json`, `LICENSE` and `vendor/npm`. 46 | 47 | 1. Fix any breaking tests. 48 | 49 | 1. Deploy! :rocket: 50 | 51 | [changelog]: ../CHANGELOG.md 52 | [changesets]: https://github.com/atlassian/changesets 53 | -------------------------------------------------------------------------------- /src/forms/radio-group.scss: -------------------------------------------------------------------------------- 1 | // Tab like radio group 2 | 3 | .radio-group { 4 | @include clearfix; 5 | } 6 | 7 | .radio-label { 8 | float: left; 9 | // stylelint-disable-next-line primer/spacing 10 | padding: 6px var(--base-size-16) 6px calc(var(--base-size-16) + 12px + var(--base-size-8)); // 12px is the size of the radio-input 11 | // stylelint-disable-next-line primer/spacing 12 | margin-left: -1px; 13 | // stylelint-disable-next-line primer/typography 14 | font-size: $body-font-size; 15 | // stylelint-disable-next-line primer/typography 16 | line-height: 20px; // Specifically not inherit our `` default 17 | color: var(--fgColor-default, var(--color-fg-default)); 18 | cursor: pointer; 19 | // stylelint-disable-next-line primer/borders, primer/colors 20 | border: $border-width $border-style var(--borderColor-default, var(--color-border-default)); 21 | 22 | :checked + & { 23 | position: relative; 24 | z-index: 1; 25 | border-color: var(--borderColor-accent-emphasis, var(--color-accent-emphasis)); 26 | } 27 | 28 | &:first-of-type { 29 | margin-left: 0; 30 | // stylelint-disable-next-line primer/borders 31 | border-top-left-radius: $border-radius; 32 | // stylelint-disable-next-line primer/borders 33 | border-bottom-left-radius: $border-radius; 34 | } 35 | 36 | &:last-of-type { 37 | // stylelint-disable-next-line primer/borders 38 | border-top-right-radius: $border-radius; 39 | // stylelint-disable-next-line primer/borders 40 | border-bottom-right-radius: $border-radius; 41 | } 42 | 43 | .octicon { 44 | margin-left: var(--base-size-4); 45 | color: var(--fgColor-muted, var(--color-fg-subtle)); 46 | } 47 | } 48 | 49 | .radio-input { 50 | z-index: 3; 51 | float: left; 52 | // stylelint-disable-next-line primer/spacing 53 | margin: 10px calc(var(--base-size-32) * -1) 0 var(--base-size-16); 54 | 55 | &:disabled { 56 | position: relative; // enables z-index 57 | 58 | + .radio-label { 59 | color: var(--fgColor-disabled, var(--color-primer-fg-disabled)); 60 | cursor: default; 61 | background-color: var(--bgColor-neutral-muted, var(--color-neutral-subtle)); 62 | 63 | .octicon { 64 | color: inherit; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/stories/principles/SCSS.mdx: -------------------------------------------------------------------------------- 1 | # SCSS 2 | 3 | ## Spacing 4 | 5 | - Use soft-tabs with a two space indent. Spaces are the only way to guarantee code renders the same in any person's environment. 6 | - Put spaces after `:` in property declarations. 7 | - Put spaces before `{` in rule declarations. 8 | - Put line breaks between rulesets. 9 | - When grouping selectors, keep individual selectors to a single line. 10 | - Place closing braces of declaration blocks on a new line. 11 | - Each declaration should appear on its own line for more accurate error reporting. 12 | 13 | ## Formatting 14 | 15 | - Use hex color codes `#000` unless using `rgba()` in raw CSS (the SCSS `rgba()` function is overloaded to accept hex colors, as in `rgba(#000, .5)`). 16 | - Use `//` for comment blocks (instead of `/* */`). 17 | - Avoid specifying units for zero values, e.g., `margin: 0;` instead of `margin: 0px;`. 18 | - Strive to limit use of shorthand declarations to instances where you must explicitly set all the available values. 19 | 20 | ## Guidelines for using Sass features (WIP) 21 | 22 | _When and when not to create:_ 23 | 24 | - Variables 25 | - Mixins 26 | - Functions 27 | 28 | ## Lint rules (WIP) 29 | 30 | As a rule of thumb, avoid unnecessary nesting in SCSS. At most, aim for three levels. If you cannot help it, step back and rethink your overall strategy (either the specificity needed, or the layout of the nesting). 31 | 32 | - Nesting depth: 3 33 | - Extends: no 34 | - Duplicated properties: no 35 | - Final new lines: yes 36 | - Hex length: 6 37 | - ID selectors: no 38 | - Leading zero: yes 39 | - Naming format: lowercase-with-hyphens 40 | - Property order: see [HTML property order](./html) 41 | - Selector depth: 3 42 | - Single line properties: yes 43 | 44 | ## Examples 45 | 46 | Here are some good examples that apply the above guidelines: 47 | 48 | ```css 49 | // Example of good basic formatting practices 50 | .styleguide-format { 51 | color: #000; 52 | background-color: rgba(0, 0, 0, 0.5); 53 | border: 1px solid #0f0; 54 | } 55 | 56 | // Example of individual selectors getting their own lines (for error reporting) 57 | .multiple, 58 | .classes, 59 | .get-new-lines { 60 | display: block; 61 | } 62 | 63 | // Avoid unnecessary shorthand declarations 64 | .not-so-good { 65 | margin: 0 0 20px; 66 | } 67 | .good { 68 | margin-bottom: 20px; 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /script/stylelint-add-disables.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S NODE_NO_WARNINGS=1 node 2 | // Disables stylelint rules in SASS/CSS files with next-line comments. This is 3 | // useful when introducing a new rule that causes many failures. The comments 4 | // can be fixed and removed at while updating the file later. 5 | // 6 | // Usage: 7 | // 8 | // script/stylelint-add-disables.js src/**/*.scss 9 | 10 | import fs from 'fs' 11 | import {execFile} from 'child_process' 12 | 13 | execFile('stylelint', ['--quiet', '--formatter', 'json', process.argv[2]], (error, stdout, stderr) => { 14 | for (const result of JSON.parse(stdout || stderr || '[]')) { 15 | const filename = result.source 16 | const jsLines = fs.readFileSync(filename, 'utf8').split('\n') 17 | const offensesByLine = {} 18 | let addedLines = 0 19 | 20 | // Produces {47: ['github/no-d-none', 'github/no-blur'], 83: ['github/no-blur']} 21 | for (const message of result.warnings) { 22 | if (offensesByLine[message.lin] && offensesByLine[message.line].includes(message.rule)) { 23 | continue 24 | } else if (offensesByLine[message.line]) { 25 | offensesByLine[message.line].push(message.rule) 26 | } else { 27 | offensesByLine[message.line] = [message.rule] 28 | } 29 | } 30 | 31 | for (const line of Object.keys(offensesByLine)) { 32 | const lineIndex = Number(line) - 1 + addedLines 33 | const previousLine = jsLines[lineIndex - 1] 34 | const ruleIds = Array.from(new Set(offensesByLine[line])).join(', ') 35 | if (isDisableComment(previousLine)) { 36 | if (previousLine.match(/\s?\*\/$/)) { 37 | jsLines[lineIndex - 1] = previousLine.replace(/\s?\*\/$/, `, ${ruleIds} */`) 38 | } else { 39 | jsLines[lineIndex - 1] = `${jsLines[lineIndex - 1]}, ${ruleIds}` 40 | } 41 | } else { 42 | const leftPad = ' '.repeat(jsLines[lineIndex].match(/^\s*/g)[0].length) 43 | jsLines.splice(lineIndex, 0, `${leftPad}// stylelint-disable-next-line ${ruleIds}`) 44 | addedLines += 1 45 | } 46 | } 47 | 48 | if (result.warnings.length !== 0) { 49 | fs.writeFileSync(filename, jsLines.join('\n'), 'utf8') 50 | } 51 | } 52 | }) 53 | 54 | function isDisableComment(line) { 55 | return line && line.match(/\/(\*|\/) stylelint-disable(-next-line)? .+(\*\/)?/) 56 | } 57 | -------------------------------------------------------------------------------- /__tests__/utils/css.js: -------------------------------------------------------------------------------- 1 | import {join, dirname} from 'path' 2 | import semver from 'semver' 3 | import { fileURLToPath } from 'url' 4 | import fs from 'fs' 5 | 6 | const __dirname = dirname(fileURLToPath(import.meta.url)) 7 | 8 | const currentPath = join(__dirname, '../../') 9 | const lastPath = join(__dirname, '../../tmp/node_modules/@primer/css') 10 | 11 | function diffLists(before, after) { 12 | const added = [...new Set(after.filter(value => !before.includes(value)))] 13 | const removed = [...new Set(before.filter(value => !after.includes(value)))] 14 | return { 15 | changed: added.length + removed.length, 16 | added, 17 | removed 18 | } 19 | } 20 | 21 | function getSelectors(versionPath) { 22 | const stats = JSON.parse(fs.readFileSync(join(versionPath, './stats/primer.json'))) 23 | return stats.selectors.values 24 | } 25 | 26 | function getVariables(versionPath) { 27 | const variables = JSON.parse(fs.readFileSync(join(versionPath, './variables.json'))) 28 | return Object.keys(variables) 29 | } 30 | 31 | export function getCurrentVersion() { 32 | const pkg = JSON.parse(fs.readFileSync(join(currentPath, './package.json'))) 33 | return semver.parse(pkg.version) 34 | } 35 | 36 | export function getPackageStats(packageName) { 37 | const stats = JSON.parse(fs.readFileSync(join(currentPath, './dist', `./stats/${packageName}.json`))) 38 | return stats 39 | } 40 | 41 | function getDeprecations(versionPath) { 42 | const deprecations = JSON.parse(fs.readFileSync(join(versionPath, './deprecations.json'))) 43 | return deprecations 44 | } 45 | 46 | function currentVersionSelectors() { 47 | return getSelectors(join(currentPath, './dist')) 48 | } 49 | 50 | function currentVersionVariables() { 51 | return getVariables(join(currentPath, './dist')) 52 | } 53 | 54 | export function currentVersionDeprecations() { 55 | return getDeprecations(join(currentPath, './dist')) 56 | } 57 | 58 | function lastVersionSelectors() { 59 | return getSelectors(join(lastPath, './dist')) 60 | } 61 | 62 | function lastVersionVariables() { 63 | return getVariables(join(lastPath, './dist')) 64 | } 65 | 66 | export function getSelectorDiff() { 67 | return diffLists(lastVersionSelectors(), currentVersionSelectors()) 68 | } 69 | 70 | export function getVariableDiff() { 71 | return diffLists(lastVersionVariables(), currentVersionVariables()) 72 | } 73 | -------------------------------------------------------------------------------- /src/marketing/utilities/layout.scss: -------------------------------------------------------------------------------- 1 | // Layout utilities 2 | 3 | // Responsive utilities to position content 4 | // No utilities for sm and xl breakpoints 5 | @each $breakpoint, $variant in $marketing-position-variants { 6 | @include breakpoint($breakpoint) { 7 | @each $scale, $size in $spacer-map-extended { 8 | @if ($size != 0 or $variant != '') { 9 | // stylelint-disable-next-line primer/spacing 10 | .top#{$variant}-#{$scale} { top: $size !important; } 11 | // stylelint-disable-next-line primer/spacing 12 | .right#{$variant}-#{$scale} { right: $size !important; } 13 | // stylelint-disable-next-line primer/spacing 14 | .bottom#{$variant}-#{$scale} { bottom: $size !important; } 15 | // stylelint-disable-next-line primer/spacing 16 | .left#{$variant}-#{$scale} { left: $size !important; } 17 | } 18 | 19 | @if ($size != 0) { 20 | // stylelint-disable-next-line primer/spacing 21 | .top#{$variant}-n#{$scale} { top: -$size !important; } 22 | // stylelint-disable-next-line primer/spacing 23 | .right#{$variant}-n#{$scale} { right: -$size !important; } 24 | // stylelint-disable-next-line primer/spacing 25 | .bottom#{$variant}-n#{$scale} { bottom: -$size !important; } 26 | // stylelint-disable-next-line primer/spacing 27 | .left#{$variant}-n#{$scale} { left: -$size !important; } 28 | } 29 | } 30 | } 31 | } 32 | 33 | // Negative offset columns 34 | @each $breakpoint, $variant in $responsive-variants { 35 | @include breakpoint($breakpoint) { 36 | @for $offset from 1 through 7 { 37 | // stylelint-disable-next-line primer/spacing 38 | .offset#{$variant}-n#{$offset} { margin-left: -($offset * 0.0833333333 * 100%); } 39 | } 40 | } 41 | } 42 | 43 | // Width and height utilities, especially needed when the 44 | // dimensions of an image are set in HTML 45 | .width-auto { width: auto !important; } 46 | .height-auto { height: auto !important; } 47 | 48 | // Make an object fill its parent 49 | .object-fit-cover { object-fit: cover !important; } 50 | 51 | // Handling z-index 52 | .z-1 { z-index: 1 !important; } 53 | .z-2 { z-index: 2 !important; } 54 | .z-3 { z-index: 3 !important; } 55 | 56 | // Negative z-index 57 | .z-n1 { z-index: -1 !important; } 58 | .z-n2 { z-index: -2 !important; } 59 | -------------------------------------------------------------------------------- /src/navigation/filter-list.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable selector-max-specificity 2 | 3 | // Filters list 4 | // 5 | // A vertical list of filters. 6 | .filter-list { 7 | list-style-type: none; 8 | 9 | &.small .filter-item { 10 | // stylelint-disable-next-line primer/spacing 11 | padding: 6px 12px; 12 | // stylelint-disable-next-line primer/typography 13 | font-size: $font-size-small; 14 | } 15 | 16 | &.pjax-active .filter-item { 17 | color: var(--fgColor-muted, var(--color-fg-muted)); 18 | background-color: transparent; 19 | 20 | &.pjax-active { 21 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 22 | background-color: var(--bgColor-accent-emphasis, var(--color-accent-emphasis)); 23 | } 24 | } 25 | } 26 | 27 | .filter-item { 28 | position: relative; 29 | display: block; 30 | padding: var(--base-size-8) var(--base-size-16); 31 | margin-bottom: var(--base-size-4); 32 | overflow: hidden; 33 | // stylelint-disable-next-line primer/typography 34 | font-size: $h5-size; 35 | color: var(--fgColor-muted, var(--color-fg-muted)); 36 | text-decoration: none; 37 | text-overflow: ellipsis; 38 | white-space: nowrap; 39 | cursor: pointer; 40 | // stylelint-disable-next-line primer/borders 41 | border-radius: $border-radius; 42 | 43 | &:hover { 44 | text-decoration: none; 45 | background-color: var(--bgColor-muted, var(--color-canvas-subtle)); 46 | } 47 | 48 | &.selected, 49 | &[aria-selected='true'], 50 | &[aria-current]:not([aria-current='false']) { 51 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 52 | background-color: var(--bgColor-accent-emphasis, var(--color-accent-emphasis)); 53 | 54 | // fallback :focus state 55 | &:focus { 56 | @include focusOutlineOnEmphasis; 57 | 58 | // remove fallback :focus if :focus-visible is supported 59 | &:not(:focus-visible) { 60 | outline: solid 1px transparent; 61 | box-shadow: none; 62 | } 63 | } 64 | 65 | // default focus state 66 | &:focus-visible { 67 | @include focusOutlineOnEmphasis; 68 | } 69 | } 70 | 71 | .count { 72 | float: right; 73 | // stylelint-disable-next-line primer/typography 74 | font-weight: $font-weight-bold; 75 | } 76 | 77 | .bar { 78 | position: absolute; 79 | top: var(--base-size-2); 80 | right: 0; 81 | bottom: var(--base-size-2); 82 | z-index: -1; 83 | display: inline-block; 84 | background-color: var(--bgColor-neutral-muted, var(--color-neutral-subtle)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Primer CSS 2 | The CSS implementation of GitHub's Primer Design System 3 | 4 | [](https://www.npmjs.com/package/@primer/css) 5 | [](https://github.com/primer/css/actions/workflows/ci.yml) 6 | [](https://github.com/primer/css/graphs/contributors) 7 | [](https://github.com/primer/css/commits/main) 8 | [](https://github.com/primer/css/blob/main/LICENSE) 9 | 10 | > :warning: **This project is in KTLO mode!** Use existing utility classes from this project where needed. For more complete patterns that include styling and markup, please use [primer/react](https://github.com/primer/react) or, if necessary, [primer/view_components](https://github.com/primer/view_components). 11 | 12 | ## Documentation 13 | Our documentation site lives at [primer.style/css](https://primer.style/css). You'll be able to find detailed documentation on getting started, all of the components, our theme, our principles, and more. 14 | 15 | ## Install 16 | This repository is distributed with [npm]. After [installing npm][install-npm], you can install `@primer/css` with this command: 17 | 18 | ```sh 19 | npm install --save @primer/css 20 | ``` 21 | 22 | ## Usage 23 | The included source files are written in [Sass] using SCSS syntax. Once installed, you can add your project's `node_modules` directory to your Sass [include paths](https://github.com/sass/node-sass#includepaths) (AKA [load paths](http://technology.customink.com/blog/2014/10/09/understanding-and-using-sass-load-paths/) in Ruby), then import it like this: 24 | 25 | ```scss 26 | @import "@primer/css/index.scss"; 27 | ``` 28 | 29 | You can import individual Primer modules directly from the `@primer/css` package: 30 | 31 | ```scss 32 | @import "@primer/css/core/index.scss"; 33 | @import "@primer/css/product/index.scss"; 34 | @import "@primer/css/marketing/index.scss"; 35 | ``` 36 | 37 | ## Development 38 | See [DEVELOP.md](DEVELOP.md) for development docs. 39 | 40 | ## Releasing (for GitHub staff) 41 | You can find docs about our release process in [RELEASING.md](RELEASING.md). 42 | 43 | ## License 44 | 45 | [MIT](./LICENSE) © [GitHub](https://github.com/) 46 | 47 | 48 | [install-npm]: https://docs.npmjs.com/getting-started/installing-node 49 | [npm]: https://www.npmjs.com/ 50 | [sass]: http://sass-lang.com/ 51 | -------------------------------------------------------------------------------- /src/markdown/lists.scss: -------------------------------------------------------------------------------- 1 | // Base styles 2 | // stylelint-disable selector-no-qualifying-type 3 | // stylelint-disable selector-max-type 4 | .markdown-body { 5 | // Lists, Blockquotes & Such 6 | ul, 7 | ol { 8 | // stylelint-disable-next-line primer/spacing 9 | padding-left: 2em; 10 | 11 | &.no-list { 12 | padding: 0; 13 | list-style-type: none; 14 | } 15 | } 16 | 17 | ol[type='a s'] { 18 | list-style-type: lower-alpha; 19 | } 20 | 21 | ol[type='A s'] { 22 | list-style-type: upper-alpha; 23 | } 24 | 25 | ol[type='i s'] { 26 | list-style-type: lower-roman; 27 | } 28 | 29 | ol[type='I s'] { 30 | list-style-type: upper-roman; 31 | } 32 | 33 | ol[type='1'] { 34 | list-style-type: decimal; 35 | } 36 | 37 | // Reset style to decimal (HTML default) specifically for AsciiDoc 38 | // construction (doesn't affect MarkDown) 39 | div > ol:not([type]) { 40 | list-style-type: decimal; 41 | } 42 | 43 | // Did someone complain about list spacing? Encourage them 44 | // to create the spacing with their markdown formatting. 45 | // List behavior should be controled by the markup, not the css. 46 | // 47 | // For lists with padding between items, use blank 48 | // lines between items. This will generate paragraphs with 49 | // padding to space things out. 50 | // 51 | // - item 52 | // 53 | // - item 54 | // 55 | // - item 56 | // 57 | // For list without padding, don't use blank lines. 58 | // 59 | // - item 60 | // - item 61 | // - item 62 | // 63 | // Modifying the css to emulate these behaviors merely brakes 64 | // one case in the process of solving another. Don't change 65 | // this unless it's really really a bug. 66 | ul ul, 67 | ul ol, 68 | ol ol, 69 | ol ul { 70 | margin-top: 0; 71 | margin-bottom: 0; 72 | } 73 | 74 | li > p { 75 | margin-top: var(--base-size-16); 76 | } 77 | 78 | li + li { 79 | // stylelint-disable-next-line primer/spacing 80 | margin-top: $em-spacer-3; 81 | } 82 | 83 | dl { 84 | padding: 0; 85 | 86 | dt { 87 | padding: 0; 88 | margin-top: var(--base-size-16); 89 | // stylelint-disable-next-line primer/typography 90 | font-size: 1em; 91 | font-style: italic; 92 | // stylelint-disable-next-line primer/typography 93 | font-weight: $font-weight-bold; 94 | } 95 | 96 | dd { 97 | padding: 0 var(--base-size-16); 98 | margin-bottom: var(--base-size-16); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main, next_major ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '34 14 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v5 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v4 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v4 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v4 71 | -------------------------------------------------------------------------------- /docs/stories/deprecated-components/Tooltip/Tooltip.mdx: -------------------------------------------------------------------------------- 1 | import {Canvas, Meta, Story} from '@storybook/blocks' 2 | 3 | import * as TooltipStories from './Tooltip.stories' 4 | 5 | 6 | 7 | # Tooltip 8 | 9 | Please note that the `.tooltipped` component is **deprecated**. We recommend using the [Tooltip component](https://primer.style/view-components/components/alpha/tooltip) instead. 10 | 11 | Add tooltips built entirely in CSS to appropriate elements. 12 | 13 | ## Implementation and accessibility 14 | 15 | Tooltips as a UI pattern should be our last resort for conveying information because it is hidden by default and often with zero or little visual indicator of its existence. 16 | 17 | Before adding a tooltip, please consider: Is this information essential and necessary\* Can the UI be made clearer? Can the information be shown on the page by default? And check out [alternatives to Tooltips](https://primer.style/design/accessibility/tooltip-alternatives) to explore your options. 18 | 19 | ### Attention 20 | 21 | - **Never** use tooltips on static elements. They should only be used on interactive elements, because users cannot tab-focus into static elements, which may make the content inaccessible for keyboard-only users and screen readers. 22 | - we use `aria-label` for tooltip contents, because it is crucial that they are accessible to screen reader users. However, `aria-label` **replaces** the text content of an element in screen readers, so only use `.tooltipped` on elements with no existing text content such as an icon-only button. 23 | - Tooltip classes will conflict with Octicon styles, and as such, must be applied to the parent element instead of the icon. 24 | 25 | ## Tooltip direction 26 | 27 | Specify the direction of a tooltip with north, south, east, and west directions: 28 | 29 | 30 | 31 | ## Tooltip alignment 32 | 33 | Align tooltips to the left or right of an element, combined with a directional class to specify north or south. Use a modifier of `1` or `2` to choose how much the tooltip's arrow is indented. 34 | 35 | 36 | 37 | ## Tooltips with multiple lines 38 | 39 | Use `.tooltipped-multiline` when you have long content. This style has some limitations: you cannot pre-format the text with newlines, and tooltips are limited to a max-width of `250px`. 40 | 41 | 42 | 43 | ## Tooltips with no delay 44 | 45 | By default the tooltips have a slight delay before appearing. This is to keep multiple tooltips in the UI from being distracting. There is a `.tooltipped-no-delay` modifier class you can use to override this. 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/markdown/headings.scss: -------------------------------------------------------------------------------- 1 | // Needs refactoring 2 | // stylelint-disable selector-max-compound-selectors, selector-max-specificity 3 | // stylelint-disable selector-max-type 4 | .markdown-body { 5 | // Headings 6 | h1, 7 | h2, 8 | h3, 9 | h4, 10 | h5, 11 | h6 { 12 | margin-top: var(--base-size-24); 13 | margin-bottom: var(--base-size-16); 14 | // stylelint-disable-next-line primer/typography 15 | font-weight: $font-weight-bold; 16 | // stylelint-disable-next-line primer/typography 17 | line-height: $lh-condensed; 18 | 19 | .octicon-link { 20 | color: var(--fgColor-default, var(--color-fg-default)); 21 | vertical-align: middle; 22 | visibility: hidden; 23 | } 24 | 25 | &:hover .anchor { 26 | text-decoration: none; 27 | 28 | .octicon-link { 29 | visibility: visible; 30 | } 31 | } 32 | 33 | tt, 34 | code { 35 | // stylelint-disable-next-line primer/spacing 36 | padding: 0 0.2em; 37 | font-size: inherit; 38 | } 39 | } 40 | 41 | h1 { 42 | // stylelint-disable-next-line primer/spacing 43 | padding-bottom: 0.3em; 44 | // stylelint-disable-next-line primer/typography 45 | font-size: 2em; 46 | // stylelint-disable-next-line primer/borders, primer/colors 47 | border-bottom: $border-width $border-style var(--borderColor-muted, var(--color-border-muted)); 48 | } 49 | 50 | h2 { 51 | // stylelint-disable-next-line primer/spacing 52 | padding-bottom: 0.3em; 53 | // stylelint-disable-next-line primer/typography 54 | font-size: 1.5em; 55 | // stylelint-disable-next-line primer/borders, primer/colors 56 | border-bottom: $border-width $border-style var(--borderColor-muted, var(--color-border-muted)); 57 | } 58 | 59 | h3 { 60 | // stylelint-disable-next-line primer/typography 61 | font-size: 1.25em; 62 | } 63 | 64 | h4 { 65 | // stylelint-disable-next-line primer/typography 66 | font-size: 1em; 67 | } 68 | 69 | h5 { 70 | // stylelint-disable-next-line primer/typography 71 | font-size: 0.875em; 72 | } 73 | 74 | h6 { 75 | // stylelint-disable-next-line primer/typography 76 | font-size: 0.85em; 77 | color: var(--fgColor-muted, var(--color-fg-muted)); 78 | } 79 | 80 | summary { 81 | h1, 82 | h2, 83 | h3, 84 | h4, 85 | h5, 86 | h6 { 87 | display: inline-block; 88 | 89 | .anchor { 90 | // stylelint-disable-next-line primer/spacing 91 | margin-left: -40px; 92 | } 93 | } 94 | 95 | h1, 96 | h2 { 97 | padding-bottom: 0; 98 | border-bottom: 0; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /docs/stories/components/Layout/StackExamples.stories.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | StackTemplate 4 | } from './Stack.stories' 5 | 6 | export default { 7 | title: 'Components/Layout/Stack/Examples' 8 | } 9 | 10 | export const ButtonInlineStack = StackTemplate.bind({}) 11 | ButtonInlineStack.args = { 12 | direction: "inline", 13 | gap: "condensed", 14 | children: ( 15 | <> 16 | Button 17 | Button 18 | Button 19 | > 20 | ) 21 | }; 22 | 23 | export const PageSections = StackTemplate.bind({}) 24 | PageSections.args = { 25 | direction: "block", 26 | gap: "normal", 27 | children: ( 28 | <> 29 | Section 1 30 | Section 2 31 | Section 3 32 | > 33 | ) 34 | }; 35 | 36 | export const Composition = StackTemplate.bind({}) 37 | Composition.args = { 38 | direction: "block", 39 | gap: "normal", 40 | children: ( 41 | <> 42 | 44 | 46 | Heading 47 | Lorem ipsum dolor sit amet avec consequer domulus sit lorem ipsum dolor sit amet. 48 | 50 | Inline labels set to wrap 51 | Label 2 52 | Label 3 53 | Label 4 54 | Label 5 55 | > 56 | )} /> 57 | > 58 | )} /> 59 | 60 | 62 | Heading 63 | Lorem ipsum dolor sit amet avec consequer domulus sit lorem ipsum dolor sit amet. 64 | 66 | Inline labels set to wrap 67 | Label 2 68 | Label 3 69 | Label 4 70 | Label 5 71 | > 72 | )} /> 73 | > 74 | )} /> 75 | > 76 | )} /> 77 | > 78 | ) 79 | }; -------------------------------------------------------------------------------- /src/utilities/margin.scss: -------------------------------------------------------------------------------- 1 | // Margin spacer utilities 2 | // stylelint-disable primer/spacing 3 | 4 | // Loop through the breakpoint values 5 | @each $breakpoint, $variant in $responsive-variants { 6 | @include breakpoint($breakpoint) { 7 | // Loop through the spacer values 8 | @each $scale, $size in $spacer-map-rem-extended { 9 | @if ($scale < length($spacer-map-rem)) { 10 | /* Set a $size margin to all sides at $breakpoint */ 11 | .m#{$variant}-#{$scale} { margin: $size !important; } 12 | } 13 | 14 | /* Set a $size margin on the top at $breakpoint */ 15 | .mt#{$variant}-#{$scale} { margin-top: $size !important; } 16 | /* Set a $size margin on the bottom at $breakpoint */ 17 | .mb#{$variant}-#{$scale} { margin-bottom: $size !important; } 18 | 19 | @if ($scale < length($spacer-map-rem)) { 20 | /* Set a $size margin on the right at $breakpoint */ 21 | .mr#{$variant}-#{$scale} { margin-right: $size !important; } 22 | /* Set a $size margin on the left at $breakpoint */ 23 | .ml#{$variant}-#{$scale} { margin-left: $size !important; } 24 | } 25 | 26 | @if ($size != 0) { 27 | /* Set a negative $size margin on top at $breakpoint */ 28 | .mt#{$variant}-n#{$scale} { margin-top: calc(-1 * $size) !important; } 29 | /* Set a negative $size margin on the bottom at $breakpoint */ 30 | .mb#{$variant}-n#{$scale} { margin-bottom: calc(-1 * $size) !important; } 31 | 32 | @if ($scale < length($spacer-map-rem)) { 33 | /* Set a negative $size margin on the right at $breakpoint */ 34 | .mr#{$variant}-n#{$scale} { margin-right : calc(-1 * $size) !important; } 35 | /* Set a negative $size margin on the left at $breakpoint */ 36 | .ml#{$variant}-n#{$scale} { margin-left : calc(-1 * $size) !important; } 37 | } 38 | } 39 | 40 | @if ($scale < length($spacer-map-rem)) { 41 | /* Set a $size margin on the left & right at $breakpoint */ 42 | .mx#{$variant}-#{$scale} { 43 | margin-right: $size !important; 44 | margin-left: $size !important; 45 | } 46 | } 47 | 48 | /* Set a $size margin on the top & bottom at $breakpoint */ 49 | .my#{$variant}-#{$scale} { 50 | margin-top: $size !important; 51 | margin-bottom: $size !important; 52 | } 53 | } 54 | 55 | /* responsive horizontal auto margins */ 56 | .mx#{$variant}-auto { 57 | margin-right: auto !important; 58 | margin-left: auto !important; 59 | } 60 | } 61 | } 62 | 63 | .m-auto { margin: auto !important; } 64 | 65 | .mt-auto { margin-top: auto !important; } 66 | .mr-auto { margin-right: auto !important; } 67 | .mb-auto { margin-bottom: auto !important; } 68 | .ml-auto { margin-left: auto !important; } 69 | -------------------------------------------------------------------------------- /src/support/mixins/misc.scss: -------------------------------------------------------------------------------- 1 | // Generate a two-color caret for any element. 2 | @mixin double-caret($background: var(--bgColor-default, var(--color-canvas-default)), $border: var(--borderColor-default, var(--color-border-default))) { 3 | &::after, 4 | &::before { 5 | position: absolute; 6 | // stylelint-disable-next-line primer/spacing 7 | top: 11px; 8 | right: 100%; 9 | left: calc(var(--base-size-8) * -1); 10 | display: block; 11 | width: 8px; 12 | height: 16px; 13 | pointer-events: none; 14 | content: ' '; 15 | clip-path: polygon(0 50%, 100% 0, 100% 100%); 16 | } 17 | 18 | &::after { 19 | // stylelint-disable-next-line primer/spacing 20 | margin-left: 2px; 21 | background-color: var(--bgColor-default, var(--color-canvas-default)); 22 | background-image: linear-gradient($background, $background); 23 | } 24 | 25 | &::before { 26 | // stylelint-disable-next-line primer/colors 27 | background-color: $border; 28 | } 29 | } 30 | 31 | // global focus styles 32 | 33 | // inset box-shadow for form controls 34 | @mixin focusBoxShadowInset($outlineWidth: 1px, $outlineColor: var(--focus-outlineColor, var(--color-accent-fg))) { 35 | // stylelint-disable-next-line primer/colors 36 | border-color: var(--focus-outlineColor, var(--color-accent-fg)); 37 | outline: none; 38 | // stylelint-disable-next-line primer/box-shadow 39 | box-shadow: inset 0 0 0 $outlineWidth $outlineColor; 40 | } 41 | 42 | // box-shadow for :target styles (no inset) 43 | // !important to override PCSS utilities 44 | @mixin targetBoxShadow($outlineWidth: 2px, $outlineColor: var(--focus-outlineColor, var(--color-accent-fg))) { 45 | outline: none !important; 46 | // stylelint-disable-next-line primer/box-shadow 47 | box-shadow: 0 0 0 $outlineWidth $outlineColor !important; 48 | } 49 | 50 | // outline 51 | @mixin focusOutline($outlineOffset: -2px, $outlineColor: var(--focus-outlineColor, var(--color-accent-fg))) { 52 | outline: 2px solid $outlineColor; 53 | outline-offset: $outlineOffset; 54 | box-shadow: none; 55 | } 56 | 57 | // outline with fg box-shadow for buttons 58 | @mixin focusOutlineOnEmphasis($outlineOffset: -2px, $outlineColor: var(--focus-outlineColor, var(--color-accent-fg))) { 59 | outline: 2px solid $outlineColor; 60 | outline-offset: $outlineOffset; 61 | // stylelint-disable-next-line primer/box-shadow 62 | box-shadow: inset 0 0 0 3px var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 63 | } 64 | 65 | // if min-width is undefined, return only min-height 66 | @mixin minTouchTarget($min-height: 32px, $min-width: '') { 67 | position: absolute; 68 | top: 50%; 69 | left: 50%; 70 | width: 100%; 71 | height: 100%; 72 | min-height: $min-height; 73 | content: ''; 74 | transform: translateX(-50%) translateY(-50%); 75 | 76 | @if $min-width != '' { 77 | min-width: $min-width; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /docs/stories/utilities/Padding.stories.jsx: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Utilities/Padding' 3 | } 4 | 5 | export const Uniform = ({}) => ( 6 | 7 | .p-0 8 | .p-1 9 | .p-2 10 | .p-3 11 | .p-4 12 | .p-5 13 | .p-6 14 | 15 | ) 16 | 17 | export const Directional = ({}) => ( 18 | 19 | .pt-3 20 | .pr-3 21 | .pb-3 22 | .pl-3 23 | .py-3 24 | .px-3 25 | 26 | ) 27 | 28 | export const DirectionalExtended = ({}) => ( 29 | 30 | .pt-7 31 | .pt-8 32 | .pt-9 33 | .pt-10 34 | .pt-11 35 | .pt-12 36 | 37 | ) 38 | 39 | export const Responsive = ({}) => ( 40 | 41 | .px-sm-2 .px-md-4 42 | 43 | ) 44 | 45 | export const ResponsiveContainer = ({}) => ( 46 | 47 | .p-responsive 48 | 49 | ) 50 | -------------------------------------------------------------------------------- /script/stylelint-remove-disables.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import {execFile} from 'child_process' 3 | import {readFileSync, writeFileSync} from 'fs' 4 | 5 | const files = process.argv.slice(2) 6 | if (files.length === 0) { 7 | files.push('app/assets/stylesheets') 8 | } 9 | 10 | // we use an empty "marker" to delineate removed lines 11 | const REMOVED = `===REMOVED@${Date.now()}===` 12 | 13 | execFile('stylelint', ['--rd', '--quiet', '--formatter', 'json', process.argv[2]], (error, stdout) => { 14 | 15 | // Filter to only needless disables comments 16 | const results = JSON.parse(stdout) 17 | .filter(result => result.warnings.length > 0) 18 | .map(({source, warnings}) => { 19 | return { 20 | source, 21 | warnings: warnings.filter(warning => warning.rule === '--report-needless-disables') 22 | } 23 | }) 24 | 25 | for (const {source, warnings} of results) { 26 | console.log(`--- ${source}\n+++ ${source}`) 27 | const lines = readFileSync(source, 'utf8').split(/\n/) 28 | for (const {text, line: lineNum} of warnings) { 29 | const ruleName = text.match(/^Needless disable for "(.+)"$/)[1] 30 | const line = lines[lineNum - 1] 31 | let disableComment = parseDisableComment(line) 32 | 33 | if (disableComment) { 34 | const rules = new Set(disableComment.rules) 35 | rules.delete(ruleName) 36 | let replacementLine = line 37 | if (rules.size === 0) { 38 | console.log(`@@ ${lineNum},${lineNum - 1} @@`) 39 | replacementLine = line.replace(`// ${disableComment.content}`, REMOVED) 40 | } else { 41 | console.log(`@@ ${lineNum},${lineNum} @@`) 42 | replacementLine = line.replace(disableComment.content, `${disableComment.type} ${Array.from(rules).join(', ')}`) 43 | } 44 | lines[lineNum - 1] = replacementLine 45 | 46 | if (lines[lineNum - 2]) { 47 | console.log(`${lines[lineNum - 2]}`) 48 | } 49 | console.log(`- ${line}`) 50 | if (!replacementLine.includes(REMOVED)) { 51 | console.log(`+ ${replacementLine}`) 52 | } 53 | if (lines[lineNum]) { 54 | console.log(`${lines[lineNum]}`) 55 | } 56 | } 57 | } 58 | console.log('') 59 | const output = lines.map(line => { 60 | if (line.trim() === REMOVED) { 61 | return null 62 | } else if (line.includes(REMOVED)) { 63 | line = line.replace(REMOVED, '').trimEnd() 64 | } 65 | 66 | return line 67 | }).filter(line => line !== null).join('\n') 68 | 69 | writeFileSync(source, output, 'utf8') 70 | } 71 | }) 72 | 73 | function parseDisableComment(str) { 74 | const match = str.match(/(stylelint-disable((-next)?-line)?)\s+(.+)$/) 75 | return match 76 | ? { 77 | content: match[0], 78 | type: match[1], 79 | rules: match[4].split(/,\s+/) 80 | } 81 | : false 82 | } 83 | -------------------------------------------------------------------------------- /src/buttons/button-group.scss: -------------------------------------------------------------------------------- 1 | // Button group 2 | // 3 | // A button group is a series of buttons laid out next to each other, all part 4 | // of one visual button, but separated by rules to be separate. 5 | 6 | .BtnGroup { 7 | display: inline-block; 8 | vertical-align: middle; 9 | 10 | @include clearfix(); 11 | 12 | // Proper spacing for multiple button groups (a la, gollum editor) 13 | + .BtnGroup, 14 | + .btn { 15 | margin-left: var(--base-size-4); 16 | } 17 | } 18 | 19 | .BtnGroup-item { 20 | position: relative; 21 | float: left; 22 | border-right-width: 0; 23 | border-radius: 0; 24 | 25 | &:first-child { 26 | // stylelint-disable-next-line primer/borders 27 | border-top-left-radius: $border-radius; 28 | // stylelint-disable-next-line primer/borders 29 | border-bottom-left-radius: $border-radius; 30 | } 31 | 32 | &:last-child { 33 | // stylelint-disable-next-line primer/borders 34 | border-right-width: $border-width; 35 | // stylelint-disable-next-line primer/borders 36 | border-top-right-radius: $border-radius; 37 | // stylelint-disable-next-line primer/borders 38 | border-bottom-right-radius: $border-radius; 39 | } 40 | 41 | &.selected, 42 | &[aria-selected='true'], 43 | &:focus, 44 | &:active, 45 | &:hover { 46 | // stylelint-disable-next-line primer/borders 47 | border-right-width: $border-width; 48 | 49 | + .BtnGroup-item, 50 | + .BtnGroup-parent .BtnGroup-item { 51 | border-left-width: 0; 52 | } 53 | } 54 | } 55 | 56 | .BtnGroup-parent { 57 | float: left; 58 | 59 | &:first-child .BtnGroup-item { 60 | // stylelint-disable-next-line primer/borders 61 | border-top-left-radius: $border-radius; 62 | // stylelint-disable-next-line primer/borders 63 | border-bottom-left-radius: $border-radius; 64 | } 65 | 66 | &:last-child .BtnGroup-item { 67 | // stylelint-disable-next-line primer/borders 68 | border-right-width: $border-width; 69 | // stylelint-disable-next-line primer/borders 70 | border-top-right-radius: $border-radius; 71 | // stylelint-disable-next-line primer/borders 72 | border-bottom-right-radius: $border-radius; 73 | } 74 | 75 | .BtnGroup-item { 76 | border-right-width: 0; 77 | border-radius: 0; 78 | } 79 | 80 | &.selected, 81 | &[aria-selected='true'], 82 | &:focus, 83 | &:active, 84 | &:hover { 85 | .BtnGroup-item { 86 | // stylelint-disable-next-line primer/borders 87 | border-right-width: $border-width; 88 | } 89 | 90 | + .BtnGroup-item, 91 | + .BtnGroup-parent .BtnGroup-item { 92 | border-left-width: 0; 93 | } 94 | } 95 | } 96 | 97 | // ensure that the focus ring sits above the adjacent buttons 98 | .BtnGroup-item, 99 | .BtnGroup-parent { 100 | &:focus, 101 | &:active { 102 | z-index: 1; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/utilities/details.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable selector-max-type, selector-max-specificity, max-nesting-depth, selector-no-qualifying-type 2 | 3 | .details-overlay[open] > summary::before { 4 | position: fixed; 5 | top: 0; 6 | right: 0; 7 | bottom: 0; 8 | left: 0; 9 | z-index: 80; 10 | display: block; 11 | cursor: default; 12 | content: ' '; 13 | background: transparent; 14 | } 15 | 16 | .details-overlay-dark[open] > summary::before { 17 | z-index: 111; 18 | background: var(--overlay-backdrop-bgColor, var(--color-primer-canvas-backdrop)); 19 | } 20 | 21 | .details-reset { 22 | // Remove marker added by the display: list-item browser default 23 | > summary { 24 | list-style: none; 25 | transition: 80ms cubic-bezier(0.33, 1, 0.68, 1); 26 | transition-property: color, background-color, box-shadow, border-color; 27 | // fallback :focus state 28 | &:focus { 29 | @include focusOutline; 30 | 31 | // remove fallback :focus if :focus-visible is supported 32 | &:not(:focus-visible) { 33 | outline: solid 1px transparent; 34 | } 35 | } 36 | 37 | // default focus state 38 | &:focus-visible { 39 | @include focusOutline; 40 | } 41 | 42 | &.btn-primary { 43 | // fallback :focus state 44 | &:focus { 45 | @include focusOutlineOnEmphasis; 46 | 47 | // remove fallback :focus if :focus-visible is supported 48 | &:not(:focus-visible) { 49 | outline: solid 1px transparent; 50 | box-shadow: none; 51 | } 52 | } 53 | 54 | // default focus state 55 | &:focus-visible { 56 | @include focusOutlineOnEmphasis; 57 | } 58 | } 59 | } 60 | // Remove marker added by details polyfill 61 | > summary::before { 62 | display: none; 63 | } 64 | // Remove marker added by Chrome 65 | > summary::-webkit-details-marker { 66 | display: none; 67 | } 68 | } 69 | 70 | .details-overlay > summary { 71 | transition: 80ms cubic-bezier(0.33, 1, 0.68, 1); 72 | transition-property: color, background-color, box-shadow, border-color; 73 | // fallback :focus state 74 | &:focus { 75 | @include focusOutline; 76 | 77 | // remove fallback :focus if :focus-visible is supported 78 | &:not(:focus-visible) { 79 | outline: solid 1px transparent; 80 | } 81 | } 82 | 83 | // default focus state 84 | &:focus-visible { 85 | @include focusOutline; 86 | } 87 | 88 | &.btn-primary { 89 | // fallback :focus state 90 | &:focus { 91 | @include focusOutlineOnEmphasis; 92 | 93 | // remove fallback :focus if :focus-visible is supported 94 | &:not(:focus-visible) { 95 | outline: solid 1px transparent; 96 | box-shadow: none; 97 | } 98 | } 99 | 100 | // default focus state 101 | &:focus-visible { 102 | @include focusOutlineOnEmphasis; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/utilities/flexbox.scss: -------------------------------------------------------------------------------- 1 | // Flex utility classes 2 | @each $breakpoint, $variant in $responsive-variants { 3 | @include breakpoint($breakpoint) { 4 | // Flexbox classes 5 | // Container 6 | .flex#{$variant}-row { flex-direction: row !important; } 7 | .flex#{$variant}-row-reverse { flex-direction: row-reverse !important; } 8 | .flex#{$variant}-column { flex-direction: column !important; } 9 | .flex#{$variant}-column-reverse { flex-direction: column-reverse !important; } 10 | 11 | .flex#{$variant}-wrap { flex-wrap: wrap !important; } 12 | .flex#{$variant}-nowrap { flex-wrap: nowrap !important; } 13 | .flex#{$variant}-wrap-reverse { flex-wrap: wrap-reverse !important; } 14 | 15 | .flex#{$variant}-justify-start { justify-content: flex-start !important; } 16 | .flex#{$variant}-justify-end { justify-content: flex-end !important; } 17 | .flex#{$variant}-justify-center { justify-content: center !important; } 18 | .flex#{$variant}-justify-between { justify-content: space-between !important; } 19 | .flex#{$variant}-justify-around { justify-content: space-around !important; } 20 | 21 | .flex#{$variant}-items-start { align-items: flex-start !important; } 22 | .flex#{$variant}-items-end { align-items: flex-end !important; } 23 | .flex#{$variant}-items-center { align-items: center !important; } 24 | .flex#{$variant}-items-baseline { align-items: baseline !important; } 25 | .flex#{$variant}-items-stretch { align-items: stretch !important; } 26 | 27 | .flex#{$variant}-content-start { align-content: flex-start !important; } 28 | .flex#{$variant}-content-end { align-content: flex-end !important; } 29 | .flex#{$variant}-content-center { align-content: center !important; } 30 | .flex#{$variant}-content-between { align-content: space-between !important; } 31 | .flex#{$variant}-content-around { align-content: space-around !important; } 32 | .flex#{$variant}-content-stretch { align-content: stretch !important; } 33 | 34 | // Item 35 | .flex#{$variant}-1 { flex: 1 !important; } 36 | .flex#{$variant}-auto { flex: auto !important; } 37 | .flex#{$variant}-grow-0 { flex-grow: 0 !important; } 38 | .flex#{$variant}-shrink-0 { flex-shrink: 0 !important; } 39 | 40 | .flex#{$variant}-self-auto { align-self: auto !important; } 41 | .flex#{$variant}-self-start { align-self: flex-start !important; } 42 | .flex#{$variant}-self-end { align-self: flex-end !important; } 43 | .flex#{$variant}-self-center { align-self: center !important; } 44 | .flex#{$variant}-self-baseline { align-self: baseline !important; } 45 | .flex#{$variant}-self-stretch { align-self: stretch !important; } 46 | 47 | .flex#{$variant}-order-1 { order: 1 !important; } 48 | .flex#{$variant}-order-2 { order: 2 !important; } 49 | .flex#{$variant}-order-none { order: inherit !important; } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/stories/utilities/Border.stories.jsx: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Utilities/Border' 3 | } 4 | 5 | export const Default = ({}) => .border 6 | 7 | export const Direction = ({}) => ( 8 | 9 | .border-left 10 | .border-top 11 | .border-bottom 12 | .border-right 13 | .border-x 14 | .border-y 15 | 16 | ) 17 | 18 | export const Hide = ({}) => .border-top-0 19 | 20 | export const Style = ({}) => .border-dashed 21 | 22 | export const Rounded = ({}) => ( 23 | 24 | .rounded-0 25 | .rounded-1 26 | .rounded-2 27 | .rounded-3 28 | 32 | .circle 33 | 34 | 35 | ) 36 | 37 | export const RoundedDirection = ({}) => ( 38 | 39 | .rounded-top-2 40 | .rounded-right-2 41 | .rounded-bottom-2 42 | .rounded-left-2 43 | 44 | ) 45 | 46 | export const RoundedResponsive = ({}) => ( 47 | 51 | .border-left 52 | .rounded-sm-1 53 | .rounded-md-2 54 | .rounded-lg-3 55 | 56 | ) 57 | 58 | export const Responsive = ({}) => ( 59 | 63 | .border-left 64 | .border-sm-top 65 | .border-md-right 66 | .border-lg-bottom 67 | 68 | ) 69 | -------------------------------------------------------------------------------- /src/markdown/markdown-body.scss: -------------------------------------------------------------------------------- 1 | // All of our block level items should have the same margin 2 | // stylelint-disable selector-max-type 3 | 4 | // This is styling for generic markdownized text. Anything you put in a 5 | // container with .markdown-body on it should render generally well. It also 6 | // includes some GitHub Flavored Markdown specific styling (like @mentions) 7 | .markdown-body { 8 | // stylelint-disable-next-line primer/typography 9 | font-family: $body-font; 10 | // stylelint-disable-next-line primer/typography 11 | font-size: $h4-size; 12 | // stylelint-disable-next-line primer/typography 13 | line-height: $body-line-height; 14 | word-wrap: break-word; 15 | 16 | // Clearfix on the markdown body 17 | &::before { 18 | display: table; 19 | content: ''; 20 | } 21 | 22 | &::after { 23 | display: table; 24 | clear: both; 25 | content: ''; 26 | } 27 | 28 | > *:first-child { 29 | margin-top: 0 !important; 30 | } 31 | 32 | > *:last-child { 33 | margin-bottom: 0 !important; 34 | } 35 | 36 | // Anchors like . These sometimes end up wrapped around 37 | // text when users mistakenly forget to close the tag or use self-closing tag 38 | // syntax. We don't want them to appear like links. 39 | // FIXME: a:not(:link):not(:visited) would be a little clearer here (and 40 | // possibly faster to match), but it breaks styling of elements due 41 | // to https://bugs.webkit.org/show_bug.cgi?id=142737. 42 | a:not([href]) { 43 | color: inherit; 44 | text-decoration: none; 45 | } 46 | 47 | // Link Colors 48 | .absent { 49 | color: var(--fgColor-danger, var(--color-danger-fg)); 50 | } 51 | 52 | .anchor { 53 | float: left; 54 | padding-right: var(--base-size-4); 55 | // stylelint-disable-next-line primer/spacing 56 | margin-left: -20px; 57 | // stylelint-disable-next-line primer/typography 58 | line-height: $lh-condensed-ultra; 59 | 60 | &:focus { 61 | outline: none; 62 | } 63 | } 64 | 65 | p, 66 | blockquote, 67 | ul, 68 | ol, 69 | dl, 70 | table, 71 | pre, 72 | details { 73 | margin-top: 0; 74 | margin-bottom: var(--base-size-16); 75 | } 76 | 77 | hr { 78 | height: $em-spacer-3; 79 | padding: 0; 80 | margin: var(--base-size-24) 0; 81 | // stylelint-disable-next-line primer/colors 82 | background-color: var(--borderColor-default, var(--color-border-default)); 83 | border: 0; 84 | } 85 | 86 | blockquote { 87 | // stylelint-disable-next-line primer/spacing 88 | padding: 0 1em; 89 | color: var(--fgColor-muted, var(--color-fg-muted)); 90 | // stylelint-disable-next-line primer/borders, primer/colors, declaration-property-value-no-unknown 91 | border-left: 0.25em $border-style var(--borderColor-default, var(--color-border-default)); 92 | 93 | > :first-child { 94 | margin-top: 0; 95 | } 96 | 97 | > :last-child { 98 | margin-bottom: 0; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /docs/stories/utilities/Shadow.stories.jsx: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Utilities/Shadow' 3 | } 4 | 5 | export const Small = ({}) => ( 6 | 7 | .color-shadow-small 8 | 9 | ) 10 | 11 | export const SmallExample = ({}) => ( 12 | 13 | 14 | Organization 15 | 16 | 17 | 18 | Taxidermy live-edge mixtape, keytar tumeric locavore meh selvage deep v letterpress vexillologist lo-fi tousled 19 | church-key thundercats. Brooklyn bicycle rights tousled, marfa actually. 20 | 21 | 22 | 23 | 24 | Create an organization 25 | 26 | 27 | 28 | ) 29 | 30 | export const Medium = ({}) => ( 31 | 32 | .color-shadow-medium 33 | 34 | ) 35 | 36 | export const MediumExample = ({}) => ( 37 | 38 | Serverless architecture 39 | 40 | Build powerful, event-driven, serverless architectures with these open-source libraries and frameworks. 41 | 42 | 43 | 44 | 45 | 12 Repositories 46 | 47 | 48 | 49 | 5 Languages 50 | 51 | 52 | 53 | ) 54 | 55 | export const Large = ({}) => ( 56 | 57 | .color-shadow-large 58 | 59 | ) 60 | 61 | export const LargeExample = ({}) => ( 62 | 63 | 64 | 65 | ) 66 | 67 | export const ExtraLarge = ({}) => ( 68 | 69 | .color-shadow-extra-large 70 | 71 | ) 72 | 73 | export const None = ({}) => ( 74 | 75 | .box-shadow-none 76 | 77 | ) 78 | 79 | -------------------------------------------------------------------------------- /src/navigation/sidenav.scss: -------------------------------------------------------------------------------- 1 | // Side Nav 2 | // 3 | // A vertical list of navigational links, typically used on the left side of a page. 4 | 5 | .SideNav { 6 | background-color: var(--bgColor-muted, var(--color-canvas-subtle)); 7 | } 8 | 9 | .SideNav-item { 10 | position: relative; 11 | display: block; 12 | width: 100%; 13 | // stylelint-disable-next-line primer/spacing 14 | padding: 12px var(--base-size-16); 15 | color: var(--fgColor-default, var(--color-fg-default)); 16 | text-align: left; 17 | background-color: transparent; 18 | border: 0; 19 | // stylelint-disable-next-line primer/borders, primer/colors 20 | border-top: $border-width $border-style var(--borderColor-muted, var(--color-border-muted)); 21 | 22 | &:first-child { 23 | border-top: 0; 24 | } 25 | 26 | &:last-child { 27 | // makes sure there is a "bottom border" in case the list is not long enough 28 | // stylelint-disable-next-line primer/box-shadow, declaration-property-value-no-unknown 29 | box-shadow: 0 $border-width 0 var(--borderColor-default, var(--color-border-default)); 30 | } 31 | 32 | // Bar on the left 33 | &::before { 34 | position: absolute; 35 | top: 0; 36 | bottom: 0; 37 | left: 0; 38 | z-index: 1; 39 | width: 2px; 40 | pointer-events: none; 41 | content: ''; 42 | } 43 | } 44 | 45 | // States 46 | 47 | .SideNav-item:hover { 48 | text-decoration: none; 49 | background-color: var(--bgColor-neutral-muted, var(--color-neutral-subtle)); 50 | } 51 | 52 | .SideNav-item:active { 53 | background-color: var(--bgColor-muted, var(--color-canvas-subtle)); 54 | } 55 | 56 | .SideNav-item[aria-current]:not([aria-current='false']), 57 | .SideNav-item[aria-selected='true'] { 58 | background-color: var(--sideNav-bgColor-selected, var(--color-sidenav-selected-bg)); 59 | 60 | // Bar on the left 61 | &::before { 62 | // stylelint-disable-next-line primer/colors 63 | background-color: var(--underlineNav-borderColor-active, var(--color-primer-border-active)); 64 | } 65 | } 66 | 67 | // Icon 68 | // 69 | // Makes sure multiple icons are vertically aligned 70 | 71 | .SideNav-icon { 72 | width: 16px; 73 | margin-right: var(--base-size-8); 74 | color: var(--fgColor-muted, var(--color-fg-muted)); 75 | } 76 | 77 | // Sub Nav 78 | // 79 | // A more lightweight version, suited as a sub nav 80 | 81 | .SideNav-subItem { 82 | position: relative; 83 | display: block; 84 | width: 100%; 85 | padding: var(--base-size-4) 0; 86 | color: var(--fgColor-accent, var(--color-accent-fg)); 87 | text-align: left; 88 | background-color: transparent; 89 | border: 0; 90 | } 91 | 92 | .SideNav-subItem:hover { 93 | color: var(--fgColor-default, var(--color-fg-default)); 94 | text-decoration: none; 95 | } 96 | 97 | .SideNav-subItem[aria-current]:not([aria-current='false']), 98 | .SideNav-subItem[aria-selected='true'] { 99 | // stylelint-disable-next-line primer/typography 100 | font-weight: $font-weight-semibold; 101 | color: var(--fgColor-default, var(--color-fg-default)); 102 | } 103 | -------------------------------------------------------------------------------- /src/utilities/visibility-display.scss: -------------------------------------------------------------------------------- 1 | // Visibility and display utilities 2 | 3 | // Responsive display utilities 4 | @each $breakpoint, $variant in $responsive-variants { 5 | @include breakpoint($breakpoint) { 6 | @each $display in $display-values { 7 | .d#{$variant}-#{$display} { display: $display !important; } 8 | } 9 | } 10 | } 11 | 12 | .v-hidden { visibility: hidden !important; } 13 | .v-visible { visibility: visible !important; } 14 | 15 | // Hide utilities for each breakpoint 16 | // Each hide utility only applies to one breakpoint range. 17 | @media (max-width: $width-sm - 0.02px) { 18 | .hide-sm { 19 | display: none !important; 20 | } 21 | } 22 | 23 | @media (min-width: $width-sm) and (max-width: $width-md - 0.02px) { 24 | .hide-md { 25 | display: none !important; 26 | } 27 | } 28 | 29 | @media (min-width: $width-md) and (max-width: $width-lg - 0.02px) { 30 | .hide-lg { 31 | display: none !important; 32 | } 33 | } 34 | 35 | @media (min-width: $width-lg) { 36 | .hide-xl { 37 | display: none !important; 38 | } 39 | } 40 | 41 | // Show/Hide Viewport range utilities 42 | 43 | .show-whenNarrow, 44 | .show-whenRegular, 45 | .show-whenWide, 46 | .show-whenRegular.hide-whenWide { 47 | display: none !important; 48 | } 49 | 50 | .hide-whenNarrow, 51 | .hide-whenRegular, 52 | .hide-whenWide { 53 | display: block !important; 54 | } 55 | 56 | @media (max-width: $width-md - 0.02px) { 57 | .show-whenNarrow { 58 | display: block !important; 59 | } 60 | 61 | .hide-whenNarrow { 62 | display: none !important; 63 | } 64 | } 65 | 66 | @media (min-width: $width-md) { 67 | .show-whenRegular, 68 | .show-whenRegular.hide-whenWide { 69 | display: block !important; 70 | } 71 | 72 | .hide-whenRegular { 73 | display: none !important; 74 | } 75 | } 76 | 77 | // The width of a `wide` viewport range may change as we're finalizing 78 | // the Primer primitives viewport ranges proposal 79 | @media (min-width: $width-xl) { 80 | .show-whenWide { 81 | display: block !important; 82 | } 83 | 84 | .hide-whenWide, 85 | .show-whenRegular.hide-whenWide { 86 | display: none !important; 87 | } 88 | } 89 | 90 | /* Set the table-layout to fixed */ 91 | .table-fixed { table-layout: fixed !important; } 92 | 93 | // Only display content to screen readers 94 | // 95 | // See: http://a11yproject.com/posts/how-to-hide-content/ 96 | .sr-only { 97 | position: absolute; 98 | width: 1px; 99 | height: 1px; 100 | padding: 0; 101 | overflow: hidden; 102 | clip-path: rect(0 0 0 0); 103 | // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1241631 104 | overflow-wrap: normal; 105 | border: 0; 106 | } 107 | 108 | // Only display content on focus 109 | .show-on-focus { 110 | position: absolute !important; 111 | 112 | &:not(:focus) { 113 | width: 1px !important; 114 | height: 1px !important; 115 | padding: 0 !important; 116 | overflow: hidden !important; 117 | clip: rect(1px, 1px, 1px, 1px) !important; 118 | border: 0 !important; 119 | } 120 | 121 | &:focus { 122 | z-index: 999; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /script/analyze-variables.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import postcss from 'postcss' 3 | import {join} from 'path' 4 | import fs from 'fs' 5 | import atImport from 'postcss-import' 6 | import syntax from 'postcss-scss' 7 | import calc from 'postcss-calc' 8 | import simpleVars from 'postcss-simple-vars' 9 | 10 | import { dirname } from 'path'; 11 | import { fileURLToPath } from 'url'; 12 | 13 | const __dirname = dirname(fileURLToPath(import.meta.url)); 14 | 15 | const processor = postcss([ 16 | atImport({path: ['src']}), 17 | collectVariables(), 18 | simpleVars({includePaths: [join(__dirname, '../src/support/variables')]}) 19 | ]) 20 | 21 | async function analyzeVariables(fileName) { 22 | const contents = fs.readFileSync(fileName, 'utf8') 23 | 24 | const result = await processor.process(contents, {from: fileName, map: false, syntax}) 25 | for (const message of result.messages) { 26 | if (message.plugin === 'postcss-simple-vars' && message.type === 'variable') { 27 | if (!result.variables[`$${message.name}`].values.includes(message.value)) { 28 | result.variables[`$${message.name}`].values.push(message.value) 29 | } 30 | let computed = message.value 31 | try { 32 | const c = `--temp-property: calc(${message.value})`.replace('round(', '(') 33 | computed = postcss().use(calc()).process(c).css 34 | computed = computed.replace('--temp-property: ', '') 35 | } catch (e) { 36 | // Couldn't calculate because value might not be a number 37 | } 38 | result.variables[`$${message.name}`].computed = computed 39 | } 40 | } 41 | return result.variables 42 | } 43 | 44 | function checkNode(node) { 45 | const allowedFuncts = ['var', 'round', 'cubic-bezier'] 46 | const funcMatch = node.value.match(/([^\s]*)\(/) 47 | let approvedMatch = true 48 | if (funcMatch && !allowedFuncts.includes(funcMatch[1])) { 49 | approvedMatch = false 50 | } 51 | return node.variable && approvedMatch 52 | } 53 | 54 | function collectVariables() { 55 | return { 56 | postcssPlugin: 'prepare-contents', 57 | prepare(result) { 58 | const variables = {} 59 | return { 60 | AtRule(atRule) { 61 | atRule.remove() 62 | }, 63 | Comment(comment) { 64 | comment.remove() 65 | }, 66 | Declaration(node) { 67 | if (checkNode(node)) { 68 | node.value = node.value.replace(' !default', '') 69 | const fileName = node.source.input.file.replace(`${process.cwd()}/`, '') 70 | variables[node.prop] = { 71 | // computed: value, 72 | values: [node.value], 73 | source: { 74 | path: fileName, 75 | line: node.source.start.line 76 | } 77 | } 78 | } else { 79 | node.remove() 80 | } 81 | }, 82 | OnceExit() { 83 | result.variables = variables 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | export default analyzeVariables 91 | 92 | ;(async () => { 93 | const args = process.argv.slice(2) 94 | const file = args.length ? args.shift() : 'src/support/index.scss' 95 | const variables = await analyzeVariables(file) 96 | JSON.stringify(variables, null, 2) 97 | })() 98 | -------------------------------------------------------------------------------- /src/autocomplete/suggester.scss: -------------------------------------------------------------------------------- 1 | // Needs refactoring 2 | // stylelint-disable selector-max-type, selector-no-qualifying-type 3 | 4 | .suggester { 5 | position: relative; 6 | top: 0; 7 | left: 0; 8 | min-width: 180px; 9 | padding: 0; 10 | margin: 0; 11 | margin-top: var(--base-size-24); 12 | list-style: none; 13 | cursor: pointer; 14 | background: var(--overlay-bgColor, var(--color-canvas-overlay)); 15 | // stylelint-disable-next-line primer/borders, primer/colors 16 | border: $border-width $border-style var(--borderColor-default, var(--color-border-default)); 17 | // stylelint-disable-next-line primer/borders 18 | border-radius: $border-radius; 19 | box-shadow: var(--shadow-resting-medium, var(--color-shadow-medium)); 20 | 21 | li { 22 | display: block; 23 | padding: var(--base-size-4) var(--base-size-8); 24 | // stylelint-disable-next-line primer/typography 25 | font-weight: $font-weight-semibold; 26 | // stylelint-disable-next-line primer/borders, primer/colors 27 | border-bottom: $border-width $border-style var(--borderColor-muted, var(--color-border-muted)); 28 | 29 | small { 30 | // stylelint-disable-next-line primer/typography 31 | font-weight: $font-weight-normal; 32 | color: var(--fgColor-muted, var(--color-fg-muted)); 33 | } 34 | 35 | &:last-child { 36 | border-bottom: 0; 37 | // stylelint-disable-next-line primer/borders 38 | border-bottom-right-radius: $border-radius; 39 | // stylelint-disable-next-line primer/borders 40 | border-bottom-left-radius: $border-radius; 41 | } 42 | 43 | &:first-child { 44 | // stylelint-disable-next-line primer/borders 45 | border-top-left-radius: $border-radius; 46 | // stylelint-disable-next-line primer/borders 47 | border-top-right-radius: $border-radius; 48 | } 49 | 50 | &:hover { 51 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 52 | text-decoration: none; 53 | background: var(--bgColor-accent-emphasis, var(--color-accent-emphasis)); 54 | 55 | small { 56 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 57 | } 58 | 59 | .octicon { 60 | color: inherit !important; 61 | } 62 | } 63 | 64 | &[aria-selected='true'], 65 | &.navigation-focus { 66 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 67 | text-decoration: none; 68 | background: var(--bgColor-accent-emphasis, var(--color-accent-emphasis)); 69 | 70 | small { 71 | color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)); 72 | } 73 | 74 | .octicon { 75 | color: inherit !important; 76 | } 77 | } 78 | } 79 | } 80 | 81 | .suggester-container { 82 | position: absolute; 83 | top: 0; 84 | left: 0; 85 | z-index: 30; 86 | } 87 | 88 | // Responsive 89 | 90 | .page-responsive { 91 | @media (max-width: $width-sm) { 92 | .suggester-container { 93 | right: var(--base-size-8) !important; 94 | left: var(--base-size-8) !important; 95 | } 96 | 97 | .suggester li { 98 | padding: var(--base-size-8) var(--base-size-16); 99 | } 100 | } 101 | } 102 | --------------------------------------------------------------------------------
18 | Taxidermy live-edge mixtape, keytar tumeric locavore meh selvage deep v letterpress vexillologist lo-fi tousled 19 | church-key thundercats. Brooklyn bicycle rights tousled, marfa actually. 20 |
40 | Build powerful, event-driven, serverless architectures with these open-source libraries and frameworks. 41 |