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

Add internal text to switch component #3327

Merged
merged 15 commits into from
Feb 6, 2019
2 changes: 2 additions & 0 deletions packages/core/src/common/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,11 @@ export const INPUT_ACTION = `${INPUT}-action`;

export const CONTROL = `${NS}-control`;
export const CONTROL_INDICATOR = `${CONTROL}-indicator`;
export const CONTROL_INDICATOR_CHILD = `${CONTROL_INDICATOR}-child`;
export const CHECKBOX = `${NS}-checkbox`;
export const RADIO = `${NS}-radio`;
export const SWITCH = `${NS}-switch`;
export const SWITCH_INNER_TEXT = `${SWITCH}-inner-text`;
export const FILE_INPUT = `${NS}-file-input`;
export const FILE_UPLOAD_INPUT = `${NS}-file-upload-input`;

Expand Down
44 changes: 42 additions & 2 deletions packages/core/src/components/forms/_controls.scss
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,12 @@ $control-indicator-spacing: $pt-grid-size !default;
$switch-indicator-margin: 2px !default;
$switch-indicator-size: calc(1em - #{$switch-indicator-margin * 2});

$switch-indicator-child-height: 1em;
$switch-indicator-child-outside-margin: 0.5em;
$switch-indicator-child-inside-margin: 1.2em;

$switch-indicator-text-font-size: 0.7em;

$switch-background-color: rgba($gray4, 0.5) !default;
$switch-background-color-hover: rgba($gray2, 0.5) !default;
$switch-background-color-active: rgba($gray1, 0.5) !default;
Expand Down Expand Up @@ -350,7 +356,8 @@ $control-indicator-spacing: $pt-grid-size !default;
// override default button styles, never have a box-shadow here.
// stylelint-disable-next-line declaration-no-important
box-shadow: none !important;
width: $switch-width;
width: auto;
min-width: $switch-width;
transition: background-color $pt-transition-duration $pt-transition-ease;

&::before {
Expand All @@ -368,7 +375,7 @@ $control-indicator-spacing: $pt-grid-size !default;

input:checked ~ .#{$ns}-control-indicator::before {
// 1em is size of indicator
left: $switch-width - 1em;
left: calc(100% - 1em);
}

&.#{$ns}-large {
Expand Down Expand Up @@ -401,6 +408,39 @@ $control-indicator-spacing: $pt-grid-size !default;
box-shadow: inset $dark-button-box-shadow;
}
}

.#{$ns}-switch-inner-text {
text-align: center;
font-size: $switch-indicator-text-font-size;
}

.#{$ns}-control-indicator-child {
&:first-child {
visibility: hidden;
margin-right: $switch-indicator-child-inside-margin;
margin-left: $switch-indicator-child-outside-margin;
line-height: 0;
}

&:last-child {
visibility: visible;
margin-right: $switch-indicator-child-outside-margin;
margin-left: $switch-indicator-child-inside-margin;
line-height: $switch-indicator-child-height;
}
}

input:checked ~ .#{$ns}-control-indicator .#{$ns}-control-indicator-child {
&:first-child {
visibility: visible;
line-height: $switch-indicator-child-height;
}

&:last-child {
visibility: hidden;
line-height: 0;
}
}
}

.#{$ns}-dark & {
Expand Down
42 changes: 39 additions & 3 deletions packages/core/src/components/forms/controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export interface IControlProps extends IProps, HTMLInputProps {
interface IControlInternalProps extends IControlProps {
type: "checkbox" | "radio";
typeClassName: string;
indicatorChildren?: React.ReactNode;
}

/**
Expand All @@ -92,6 +93,7 @@ const Control: React.SFC<IControlInternalProps> = ({
alignIndicator,
children,
className,
indicatorChildren,
inline,
inputRef,
label,
Expand All @@ -117,7 +119,7 @@ const Control: React.SFC<IControlInternalProps> = ({
return (
<TagName className={classes} style={style}>
<input {...htmlProps} ref={inputRef} type={type} />
<span className={Classes.CONTROL_INDICATOR} />
<span className={Classes.CONTROL_INDICATOR}>{indicatorChildren}</span>
{label}
{labelElement}
{children}
Expand All @@ -129,13 +131,47 @@ const Control: React.SFC<IControlInternalProps> = ({
// Switch
//

export interface ISwitchProps extends IControlProps {}
export interface ISwitchProps extends IControlProps {
/**
* Text to display inside the switch indicator when checked.
* If `innerLabel` is provided and this prop is omitted, then `innerLabel`
* will be used for both states.
* @default innerLabel
*/
innerLabelChecked?: string;

/**
* Text to display inside the switch indicator when unchecked.
*/
innerLabel?: string;
}

export class Switch extends React.PureComponent<ISwitchProps> {
public static displayName = `${DISPLAYNAME_PREFIX}.Switch`;

public render() {
return <Control {...this.props} type="checkbox" typeClassName={Classes.SWITCH} />;
const { innerLabelChecked, innerLabel, ...controlProps } = this.props;
const switchLabels =
innerLabel || innerLabelChecked
? [
<div key="checked" className={Classes.CONTROL_INDICATOR_CHILD}>
<div className={Classes.SWITCH_INNER_TEXT}>
{innerLabelChecked ? innerLabelChecked : innerLabel}
</div>
</div>,
<div key="unchecked" className={Classes.CONTROL_INDICATOR_CHILD}>
<div className={Classes.SWITCH_INNER_TEXT}>{innerLabel}</div>
</div>,
]
: null;
return (
<Control
{...controlProps}
type="checkbox"
typeClassName={Classes.SWITCH}
indicatorChildren={switchLabels}
/>
);
}
}

Expand Down
16 changes: 13 additions & 3 deletions packages/core/test/controls/controlsTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,17 @@ describe("Controls:", () => {
});
});

controlsTests(Switch, "checkbox", Classes.SWITCH);
controlsTests(Switch, "checkbox", Classes.SWITCH, () => {
describe("internal text", () => {
const switchWithText = mount(<Switch innerLabelChecked="Checked text" innerLabel="Unchecked text" />);
it("renders internal text", () => {
giladgray marked this conversation as resolved.
Show resolved Hide resolved
const innerTextNodes = switchWithText.find(`.${Classes.SWITCH}-inner-text`);
assert.lengthOf(innerTextNodes, 2);
const uncheckedText = innerTextNodes.last().text();
assert.equal(uncheckedText.trim(), "Unchecked text");
});
});
});

controlsTests(Radio, "radio", Classes.RADIO);

Expand All @@ -51,7 +61,7 @@ describe("Controls:", () => {
it("supports JSX children", () => {
const control = mountControl({}, <span className="jsx-child" key="jsx" />, "Label Text");
assert.lengthOf(control.find(".jsx-child"), 1);
assert.equal(control.text(), "Label Text");
assert.equal(control.text().trim(), "Label Text");
});

it("supports JSX labelElement", () => {
Expand All @@ -60,7 +70,7 @@ describe("Controls:", () => {

const control = mountControl({ labelElement: <strong>boom</strong> });
assert.lengthOf(control.find("strong"), 1);
assert.equal(control.text(), "boom");
assert.equal(control.text().trim(), "boom");
});

if (moreTests != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class SwitchExample extends CheckboxExample {
<Switch {...this.state} labelElement={<strong>Enabled</strong>} />
<Switch {...this.state} labelElement={<em>Public</em>} />
<Switch {...this.state} labelElement={<u>Cooperative</u>} defaultChecked={true} />
<Switch {...this.state} labelElement={"Containing Text"} innerLabelChecked="on" innerLabel="off" />
</div>
<small style={{ width: "100%", textAlign: "center" }}>
This example uses <Code>labelElement</Code> to demonstrate JSX labels.
Expand Down