Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(CopyButton): use tooltip styles for feedback and add animation #4715

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions packages/components/src/components/copy-button/_copy-button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

@import '../../globals/scss/vars';
@import '../../globals/scss/typography';
@import '../../globals/scss/helper-mixins';
@import '../../globals/scss/layer';
@import '../../globals/scss/vendor/@carbon/elements/scss/import-once/import-once';
@import '../../globals/scss/css--reset';
@import '../button/button';
@import 'keyframes';

@include exports('copy-button') {
.#{$prefix}--btn--copy {
Expand Down Expand Up @@ -70,4 +72,54 @@
display: inline-flex;
}
}

// TODO: deprecate above styles
.#{$prefix}--copy-btn {
@include reset;
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
border: none;
height: $carbon--spacing-08;
width: $carbon--spacing-08;
background-color: $ui-01;

&::before {
@include tooltip--caret;
display: none;
}

.#{$prefix}--copy-btn__feedback {
@include tooltip--content('icon');
clip: auto;
margin: auto;
overflow: visible;
display: none;
}
@include tooltip--placement('icon', 'bottom', 'center');

&:focus {
@include focus-outline('outline');
outline-color: $focus;
}

&.#{$prefix}--copy-btn--animating::before,
&.#{$prefix}--copy-btn--animating .#{$prefix}--copy-btn__feedback {
display: block;
}

&.#{$prefix}--copy-btn--animating.#{$prefix}--copy-btn--fade-out::before,
&.#{$prefix}--copy-btn--animating.#{$prefix}--copy-btn--fade-out
.#{$prefix}--copy-btn__feedback {
animation: $duration--fast-02 motion(standard, productive) hide-feedback;
}

&.#{$prefix}--copy-btn--animating.#{$prefix}--copy-btn--fade-in::before,
&.#{$prefix}--copy-btn--animating.#{$prefix}--copy-btn--fade-in
.#{$prefix}--copy-btn__feedback {
animation: $duration--fast-02 motion(standard, productive) show-feedback;
}
}
}
34 changes: 34 additions & 0 deletions packages/components/src/components/copy-button/_keyframes.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// Copyright IBM Corp. 2016, 2018
//
// This source code is licensed under the Apache-2.0 license found in the
// LICENSE file in the root directory of this source tree.
//

@mixin content-visible {
visibility: visible;
opacity: 1;
}

@mixin content-hidden {
visibility: hidden;
opacity: 0;
}

@keyframes hide-feedback {
0% {
@include content-visible;
}
100% {
@include content-hidden;
}
}

@keyframes show-feedback {
0% {
@include content-hidden;
}
100% {
@include content-visible;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<!--
<!--
Copyright IBM Corp. 2016, 2018

This source code is licensed under the Apache-2.0 license found in the
LICENSE file in the root directory of this source tree.
-->

<button data-copy-btn class="{{@root.prefix}}--snippet-button" type="button" aria-label="Copy" tabindex="0">
{{ carbon-icon 'Copy16' class=(add @root.prefix '--snippet__icon')}}
<div class="{{@root.prefix}}--btn--copy__feedback" role="alert" data-feedback="Copied!"></div>
<button data-copy-btn class="{{@root.prefix}}--copy-btn" type="button" tabindex="0">
<span class="{{@root.prefix}}--assistive-text {{@root.prefix}}--copy-btn__feedback">Copied!</span>
{{ carbon-icon 'Copy16' class=(add @root.prefix '--snippet__icon' hidden="true")}}
</button>
23 changes: 23 additions & 0 deletions packages/components/src/components/copy-button/copy-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ class CopyButton extends mixin(
constructor(element, options) {
super(element, options);
this.manage(on(this.element, 'click', () => this.handleClick()));
this.manage(
on(this.element, 'animationend', event => this.handleAnimationEnd(event))
);
}

/**
* Cleanup animation classes
*/
handleAnimationEnd(event) {
if (event.animationName === 'hide-feedback') {
this.element.classList.remove(this.options.classAnimating);
this.element.classList.remove(this.options.classFadeOut);
}
}

/**
Expand All @@ -39,6 +52,13 @@ class CopyButton extends mixin(
setTimeout(() => {
feedback.classList.remove(this.options.classShowFeedback);
}, this.options.timeoutValue);
} else {
this.element.classList.add(this.options.classAnimating);
this.element.classList.add(this.options.classFadeIn);
setTimeout(() => {
this.element.classList.remove(this.options.classFadeIn);
this.element.classList.add(this.options.classFadeOut);
}, this.options.timeoutValue);
}
}

Expand Down Expand Up @@ -66,6 +86,9 @@ class CopyButton extends mixin(
selectorInit: '[data-copy-btn]',
feedbackTooltip: '[data-feedback]',
classShowFeedback: `${prefix}--btn--copy__feedback--displayed`,
classAnimating: `${prefix}--copy-btn--animating`,
classFadeIn: `${prefix}--copy-btn--fade-in`,
classFadeOut: `${prefix}--copy-btn--fade-out`,
timeoutValue: 2000,
};
}
Expand Down
100 changes: 59 additions & 41 deletions packages/components/src/globals/scss/_tooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,56 @@

@import 'layer';

// Tooltip
// Tooltip caret visual styles
/// @access public
/// @group tooltip
@mixin tooltip--caret {
position: absolute;
z-index: z('floating');
width: 0;
height: 0;
border-style: solid;
content: '';
}

// Tooltip
// Tooltip content box visual styles
/// @param {String} $tooltip-type ['icon'] - The type, from: `icon`, `definition`
/// @access public
/// @group tooltip
@mixin tooltip--content($tooltip-type: 'icon') {
@include layer('overlay');
width: max-content;
max-width: rem(208px);
height: auto;
padding: if(
$tooltip-type == 'definition',
rem(8px) rem(16px),
rem(3px) rem(16px)
);
border-radius: rem(2px);
color: $inverse-01;
font-weight: 400;
text-align: left;
transform: translateX(-50%);
pointer-events: none;
background-color: $inverse-02;
@include type-style('body-short-01');

// IE media query
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
width: rem(208px);
}
// Edge 12-15 and Edge 16 feature queries
@supports (-ms-accelerator: true) {
width: rem(208px);
}
@supports (-ms-ime-align: auto) {
width: rem(208px);
}
}

// Tooltip
// Definition and Icon CSS only tooltip
/// @param {String} $tooltip-type ['icon'] - The type, from: `icon`, `definition`
Expand Down Expand Up @@ -42,7 +92,6 @@
&::after,
.#{$prefix}--assistive-text,
+ .#{$prefix}--assistive-text {
@include type-style('body-short-01');
position: absolute;
z-index: z('floating');
display: flex;
Expand Down Expand Up @@ -88,38 +137,9 @@
// content box
// @todo Simplify CSS selectors on next major release
&::after,
&:hover .#{$prefix}--assistive-text,
&:focus .#{$prefix}--assistive-text,
&:hover + .#{$prefix}--assistive-text,
&:focus + .#{$prefix}--assistive-text {
@include layer('overlay');
width: max-content;
max-width: rem(208px);
height: auto;
padding: if(
$tooltip-type == 'definition',
rem(8px) rem(16px),
rem(3px) rem(16px)
);
border-radius: rem(2px);
color: $inverse-01;
font-weight: 400;
text-align: left;
transform: translateX(-50%);
pointer-events: none;
background-color: $inverse-02;

// IE media query
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
width: rem(208px);
}
// Edge 12-15 and Edge 16 feature queries
@supports (-ms-accelerator: true) {
width: rem(208px);
}
@supports (-ms-ime-align: auto) {
width: rem(208px);
}
.#{$prefix}--assistive-text,
+ .#{$prefix}--assistive-text {
@include tooltip--content($tooltip-type);
}

&::after {
Expand Down Expand Up @@ -186,10 +206,8 @@
// @todo Simplify CSS selectors on next major release
&::before,
&::after,
&:hover .#{$prefix}--assistive-text,
&:focus .#{$prefix}--assistive-text,
&:hover + .#{$prefix}--assistive-text,
&:focus + .#{$prefix}--assistive-text {
.#{$prefix}--assistive-text,
+ .#{$prefix}--assistive-text {
@if ($position == 'top') {
top: 0;
left: 50%;
Expand Down Expand Up @@ -238,10 +256,10 @@
// alignment options available only for top and bottom tooltip position
// @todo Simplify CSS selectors on next major release
&::after,
&:hover .#{$prefix}--assistive-text,
&:focus .#{$prefix}--assistive-text,
&:hover + .#{$prefix}--assistive-text,
&:focus + .#{$prefix}--assistive-text {
.#{$prefix}--assistive-text,
.#{$prefix}--assistive-text,
+ .#{$prefix}--assistive-text,
+ .#{$prefix}--assistive-text {
@if ($position == 'top') {
top: -$body-spacing;
@if ($align == 'start') {
Expand Down
34 changes: 14 additions & 20 deletions packages/react/src/components/CopyButton/CopyButton-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ describe('CopyButton', () => {

it('Renders children as expected', () => {
expect(wrapper.is('button')).toBe(true);
expect(wrapper.hasClass(`${prefix}--snippet-button`)).toBe(true);
expect(wrapper.find(`.${prefix}--btn--copy__feedback`).length).toBe(1);
expect(wrapper.hasClass(`${prefix}--copy-btn`)).toBe(true);
expect(wrapper.find(`.${prefix}--copy-btn__feedback`).length).toBe(1);
expect(wrapper.find(Copy16).length).toBe(1);
});

Expand All @@ -59,38 +59,32 @@ describe('CopyButton', () => {
it('Should be able to specify the feedback message', () => {
const feedbackWrapper = mount(<CopyButton feedback="Copied!" />);
expect(
feedbackWrapper.find(`.${prefix}--btn--copy__feedback`).props()[
'data-feedback'
]
feedbackWrapper.find(`.${prefix}--copy-btn__feedback`).text()
).toBe('Copied!');
});
});

describe('Renders feedback as expected', () => {
it('Should make the feedback visible', () => {
const feedbackWrapper = mount(<CopyButton feedback="Copied!" />);
const feedback = () =>
feedbackWrapper.find(`.${prefix}--btn--copy__feedback`);
expect(
feedback().hasClass(`${prefix}--btn--copy__feedback--displayed`)
).toBe(false);
feedbackWrapper.setState({ showFeedback: true });
expect(
feedback().hasClass(`${prefix}--btn--copy__feedback--displayed`)
).toBe(true);
const feedback = feedbackWrapper.find(`.${prefix}--copy-btn__feedback`);
expect(feedback).toBeFalsy;
feedbackWrapper.simulate('click');
expect(feedback).toBeTruthy;
});

it('Should show feedback for a limited amount of time', () => {
const feedbackWrapper = mount(
<CopyButton feedback="Copied!" feedbackTimeout={5000} />
);
expect(feedbackWrapper.state().showFeedback).toBe(false);
feedbackWrapper.simulate('click');
expect(feedbackWrapper.state().showFeedback).toBe(true);
expect(setTimeout.mock.calls.length).toBe(2);
expect(setTimeout.mock.calls[1][1]).toBe(5000);
jest.runAllTimers();
expect(feedbackWrapper.state().showFeedback).toBe(false);
const copyButton = feedbackWrapper.find('button');
expect(copyButton.hasClass(`${prefix}--copy-btn--animating`)).toBe(true);
setTimeout(() => {
expect(copyButton.hasClass(`${prefix}--copy-btn--animating`)).toBe(
false
);
}, 5220); // 5000 + 2 * 110 (transition duration)
});
});

Expand Down
Loading