Skip to content

Commit

Permalink
jobs_v2: refactor CopyToClipboardButton
Browse files Browse the repository at this point in the history
also make the success/failure feedback disappear on a timer
instead of on blur, which is more consistent with what other
sites do (also the blur doesn't work quite right on Safari)

test plan:
 - the copy-to-clipboard button in the jobs list should
   behave as expected

refs DE-1142

Change-Id: I95bd757bc182c6b16ebcc9c8c5de576b2ad32313
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/289774
Tested-by: Service Cloud Jenkins <[email protected]>
Reviewed-by: Aaron Ogata <[email protected]>
QA-Review: Jeremy Stanley <[email protected]>
Product-Review: Jeremy Stanley <[email protected]>
  • Loading branch information
jstanley0 committed Apr 19, 2022
1 parent affdc50 commit 434b64d
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 30 deletions.
33 changes: 3 additions & 30 deletions ui/features/jobs_v2/react/components/JobsTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,20 @@

import {useScope as useI18nScope} from '@canvas/i18n'
import {Table} from '@instructure/ui-table'
import React, {useCallback, useState} from 'react'
import React, {useCallback} from 'react'
import {Flex} from '@instructure/ui-flex'
import {Responsive} from '@instructure/ui-responsive'
import {IconCopyLine, IconXSolid, IconCheckDarkSolid} from '@instructure/ui-icons'
import {IconButton} from '@instructure/ui-buttons'
import {Link} from '@instructure/ui-link'
import {Tooltip} from '@instructure/ui-tooltip'
import {Text} from '@instructure/ui-text'
import {TruncateText} from '@instructure/ui-truncate-text'
import {InfoColumn, InfoColumnHeader} from './InfoColumn'
import SortColumnHeader from './SortColumnHeader'
import CopyToClipboardButton from '@canvas/copy-to-clipboard-button'

const I18n = useI18nScope('jobs_v2')

function CopyToClipboardTruncatedValue({value, onClick}) {
const [feedback, setFeedback] = useState(null)

const copyToClipboardAction = useCallback(() => {
return navigator.clipboard.writeText(value).then(
() => setFeedback(true),
() => setFeedback(false)
)
}, [setFeedback, value])

const handleBlur = useCallback(() => {
setFeedback(null)
}, [setFeedback])

const renderFeedbackIcon = useCallback(() => {
if (feedback === true) return <IconCheckDarkSolid color="success" />
else if (feedback === false) return <IconXSolid color="error" />
else return <IconCopyLine />
}, [feedback])

if (!value) {
return <Text color="secondary">-</Text>
}
Expand All @@ -68,14 +48,7 @@ function CopyToClipboardTruncatedValue({value, onClick}) {
</Flex.Item>
<Flex.Item>
<div className="copy-button-container-cell">
<IconButton
size="small"
onClick={copyToClipboardAction}
screenReaderLabel={I18n.t('Copy')}
onBlur={handleBlur}
>
{renderFeedbackIcon()}
</IconButton>
<CopyToClipboardButton value={value} />
</div>
</Flex.Item>
</Flex>
Expand Down
7 changes: 7 additions & 0 deletions ui/shared/copy-to-clipboard-button/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "@canvas/copy-to-clipboard-button",
"private": true,
"version": "1.0.0",
"author": "Jeremy Stanley",
"main": "./react/index.js"
}
42 changes: 42 additions & 0 deletions ui/shared/copy-to-clipboard-button/react/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2022 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React from 'react'
import {render, fireEvent} from '@testing-library/react'
import CopyToClipboardButton from '../index'

describe('CopyToClipboardButton', () => {
let originalClipboard

beforeAll(() => {
const writeText = jest.fn(() => Promise.resolve({}))
originalClipboard = navigator.clipboard
navigator.clipboard = {writeText}
})

afterAll(() => {
navigator.clipboard = originalClipboard
})

it('copies value when the button is clicked', () => {
const {getByRole} = render(<CopyToClipboardButton value="foobar" />)
const button = getByRole('button', {name: 'Copy'})
fireEvent.click(button)
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('foobar')
})
})
65 changes: 65 additions & 0 deletions ui/shared/copy-to-clipboard-button/react/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2022 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React, {useCallback, useState} from 'react'
import {IconCheckDarkSolid, IconCopyLine, IconXSolid} from '@instructure/ui-icons'
import {IconButton} from '@instructure/ui-buttons'
import PropTypes from 'prop-types'
import {useScope as useI18nScope} from '@canvas/i18n'

const I18n = useI18nScope('copy-to-clipboard-button')

export default function CopyToClipboardButton({value, screenReaderLabel}) {
const [feedback, setFeedback] = useState(null)

const temporarilySetFeedback = useCallback(
success => {
setFeedback(success)
setTimeout(() => setFeedback(null), 1000)
},
[setFeedback]
)

const copyToClipboardAction = useCallback(() => {
return navigator.clipboard.writeText(value).then(
() => temporarilySetFeedback(true),
() => temporarilySetFeedback(false)
)
}, [temporarilySetFeedback, value])

const renderFeedbackIcon = useCallback(() => {
if (feedback === true) return <IconCheckDarkSolid color="success" />
else if (feedback === false) return <IconXSolid color="error" />
else return <IconCopyLine />
}, [feedback])

return (
<IconButton size="small" onClick={copyToClipboardAction} screenReaderLabel={screenReaderLabel}>
{renderFeedbackIcon()}
</IconButton>
)
}

CopyToClipboardButton.propTypes = {
value: PropTypes.string,
screenReaderLabel: PropTypes.string
}

CopyToClipboardButton.defaultProps = {
screenReaderLabel: I18n.t('Copy')
}

0 comments on commit 434b64d

Please sign in to comment.