Skip to content

Commit

Permalink
fix(CopyButton): use tooltip styles for feedback and add animation (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
emyarod authored and asudoh committed Nov 19, 2019
1 parent bf14480 commit 6c0aaba
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 144 deletions.
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

0 comments on commit 6c0aaba

Please sign in to comment.