-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update Copy to Clipboard recipe to show/hide on hover/focus events (#…
…11802) ### WHAT is this pull request doing? This PR updates the Copy to Clipboard recipe to show/hide the copy button on hover/focus events. This was accomplished by adding a few more hooks to the system: - `useHover` - Tracks `mouseenter`/`mouseleave` events on a target ref - `useFocus` - Tracks `focus`/`blur` events on a target ref - `useFocusIn` - Tracks `focusin`/`focusout` events on a target ref - `useMediaQuery` - Tracks `change` events on a target `matchMedia(query)`. Additionally, supports `queryAliases` to streamline the application of [common media queries](https://github.com/argyleink/open-props/blob/09e70c03c0a2533d06ec823f47490f018eb27f23/src/props.media.css#L21-L24) e.g. `const isMouseDevice = useMediaQuery('mouse')` Example: Show the copy button on `mouseenter` of the target ref: https://github.com/Shopify/polaris/assets/32409546/b3799bdf-e915-4761-a68c-f2724cccd9f1 Example: Always show the copy button on non-mouse devices https://github.com/Shopify/polaris/assets/32409546/221d7c76-9a4a-49e2-8be3-8f97d1d7a3b2 Example: Show the copy button on `focusin` of the target ref https://github.com/Shopify/polaris/assets/32409546/49c078f0-158a-40b3-8494-c15d94f3d0ea --------- Co-authored-by: Lo Kim <lo.kim@shopify.com> Co-authored-by: Sam Rose <11774595+sam-b-rose@users.noreply.github.com>
- Loading branch information
1 parent
cbb28ec
commit c3b6ffe
Showing
9 changed files
with
544 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@shopify/polaris': minor | ||
--- | ||
|
||
Added `useHover`, `useFocus`, `useFocusIn`, and `useMediaQuery` hooks for building Copy to Clipboard actions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import React, {useRef} from 'react'; | ||
import {mount} from 'tests/utilities'; | ||
|
||
import {useFocus, useFocusIn} from '../use-focus'; | ||
|
||
describe('useFocus', () => { | ||
it('returns false by default', () => { | ||
function App() { | ||
const ref = useRef(null); | ||
const isFocused = useFocus(ref); | ||
return <div ref={ref}>{String(isFocused)}</div>; | ||
} | ||
|
||
const app = mount(<App />); | ||
|
||
expect(app).toContainReactText('false'); | ||
}); | ||
|
||
it('returns true on focus', () => { | ||
function App() { | ||
const ref = useRef(null); | ||
const isFocused = useFocus(ref); | ||
return <div ref={ref}>{String(isFocused)}</div>; | ||
} | ||
|
||
const app = mount(<App />); | ||
const div = app.find('div')!.domNode!; | ||
|
||
app.act(() => { | ||
div.dispatchEvent(new Event('focus')); | ||
}); | ||
|
||
expect(app).toContainReactText('true'); | ||
}); | ||
|
||
it('returns false on focus and blur', () => { | ||
function App() { | ||
const ref = useRef(null); | ||
const isFocused = useFocus(ref); | ||
return <div ref={ref}>{String(isFocused)}</div>; | ||
} | ||
|
||
const app = mount(<App />); | ||
const div = app.find('div')!.domNode!; | ||
|
||
app.act(() => { | ||
div.dispatchEvent(new Event('focus')); | ||
div.dispatchEvent(new Event('blur')); | ||
}); | ||
|
||
expect(app).toContainReactText('false'); | ||
}); | ||
}); | ||
|
||
describe('useFocusIn', () => { | ||
it('returns false by default', () => { | ||
function App() { | ||
const ref = useRef(null); | ||
const isFocusedIn = useFocusIn(ref); | ||
return <div ref={ref}>{String(isFocusedIn)}</div>; | ||
} | ||
|
||
const app = mount(<App />); | ||
|
||
expect(app).toContainReactText('false'); | ||
}); | ||
|
||
it('returns true on focusin', () => { | ||
function App() { | ||
const ref = useRef(null); | ||
const isFocusedIn = useFocusIn(ref); | ||
return <div ref={ref}>{String(isFocusedIn)}</div>; | ||
} | ||
|
||
const app = mount(<App />); | ||
const div = app.find('div')!.domNode!; | ||
|
||
app.act(() => { | ||
div.dispatchEvent(new Event('focusin')); | ||
}); | ||
|
||
expect(app).toContainReactText('true'); | ||
}); | ||
|
||
it('returns false on focusin and focusout', () => { | ||
jest.useFakeTimers(); | ||
|
||
function App() { | ||
const ref = useRef(null); | ||
const isFocusedIn = useFocusIn(ref); | ||
return <div ref={ref}>{String(isFocusedIn)}</div>; | ||
} | ||
|
||
const app = mount(<App />); | ||
const div = app.find('div')!.domNode!; | ||
|
||
app.act(() => { | ||
div.dispatchEvent(new Event('focusin')); | ||
div.dispatchEvent(new Event('focusout')); | ||
}); | ||
|
||
// Remains true until the next turn of the event loop (See next test) | ||
expect(app).toContainReactText('true'); | ||
|
||
app.act(() => jest.advanceTimersByTime(1)); | ||
|
||
// Updates to false on the next turn of the event loop | ||
expect(app).toContainReactText('false'); | ||
}); | ||
|
||
it('returns true on focusin between children', () => { | ||
jest.useFakeTimers(); | ||
|
||
function App() { | ||
const ref = useRef(null); | ||
const isFocusedIn = useFocusIn(ref); | ||
return <div ref={ref}>{String(isFocusedIn)}</div>; | ||
} | ||
|
||
const app = mount(<App />); | ||
const div = app.find('div')!.domNode!; | ||
|
||
app.act(() => { | ||
div.dispatchEvent(new Event('focusin')); | ||
div.dispatchEvent(new Event('focusout')); | ||
}); | ||
|
||
// Remains true until the next turn of the event loop | ||
expect(app).toContainReactText('true'); | ||
|
||
// Subsequent focusin events clear the deferred focusout | ||
app.act(() => { | ||
div.dispatchEvent(new Event('focusin')); | ||
}); | ||
|
||
app.act(() => jest.advanceTimersByTime(1)); | ||
|
||
// Remains true on the next turn of the event loop | ||
expect(app).toContainReactText('true'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import React, {useRef} from 'react'; | ||
import {mount} from 'tests/utilities'; | ||
|
||
import {useHover} from '../use-hover'; | ||
|
||
describe('useHover', () => { | ||
it('returns false by default', () => { | ||
function App() { | ||
const ref = useRef(null); | ||
const isHovered = useHover(ref); | ||
return <div ref={ref}>{String(isHovered)}</div>; | ||
} | ||
|
||
const app = mount(<App />); | ||
|
||
expect(app).toContainReactText('false'); | ||
}); | ||
|
||
it('returns true on mouseenter', () => { | ||
function App() { | ||
const ref = useRef(null); | ||
const isHovered = useHover(ref); | ||
return <div ref={ref}>{String(isHovered)}</div>; | ||
} | ||
|
||
const app = mount(<App />); | ||
const div = app.find('div')!.domNode!; | ||
|
||
app.act(() => { | ||
div.dispatchEvent(new Event('mouseenter')); | ||
}); | ||
|
||
expect(app).toContainReactText('true'); | ||
}); | ||
|
||
it('returns false on mouseenter and mouseleave', () => { | ||
function App() { | ||
const ref = useRef(null); | ||
const isHovered = useHover(ref); | ||
return <div ref={ref}>{String(isHovered)}</div>; | ||
} | ||
|
||
const app = mount(<App />); | ||
const div = app.find('div')!.domNode!; | ||
|
||
app.act(() => { | ||
div.dispatchEvent(new Event('mouseenter')); | ||
div.dispatchEvent(new Event('mouseleave')); | ||
}); | ||
|
||
expect(app).toContainReactText('false'); | ||
}); | ||
}); |
Oops, something went wrong.