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

Spinner size prop replaces small/large #2579

Merged
merged 4 commits into from
Jun 11, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 4 additions & 9 deletions packages/core/src/components/forms/_input-group.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

@import "../../common/variables";
@import "../button/common";
@import "../spinner/common";

/*
Input groups
Expand Down Expand Up @@ -84,17 +83,16 @@ $input-button-height-large: $pt-button-height !default;
// direct descendant to exclude icons in buttons
> .#{$ns}-icon {
@include pt-icon($pt-icon-size-standard);

// bump icon up so it sits above input
z-index: 1;
margin: ($pt-input-height - $pt-icon-size-standard) / 2;
color: $pt-icon-color;
}

// adjusting the margin of spinners in input groups
// we have to avoid targetting buttons that contain a spinner
> .#{$ns}-icon,
.#{$ns}-input-action > .#{$ns}-spinner {
margin: ($pt-input-height - $spinner-width * $spinner-width-factor-small) / 2;
margin: ($pt-input-height - $pt-icon-size-standard) / 2;
}

.#{$ns}-tag {
Expand Down Expand Up @@ -146,7 +144,8 @@ $input-button-height-large: $pt-button-height !default;
margin: ($pt-input-height-large - $input-button-height-large) / 2;
}

> .#{$ns}-icon {
> .#{$ns}-icon,
.#{$ns}-input-action > .#{$ns}-spinner {
margin: ($pt-input-height-large - $pt-icon-size-standard) / 2;
}

Expand All @@ -161,10 +160,6 @@ $input-button-height-large: $pt-button-height !default;
padding-right: $pt-button-height-large;
}
}

.#{$ns}-input-action > .#{$ns}-spinner {
margin: ($pt-input-height-large - $spinner-width * $spinner-width-factor-small) / 2;
}
}

&.#{$ns}-fill {
Expand Down
23 changes: 0 additions & 23 deletions packages/core/src/components/spinner/_common.scss

This file was deleted.

38 changes: 1 addition & 37 deletions packages/core/src/components/spinner/_spinner.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,13 @@

@import "../../common/variables";
@import "../progress-bar/common";
@import "./common";

/*
Spinners

Markup:
<svg class="#{$ns}-spinner {{.modifier}}" viewBox="0 0 100 100">
<path class="#{$ns}-spinner-track" d="M 50,50 m 0,-44.5 a 44.5,44.5 0 1 1 0,89 a 44.5,44.5 0 1 1 0,-89"></path>
<path class="#{$ns}-spinner-head" d="M 94.5 50 A 44.5 44.5 0 0 0 50 5.5"></path>
</svg>

.#{$ns}-small - Small spinner
.#{$ns}-large - Large spinner
.#{$ns}-intent-primary - All four intents are supported
.#{$ns}-no-spin - Disable spinning animation

Styleguide spinner
*/

@keyframes pt-spinner-animation {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

.#{$ns}-spinner {
@include spinner-size($spinner-width-factor, $spinner-stroke-width, $spinner-speed);
// allow paths to overflow container -- critical for edges of circles!
overflow: visible;
vertical-align: middle;
Expand All @@ -40,8 +21,7 @@ Styleguide spinner
.#{$ns}-spinner-head {
transform-origin: center;
transition: stroke-dashoffset ($pt-transition-duration * 2) $pt-transition-ease;
animation: pt-spinner-animation $spinner-speed linear infinite;
animation-duration: inherit;
animation: pt-spinner-animation ($pt-transition-duration * 5) linear infinite;
stroke: $progress-head-color;
stroke-linecap: round;
}
Expand All @@ -53,22 +33,6 @@ Styleguide spinner
&.#{$ns}-no-spin .#{$ns}-spinner-head {
animation: none;
}

&.#{$ns}-small {
@include spinner-size(
$spinner-width-factor-small,
$spinner-stroke-width-small,
$spinner-speed-small
);
}

&.#{$ns}-large {
@include spinner-size(
$spinner-width-factor-large,
$spinner-stroke-width-large,
$spinner-speed-large
);
}
}

.#{$ns}-dark .#{$ns}-spinner {
Expand Down
12 changes: 2 additions & 10 deletions packages/core/src/components/spinner/spinner.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@

Spinners indicate indeterminate progress.

@## CSS API

You can create spinners manually by inserting their whole markup into your HTML.
Spinners created via markup use same modifier classes as the
[React `Spinner` component](#core/components/progress/spinner.javascript-api).

@css spinner

@## JavaScript API

The `Spinner` component is available in the __@blueprintjs/core__ package.
Expand All @@ -31,8 +23,8 @@ are supported via the `className` prop.
correctly because they rely on CSS animations, not transitions.
</div>

[msdn-css-svg]: https://developer.microsoft.com/en-us/microsoft-edge/platform/status/csstransitionsforsvgelements/?q=svg

@reactExample SpinnerExample

@interface ISpinnerProps

[msdn-css-svg]: https://developer.microsoft.com/en-us/microsoft-edge/platform/status/csstransitionsforsvgelements/?q=svg
55 changes: 36 additions & 19 deletions packages/core/src/components/spinner/spinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@ const SPINNER_TRACK = "M 50,50 m 0,-44.5 a 44.5,44.5 0 1 1 0,89 a 44.5,44.5 0 1
// this value is the result of `<path d={SPINNER_TRACK} />.getTotalLength()` and works in all browsers:
const PATH_LENGTH = 280;

export interface ISpinnerProps extends IProps, IIntentProps {
/** Whether this spinner should use large styles. */
large?: boolean;
const MIN_SIZE = 10;
const STROKE_WIDTH = 4;
const MIN_STROKE_WIDTH = 16;

/** Whether this spinner should use small styles. */
small?: boolean;
export interface ISpinnerProps extends IProps, IIntentProps {
/**
* Width and height of the spinner in pixels. The size cannot be less than
* 10px. Constants are available for common sizes: `Spinner.SIZE_SMALL`,
* `Spinner.SIZE_STANDARD`, `Spinner.SIZE_LARGE`.
* @default Spinner.SIZE_STANDARD = 50
*/
size?: number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iconSize only allows passing in 16 or 20. Do we really want to support anything here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, you can pass any number: http://blueprintjs.com/docs/v2/#core/components/icon

but since icons are hinted at 16 and 20, you shouldn't use any other size unless you know exactly what you're doing. not true for spinners, they look good at any size


/**
* A value between 0 and 1 (inclusive) representing how far along the operation is.
Expand All @@ -37,31 +43,42 @@ export interface ISpinnerProps extends IProps, IIntentProps {
export class Spinner extends React.PureComponent<ISpinnerProps, {}> {
public static displayName = "Blueprint2.Spinner";

public static readonly SIZE_SMALL = 24;
public static readonly SIZE_STANDARD = 50;
public static readonly SIZE_LARGE = 100;

public render() {
const { className, intent, large, small, value } = this.props;
const { className, intent, value } = this.props;
const size = this.getSize();

const classes = classNames(
Classes.SPINNER,
Classes.intentClass(intent),
{
[Classes.LARGE]: large,
[Classes.SMALL]: small,
[Classes.SPINNER_NO_SPIN]: value != null,
},
{ [Classes.SPINNER_NO_SPIN]: value != null },
className,
);

const headStyle: React.CSSProperties = {
strokeDasharray: `${PATH_LENGTH} ${PATH_LENGTH}`,
// default to quarter-circle when indeterminate
// IE11: CSS transitions on SVG elements are Not Supported :(
strokeDashoffset: PATH_LENGTH - PATH_LENGTH * (value == null ? 0.25 : clamp(value, 0, 1)),
};
// attempt to keep spinner stroke width constant at all sizes
const strokeWidth = Math.min(MIN_STROKE_WIDTH, STROKE_WIDTH * Spinner.SIZE_LARGE / size);

const strokeOffset = PATH_LENGTH - PATH_LENGTH * (value == null ? 0.25 : clamp(value, 0, 1));

return (
<svg className={classes} viewBox="0 0 100 100">
<svg className={classes} height={size} width={size} viewBox="0 0 100 100" strokeWidth={strokeWidth}>
<path className={Classes.SPINNER_TRACK} d={SPINNER_TRACK} />
<path className={Classes.SPINNER_HEAD} d={SPINNER_TRACK} pathLength={PATH_LENGTH} style={headStyle} />
<path
className={Classes.SPINNER_HEAD}
d={SPINNER_TRACK}
pathLength={PATH_LENGTH}
strokeDasharray={`${PATH_LENGTH} ${PATH_LENGTH}`}
strokeDashoffset={strokeOffset}
/>
</svg>
);
}

private getSize() {
const { size = Spinner.SIZE_STANDARD } = this.props;
return Math.max(MIN_SIZE, size);
}
}
8 changes: 5 additions & 3 deletions packages/core/test/spinner/spinnerTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ describe("Spinner", () => {
});

function assertStrokePercent(wrapper: ReactWrapper<any, {}>, percent: number) {
const style = wrapper.find(`.${Classes.SPINNER_HEAD}`).prop("style");
const pathLength = parseInt(style.strokeDasharray.split(" ")[0], 10);
assert.strictEqual(style.strokeDashoffset, pathLength * (1 - percent));
const head = wrapper.find(`.${Classes.SPINNER_HEAD}`);
// NOTE: strokeDasharray is string "X X", but parseInt terminates at non-numeric character
const pathLength = parseInt(head.prop("strokeDasharray").toString(), 10);
const offset = head.prop("strokeDashoffset");
assert.strictEqual(offset, pathLength * (1 - percent));
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import * as React from "react";

import {
Button,
Classes,
H5,
Icon,
InputGroup,
Intent,
Menu,
Expand Down Expand Up @@ -48,7 +48,7 @@ export class InputGroupExample extends React.PureComponent<IExampleProps, IInput
public render() {
const { disabled, filterValue, large, showPassword, tagValue } = this.state;

const maybeSpinner = filterValue ? <Spinner className={Classes.SMALL} /> : undefined;
const maybeSpinner = filterValue ? <Spinner size={Icon.SIZE_STANDARD} /> : undefined;

const lockButton = (
<Tooltip content={`${showPassword ? "Hide" : "Show"} Password`} disabled={disabled}>
Expand Down
39 changes: 15 additions & 24 deletions packages/docs-app/src/examples/core-examples/spinnerExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,32 @@

import * as React from "react";

import { Classes, H5, Intent, Label, Slider, Spinner, Switch } from "@blueprintjs/core";
import { H5, Intent, Label, Slider, Spinner, Switch } from "@blueprintjs/core";
import { Example, handleBooleanChange, handleStringChange, IExampleProps } from "@blueprintjs/docs-theme";
import { IntentSelect } from "./common/intentSelect";

const SIZES = [
{ label: "Default", value: "" },
{ label: "Small", value: Classes.SMALL },
{ label: "Large", value: Classes.LARGE },
];

export interface ISpinnerExampleState {
hasValue: boolean;
intent?: Intent;
size: string;
size: number;
value: number;
}

export class SpinnerExample extends React.PureComponent<IExampleProps, ISpinnerExampleState> {
public state: ISpinnerExampleState = {
hasValue: false,
size: "",
size: Spinner.SIZE_STANDARD,
value: 0.7,
};

private handleIndeterminateChange = handleBooleanChange(hasValue => this.setState({ hasValue }));
private handleModifierChange = handleStringChange((intent: Intent) => this.setState({ intent }));
private handleSizeChange = handleStringChange(size => this.setState({ size }));

public render() {
const { size, hasValue, intent, value } = this.state;
return (
<Example options={this.renderOptions()} {...this.props}>
<Spinner
intent={intent}
large={size === Classes.LARGE}
small={size === Classes.SMALL}
value={hasValue ? value : null}
/>
<Spinner intent={intent} size={size} value={hasValue ? value : null} />
</Example>
);
}
Expand All @@ -54,14 +42,16 @@ export class SpinnerExample extends React.PureComponent<IExampleProps, ISpinnerE
<>
<H5>Props</H5>
<IntentSelect intent={intent} onChange={this.handleModifierChange} />
<Label>
Size
<div className={Classes.SELECT}>
<select value={size} onChange={this.handleSizeChange}>
{SIZES.map((opt, i) => <option key={i} {...opt} />)}
</select>
</div>
</Label>
<Label>Size</Label>
<Slider
labelStepSize={50}
min={0}
max={Spinner.SIZE_LARGE * 2}
showTrackFill={false}
stepSize={10}
value={size}
onChange={this.handleSizeChange}
/>
<Switch checked={hasValue} label="Known value" onChange={this.handleIndeterminateChange} />
<Slider
disabled={!hasValue}
Expand All @@ -81,4 +71,5 @@ export class SpinnerExample extends React.PureComponent<IExampleProps, ISpinnerE
private renderLabel = (value: number) => value.toFixed(1);

private handleValueChange = (value: number) => this.setState({ value });
private handleSizeChange = (size: number) => this.setState({ size });
}