Skip to content

Commit

Permalink
Global focus styles (#1744)
Browse files Browse the repository at this point in the history
* setup base focus styles

* add feature stories

* add stories for focusable things, delete outline:0

* switch back to box-shadow

* support class

* stylelint

* fix theme viewer

* switch back to outline, address feedback

* lint

* temp stashing stories here

* Create giant-bees-talk.md

* I think we got it!

* address input directly

* checkbox/radio outline offset

* lint

* change actionlist to just use focus

* merge

* Update giant-bees-talk.md

* address marketing styles

* tabnav focus fix

* reach all buttons

* attempt windows hc selector

* Stylelint auto-fixes

* fixes

* add focus style testing page

* Stylelint auto-fixes

* add href for testing

* remove position relative to fix chrome bug

* fix details scenario

* add offset to WHC

* maintain offset specificity in whc

* inset tabnav focus

* switch offset to inset

* fix actionlist focus

* lint

* better scoping, handle forms for safari

* moving specific styles from dotcom

* address autocomplete

* cleanup

* cleanup

* selected focus states

* adjust marketing focus

* use offset instead for marketing

* Stylelint auto-fixes

* fix merge

Co-authored-by: Jon Rohan <yes@jonrohan.codes>
Co-authored-by: Actions Auto Build <actions@github.com>
  • Loading branch information
3 people committed Apr 20, 2022
1 parent 00c9060 commit 942f65a
Show file tree
Hide file tree
Showing 29 changed files with 618 additions and 254 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-bees-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/css": major
---

Global CSS focus styles
2 changes: 1 addition & 1 deletion docs/src/stories/components/Link/Link.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const LinkTemplate = ({label, variant, href, noUnderline, focusElement, f
<>
<a
href={href}
className={clsx(variant && `${variant}`, noUnderline && 'no-underline', focusAllElements && 'focus')}
className={clsx('Link', variant && `${variant}`, noUnderline && 'no-underline', focusAllElements && 'focus')}
>
{label}
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default {
layout: 'padded'
},

excludeStories: ['ButtonTemplate'],
excludeStories: ['MarketingButtonTemplate'],
argTypes: {
variant: {
options: [0, 1, 2, 3], // iterator
Expand Down Expand Up @@ -77,7 +77,7 @@ const focusMethod = function getFocus() {
button.focus()
}

export const ButtonTemplate = ({label, variant, disabled, size, animated, focusElement, focusAllElements}) => (
export const MarketingButtonTemplate = ({label, variant, disabled, size, animated, focusElement, focusAllElements}) => (
<>
<button
disabled={disabled}
Expand Down Expand Up @@ -111,7 +111,7 @@ export const ButtonTemplate = ({label, variant, disabled, size, animated, focusE
</>
)

export const Playground = ButtonTemplate.bind({})
export const Playground = MarketingButtonTemplate.bind({})
Playground.args = {
animated: false,
focusElement: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default {
layout: 'padded'
},

excludeStories: ['LinkTemplate'],
excludeStories: ['MarketingLinkTemplate'],
argTypes: {
size: {
options: [0, 1], // iterator
Expand Down Expand Up @@ -67,7 +67,7 @@ const focusMethod = function getFocus() {
link.focus()
}

export const LinkTemplate = ({label, emphasis, href, size, focusElement, focusAllElements}) => (
export const MarketingLinkTemplate = ({label, emphasis, href, size, focusElement, focusAllElements}) => (
<>
<a
href={href}
Expand Down Expand Up @@ -98,7 +98,7 @@ export const LinkTemplate = ({label, emphasis, href, size, focusElement, focusAl
</>
)

export const Playground = LinkTemplate.bind({})
export const Playground = MarketingLinkTemplate.bind({})
Playground.args = {
label: 'Link label',
href: '/',
Expand Down
88 changes: 88 additions & 0 deletions docs/src/stories/patterns/FocusStyles.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react'
import clsx from 'clsx'
import {ButtonTemplate} from '../components/Button/Button.stories.jsx'
import {CheckboxTemplate} from '../components/Forms/Checkbox.stories.jsx'
import {InputTemplate} from '../components/Forms/Input.stories.jsx'
import {SelectTemplate} from '../components/Forms/Select.stories.jsx'
import {TextareaTemplate} from '../components/Forms/Textarea.stories.jsx'
import {LinkTemplate} from '../components/Link/Link.stories.jsx'
import {MarketingButtonTemplate} from '../components/Marketing/MarketingButton.stories.jsx'
import {MarketingLinkTemplate} from '../components/Marketing/MarketingLink.stories.jsx'
import {TabNavTemplate} from '../components/Navigation/TabNav.stories.jsx'
import {TabNavItemTemplate} from '../components/Navigation/TabNavItem.stories.jsx'

export default {
title: 'Patterns/FocusStyles',
layout: 'padded'
}

export const FocusStyles = ({}) => (
<div style={{display: 'flex', flexDirection: 'column', gap: '2rem'}}>
<div style={{display: 'flex', gap: '0.5rem'}}>
<ButtonTemplate variant="btn-primary" label="Primary" />
<ButtonTemplate variant="btn-secondary" label="Secondary" />
<ButtonTemplate variant="btn-outline" label="Outline" />
<ButtonTemplate variant="btn-danger" label="Danger" />
<ButtonTemplate variant="btn-link" label="Link" />
<ButtonTemplate variant="btn-invisible" label="Invisible" />
<ButtonTemplate
variant="btn-octicon"
leadingVisual={`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
<path d="M8 16c.9 0 1.7-.6 1.9-1.5.1-.3-.1-.5-.4-.5h-3c-.3 0-.5.2-.4.5.2.9 1 1.5 1.9 1.5zM3 5c0-2.8 2.2-5 5-5s5 2.2 5 5v3l1.7 2.6c.2.2.3.5.3.8 0 .8-.7 1.5-1.5 1.5h-11c-.8.1-1.5-.6-1.5-1.4 0-.3.1-.6.3-.8L3 8.1V5z"></path>
</svg>`}
/>
<ButtonTemplate variant="btn-primary" label="Primary" disabled />
<ButtonTemplate variant="btn-secondary" label="Secondary" disabled />
<ButtonTemplate variant="btn-outline" label="Outline" disabled />
<ButtonTemplate variant="btn-danger" label="Danger" disabled />
<ButtonTemplate variant="btn-link" label="Link" disabled />
<ButtonTemplate variant="btn-invisible" label="Invisible" disabled />
</div>
<div style={{display: 'flex', flexDirection: 'column', gap: '1rem'}}>
<CheckboxTemplate label="checkbox" type="checkbox" />
<CheckboxTemplate label="radio" type="radio" />
<InputTemplate label="input" type="text" />
<input className="form-control border-0" placeholder="no border form control"></input>
<SelectTemplate label="select" />
<TextareaTemplate label="textarea" />
<LinkTemplate label="Primer link" href="/" />
<a href="/">Link with no CSS class</a>
<MarketingButtonTemplate label="Marketing Button" />
<MarketingLinkTemplate label="Marketing Link" href="/" />
</div>
<div>
<TabNavTemplate>
<TabNavItemTemplate text="First tab" ariaCurrent="location" href="#url" />
<TabNavItemTemplate text="Second tab" href="#url" />
</TabNavTemplate>
</div>
<div class="BtnGroup">
<a href="/" class="btn-sm btn BtnGroup-item">
One
</a>
<a href="/" class="btn-sm btn BtnGroup-item">
Two
</a>
</div>
<div class="Box faketarget">:target styles</div>
<nav class="UnderlineNav" aria-label="Foo bar">
<div class="UnderlineNav-body">
<a class="UnderlineNav-item" href="#url" aria-current="page">
Item 1
</a>
<a class="UnderlineNav-item" href="#url">
Item 2
</a>
<a class="UnderlineNav-item" href="#url">
Item 3
</a>
<a class="UnderlineNav-item" href="#url">
Item 4
</a>
</div>
<div class="UnderlineNav-actions">
<a class="btn btn-sm">Button</a>
</div>
</nav>
</div>
)
17 changes: 10 additions & 7 deletions src/actionlist/action-list-item.scss
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
// stylelint-disable max-nesting-depth, selector-max-specificity, selector-max-compound-selectors

@mixin focusOutline {
position: relative;
z-index: 1;
outline: none;
box-shadow: 0 0 0 2px var(--color-accent-fg); // this color breaks convention
}

@mixin activeIndicatorLine {
position: absolute;
top: calc(50% - 12px);
Expand Down Expand Up @@ -314,6 +307,16 @@
text-decoration: none;
}

&:focus {
@include focusOutline;

// remove fallback :focus if :focus-visible is supported
&:not(:focus-visible) {
outline: solid 1px transparent;
}
}

// default focus state
&:focus-visible {
@include focusOutline;
}
Expand Down
10 changes: 3 additions & 7 deletions src/autocomplete/autocomplete.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
align-items: center;

&:focus-within {
border-color: var(--color-accent-emphasis);
outline: none;
box-shadow: var(--color-primer-shadow-focus);
border-color: var(--color-accent-fg);

@include focusBoxShadowInset;
}

.form-control {
Expand All @@ -42,10 +42,6 @@
// stylelint-disable-next-line
border: none;
box-shadow: none;

&:focus {
box-shadow: none;
}
}
}

Expand Down
66 changes: 63 additions & 3 deletions src/base/base.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// stylelint-disable selector-max-type
// stylelint-disable selector-max-type, selector-no-qualifying-type
* {
box-sizing: border-box;
}
Expand Down Expand Up @@ -77,10 +77,70 @@ button {
}

details {
summary { cursor: pointer; }
summary {
cursor: pointer;
}

&:not([open]) {
// Set details content hidden by default for browsers that don't do this
> *:not(summary) { display: none !important; }
> *:not(summary) {
display: none !important;
}
}
}

// global focus styles

a,
button,
[role='button'],
input[type='radio'],
input[type='checkbox'] {
transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
transition-property: color, background-color, box-shadow, border-color;
// fallback :focus state
&:focus {
@include focusOutline;

// remove fallback :focus if :focus-visible is supported
&:not(:focus-visible) {
outline: solid 1px transparent;
}
}

// default focus state
&:focus-visible {
@include focusOutline;
}
}

a:not([class]),
input[type='radio'],
input[type='checkbox'] {
&:focus,
&:focus-visible {
outline-offset: 0;
}
}

// for handling focus conditionally
.focus {
@include focusBoxShadowInset;
}

// Windows High Contrast mode
@media (forced-colors: active) {
*:focus,
*:focus-visible {
outline: solid 1px transparent;
}

input:not([type='radio'], [type='checkbox']),
textarea,
select {
&:focus,
&:focus-visible {
outline-offset: 2px;
}
}
}
Loading

0 comments on commit 942f65a

Please sign in to comment.