Skip to content

Commit

Permalink
feat(ripple): Add new states mixins (#1624)
Browse files Browse the repository at this point in the history
  • Loading branch information
kfranqueiro authored Nov 30, 2017
1 parent 9ec35b7 commit 9356449
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 44 deletions.
27 changes: 25 additions & 2 deletions packages/mdc-ripple/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,38 @@ General notes:

#### Sass API

In order to fully style states as well as the ripple effect for pressed state, both `mdc-ripple` mixins below must be included, as well as either the basic `mdc-states-color` mixin or all of the advanced `mdc-states` mixins documented below.

Once these styles are in place for a component, it is feasible to further override only the parts necessary (e.g. `mdc-states-color` specifically) for specific variants (e.g. for flat vs. raised buttons).

These APIs implicitly use pseudo-elements for the ripple effect: `::before` for the background, and `::after` for the foreground.
All three of the following mixins are mandatory in order to fully style the ripple effect; from that point, it is feasible to further override only the parts necessary (e.g. `mdc-ripple-color` specifically) for variants of a component.

##### Ripple Mixins

Mixin | Description
--- | ---
`mdc-ripple-surface` | Adds base styles for a ripple surface
`mdc-ripple-color($color, $opacity)` | Adds styles for the color and opacity of the ripple effect
`mdc-ripple-radius($radius)` | Adds styles for the radius of the ripple effect,<br>for both bounded and unbounded ripples

##### Basic States Mixin

Mixin | Description
--- | ---
`mdc-states($color, $has-nested-focusable-element)` | Adds state and ripple styles for the indicated color, deciding opacities based on whether the passed color is light or dark. `$has-nested-focusable-element` defaults to `false` but should be set to `true` if the component contains a focusable element (e.g. an input) under the root node.

##### Advanced States Mixins

Mixin | Description
--- | ---
`mdc-states-base-color($color)` | Sets up base state styles using the provided color
`mdc-states-hover-opacity($opacity)` | Adds styles for hover state using the provided opacity
`mdc-states-focus-opacity($opacity, $has-nested-focusable-element)` | Adds styles for focus state using the provided opacity. `$has-nested-focusable-element` defaults to `false` but should be set to `true` if the component contains a focusable element (e.g. an input) under the root node.
`mdc-states-press-opacity($opacity)` | Adds styles for press state using the provided opacity

#### Legacy Sass API

The `mdc-ripple-color($color, $opacity)` mixin is deprecated. Use the basic or advanced states mixins (documented above) instead, which provide finer control over a component's opacity for different states of user interaction.

### Adding Ripple JS

First import the ripple JS.
Expand Down
5 changes: 3 additions & 2 deletions packages/mdc-ripple/_keyframes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// MDC Ripple keyframes are split into their own file so that _mixins.scss can rely on them.

@import "@material/animation/variables";
@import "./variables";

@mixin mdc-ripple-keyframes_ {
@keyframes mdc-ripple-fg-radius-in {
Expand All @@ -41,14 +42,14 @@
}

to {
opacity: 1;
opacity: var(--mdc-ripple-fg-opacity, map-get($mdc-ripple-dark-ink-opacities, "press"));
}
}

@keyframes mdc-ripple-fg-opacity-out {
from {
animation-timing-function: linear;
opacity: 1;
opacity: var(--mdc-ripple-fg-opacity, map-get($mdc-ripple-dark-ink-opacities, "press"));
}

to {
Expand Down
164 changes: 129 additions & 35 deletions packages/mdc-ripple/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
//

@import "@material/animation/variables";
@import "@material/theme/variables";
@import "@material/theme/functions";
@import "@material/theme/mixins";
@import "./keyframes";
@import "./variables";

// Ensure that styles needed by any component using MDC Ripple are emitted, but only once.
// (Every component using MDC Ripple imports these mixins, but doesn't necessarily import mdc-ripple.scss.)
Expand Down Expand Up @@ -53,64 +55,120 @@ $mdc-ripple-common-styles-emitted_: false !default;
--mdc-ripple-fg-translate-end: 0;
--mdc-ripple-fg-translate-start: 0;

will-change: transform, opacity;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);

&::before,
&::after {
position: absolute;
transition: opacity 250ms linear;
border-radius: 50%;
pointer-events: none;
content: "";
will-change: transform, opacity;
}

&::before {
transition: opacity $mdc-states-wash-duration linear;
}

// Common styles for upgraded surfaces (some of these depend on custom properties set via JS or other mixins)

&.mdc-ripple-upgraded::after {
top: 0;
left: 0;
transform: scale(0);
transform-origin: center center;
}

&.mdc-ripple-upgraded--unbounded::after {
top: var(--mdc-ripple-top, 0);
left: var(--mdc-ripple-left, 0);
}

&.mdc-ripple-upgraded--foreground-activation::after {
animation:
$mdc-ripple-translate-duration mdc-ripple-fg-radius-in forwards,
$mdc-ripple-fade-in-duration mdc-ripple-fg-opacity-in forwards;
}

&.mdc-ripple-upgraded--foreground-deactivation::after {
animation: $mdc-ripple-fade-out-duration mdc-ripple-fg-opacity-out;
// Retain transform from mdc-ripple-fg-radius-in activation
transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
}
}

@mixin mdc-ripple-color($color: black, $opacity: .06) {
@mixin mdc-states-base-color($color) {
// Opacity styles are here (rather than in mdc-ripple-surface) to ensure that opacity is re-initialized for
// cases where this mixin is used to override another inherited use of itself,
// without needing to re-include mdc-ripple-surface.
&::before,
&::after {
@include mdc-ripple-color_($color, $opacity);
@include mdc-theme-prop(background-color, $color, $edgeOptOut: true);

opacity: 0;
}
}

// Note: when :active is applied, :focus is already applied, which will effectively double the effect.
&:not(.mdc-ripple-upgraded) {
&:hover::before,
&:focus::before,
&:active::after {
transition-duration: 85ms;
opacity: .6;
}
@mixin mdc-states-hover-opacity($opacity) {
// Background wash styles, for both CSS-only and upgraded stateful surfaces
&:hover::before {
opacity: $opacity;
}
}

@mixin mdc-states-focus-opacity($opacity, $has-nested-focusable-element: false) {
// Focus overrides hover by reusing the ::before pseudo-element.
// :focus-within generally works on non-MS browsers and matches when a *child* of the element has focus.
// It is useful for cases where a component has a focusable element within the root node, e.g. text field,
// but undesirable in general in case of nested stateful components.
// We use a modifier class for JS-enabled surfaces to support all use cases in all browsers.
$cssOnlyFocusSelector: if(
$has-nested-focusable-element,
"&:not(.mdc-ripple-upgraded):focus::before, &:not(.mdc-ripple-upgraded):focus-within::before",
"&:not(.mdc-ripple-upgraded):focus::before"
);

#{$cssOnlyFocusSelector},
&.mdc-ripple-upgraded--background-focused::before {
opacity: .99999;
// Note that this duration is only effective on focus, not blur
transition-duration: 75ms;
opacity: $opacity;
}
}

&.mdc-ripple-upgraded--background-active-fill::before {
transition-duration: 120ms;
opacity: 1;
}
@mixin mdc-states-press-opacity($opacity) {
// Styles for non-upgraded (CSS-only) stateful surfaces

// Foreground ripple styles
&:not(.mdc-ripple-upgraded) {
// Apply press additively by using the ::after pseudo-element
&::after {
transition: opacity $mdc-ripple-fade-out-duration linear;
}

&.mdc-ripple-upgraded::after {
opacity: 0;
&:active::after {
transition-duration: $mdc-ripple-fade-in-duration;
opacity: $opacity;
}
}

&.mdc-ripple-upgraded--foreground-activation::after {
animation: 300ms mdc-ripple-fg-radius-in forwards, 83ms mdc-ripple-fg-opacity-in forwards;
&.mdc-ripple-upgraded {
--mdc-ripple-fg-opacity: $opacity;
}
}

&.mdc-ripple-upgraded--foreground-deactivation::after {
animation: 83ms mdc-ripple-fg-opacity-out;
// Retain transform from mdc-ripple-fg-radius-in activation
transform: translate(var(--mdc-ripple-fg-translate-end, 0)) scale(var(--mdc-ripple-fg-scale, 1));
}
// Simple mixin which automatically selects opacity values based on whether the ink color is light or dark.
@mixin mdc-states($color: black, $has-nested-focusable-element: false) {
$color-value: mdc-theme-prop-value($color);
$opacity-map: if(
mdc-theme-tone($color-value) == "light",
$mdc-ripple-light-ink-opacities,
$mdc-ripple-dark-ink-opacities
);

@include mdc-states-base-color($color);
@include mdc-states-hover-opacity(map-get($opacity-map, "hover"));
@include mdc-states-focus-opacity(map-get($opacity-map, "focus"), $has-nested-focusable-element);
@include mdc-states-press-opacity(map-get($opacity-map, "press"));
}

@mixin mdc-ripple-radius($radius: 100%) {
Expand Down Expand Up @@ -141,21 +199,57 @@ $mdc-ripple-common-styles-emitted_: false !default;
}

// Foreground ripple styles

&.mdc-ripple-upgraded::after {
top: 0;
left: 0;
width: var(--mdc-ripple-fg-size, $radius);
height: var(--mdc-ripple-fg-size, $radius);
transform: scale(0);
transform-origin: center center;
}
}

&.mdc-ripple-upgraded--unbounded::after {
top: var(--mdc-ripple-top, 0);
left: var(--mdc-ripple-left, 0);
//
// Legacy
//

@mixin mdc-ripple-color($color: black, $opacity: .06) {
// Opacity styles are here (rather than in mdc-ripple-surface) to ensure that opacity is re-initialized for
// cases where this mixin is used to override another inherited use of itself,
// without needing to re-include mdc-ripple-surface.
&::before,
&::after {
@include mdc-ripple-color_($color, $opacity);

opacity: 0;
}

// Note: when :active is applied, :focus is already applied, which will effectively double the effect.
&:not(.mdc-ripple-upgraded) {
&:hover::before,
&:focus::before,
&:active::after {
transition-duration: 85ms;
opacity: .6;
}
}

&.mdc-ripple-upgraded--background-focused::before {
opacity: .99999;
}

&.mdc-ripple-upgraded--background-active-fill::before {
transition-duration: 120ms;
opacity: 1;
}

&.mdc-ripple-upgraded::after {
// Set this to 1 for backwards compatibility with how the keyframes were originally coded for use with this mixin
--mdc-ripple-fg-opacity: 1;
}
}

//
// Private
//

@mixin mdc-ripple-color_($color, $opacity) {
// stylelint-disable at-rule-empty-line-before, block-closing-brace-newline-after
@if type-of($color) == "color" {
Expand Down
19 changes: 19 additions & 0 deletions packages/mdc-ripple/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,24 @@
// limitations under the License.
//

$mdc-ripple-fade-in-duration: 75ms;
$mdc-ripple-fade-out-duration: 150ms;
$mdc-ripple-translate-duration: 225ms;
$mdc-states-wash-duration: 15ms;

$mdc-ripple-dark-ink-opacities: (
hover: .04,
focus: .12,
press: .16
) !default;

$mdc-ripple-light-ink-opacities: (
hover: .08,
focus: .24,
press: .32
) !default;

// Legacy

$mdc-ripple-pressed-dark-ink-opacity: .16;
$mdc-ripple-pressed-light-ink-opacity: .32;
4 changes: 2 additions & 2 deletions packages/mdc-ripple/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const strings = {
const numbers = {
PADDING: 10,
INITIAL_ORIGIN_SCALE: 0.6,
DEACTIVATION_TIMEOUT_MS: 300,
FG_DEACTIVATION_MS: 83,
DEACTIVATION_TIMEOUT_MS: 225, // Corresponds to $mdc-ripple-translate-duration (i.e. activation animation duration)
FG_DEACTIVATION_MS: 150, // Corresponds to $mdc-ripple-fade-out-duration (i.e. deactivation animation duration)
};

export {cssClasses, strings, numbers};
6 changes: 3 additions & 3 deletions packages/mdc-ripple/mdc-ripple.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

.mdc-ripple-surface {
@include mdc-ripple-surface;
@include mdc-ripple-color;
@include mdc-states;
@include mdc-ripple-radius;

position: relative;
Expand All @@ -35,11 +35,11 @@
}

&--primary {
@include mdc-ripple-color(primary, $mdc-ripple-pressed-dark-ink-opacity);
@include mdc-states(primary);
}

&--accent {
@include mdc-ripple-color(secondary, $mdc-ripple-pressed-dark-ink-opacity);
@include mdc-states(secondary);
}
}

Expand Down

0 comments on commit 9356449

Please sign in to comment.