Skip to content

Commit

Permalink
refactor(form): Created SwitchTrack and InputToggleIcon components
Browse files Browse the repository at this point in the history
  • Loading branch information
mlaursen committed Apr 22, 2021
1 parent 517f199 commit d9278b3
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 34 deletions.
19 changes: 8 additions & 11 deletions packages/form/src/toggle/InputToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { bem } from "@react-md/utils";
import { Label } from "../label/Label";
import { useFocusState } from "../useFocusState";
import { ToggleContainer } from "./ToggleContainer";
import { InputToggleIcon } from "./InputToggleIcon";

/**
* The props for a checkbox or radio input element.
Expand Down Expand Up @@ -261,20 +262,16 @@ export const InputToggle = forwardRef<HTMLInputElement, Props>(
onBlur={onBlur}
className={cn(block("input"), inputClassName)}
/>
<span
<InputToggleIcon
circle={!disableIconOverlay && type === "radio"}
disabled={disabled}
overlay={!disableIconOverlay}
indeterminate={indeterminate}
style={iconStyle}
className={cn(
block("icon", {
circle: !disableIconOverlay && type === "radio",
disabled,
overlay: !disableIconOverlay,
indeterminate,
}),
iconClassName
)}
className={iconClassName}
>
{icon}
</span>
</InputToggleIcon>
{ripples}
{children}
</span>
Expand Down
75 changes: 75 additions & 0 deletions packages/form/src/toggle/InputToggleIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { forwardRef, HTMLAttributes, ReactElement } from "react";
import cn from "classnames";
import { bem } from "@react-md/utils";

/**
* @remarks \@since 2.8.0
*/
export interface InputToggleIconProps extends HTMLAttributes<HTMLSpanElement> {
/**
* Boolean if the icon should use circle styles. This should normally be
* enabled for radio input types.
*/
circle?: boolean;

/**
* Boolean if the disabled styles should be applied.
*/
disabled?: boolean;

/**
* Boolean if using an overlay for the different icon states.
*/
overlay?: boolean;

/**
* Boolean if the icon should gain the checked state.
*/
checked?: boolean;

/**
* Boolean if using the indeterminate checkbox state.
*/
indeterminate?: boolean;
}

const styles = bem("rmd-toggle");

/**
* @remarks \@since 2.8.0
*/
export const InputToggleIcon = forwardRef<
HTMLSpanElement,
InputToggleIconProps
>(function InputToggleIcon(
{
circle = false,
disabled = false,
overlay = false,
checked = false,
indeterminate = false,
className,
children,
...props
}: InputToggleIconProps,
ref
): ReactElement | null {
return (
<span
{...props}
ref={ref}
className={cn(
styles("icon", {
circle,
disabled,
overlay,
checked,
indeterminate,
}),
className
)}
>
{children}
</span>
);
});
36 changes: 13 additions & 23 deletions packages/form/src/toggle/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import React, {
} from "react";
import cn from "classnames";
import { TextIconSpacing } from "@react-md/icon";
import { bem } from "@react-md/utils";

import { Label } from "../label/Label";
import { ToggleContainer } from "./ToggleContainer";
import { SwitchTrack } from "./SwitchTrack";

export interface SwitchProps
extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "readOnly"> {
Expand Down Expand Up @@ -97,8 +97,6 @@ export interface SwitchProps
children?: ReactNode;
}

const block = bem("rmd-switch");

export const Switch = forwardRef<HTMLInputElement, SwitchProps>(function Switch(
{
style,
Expand Down Expand Up @@ -137,32 +135,24 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(function Switch(
className={labelClassName}
htmlFor={id}
error={error}
disabled={
typeof labelDisabled === "boolean" ? labelDisabled : disabled
}
disabled={labelDisabled ?? disabled}
>
{label}
</Label>
}
iconAfter={!iconAfter}
>
<span style={trackStyle} className={cn(block(), trackClassName)}>
<input
{...props}
ref={ref}
type="checkbox"
className={cn(block("input"))}
disabled={disabled}
/>
<label
htmlFor={id}
aria-hidden
style={ballStyle}
className={cn(block("ball"), ballClassName)}
>
{children}
</label>
</span>
<SwitchTrack
{...props}
ref={ref}
style={trackStyle}
className={trackClassName}
disabled={disabled}
ballStyle={ballStyle}
ballClassName={ballClassName}
>
{children}
</SwitchTrack>
</TextIconSpacing>
</ToggleContainer>
);
Expand Down
100 changes: 100 additions & 0 deletions packages/form/src/toggle/SwitchTrack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, {
CSSProperties,
forwardRef,
HTMLAttributes,
InputHTMLAttributes,
} from "react";
import cn from "classnames";
import { bem, PropsWithRef } from "@react-md/utils";

const styles = bem("rmd-switch");

/** @remarks \@since 2.8.0 */
export interface SwitchTrackProps
extends InputHTMLAttributes<HTMLInputElement> {
/**
* If an `id` is provided, the track will contain a checkbox input element and
* render the "ball" as a `<label>`. If the `id` is omitted, no input element
* will be rendered and the "ball" will be rendered as a `<span>`.
*
* Basically only omit the `id` if this is used in another accessible widget
* like `menuitemcheckbox`.
*/
id?: string;

/**
* An optional style object to provide to the ball.
*/
ballStyle?: CSSProperties;

/**
* An optional class name to provide to the ball.
*/
ballClassName?: string;

/**
* Any additional props and optional ref to provide to the track itself since
* all the props are normally provided to the `<input>` element instead.
*/
containerProps?: PropsWithRef<
HTMLAttributes<HTMLSpanElement>,
HTMLSpanElement
>;
}

/**
* This is most likely an internal only component that is used to render the
* switch element either as a checkbox or in the `MenuItemSwitch` component.
*
* @remarks \@since 2.8.0
*/
export const SwitchTrack = forwardRef<HTMLInputElement, SwitchTrackProps>(
function SwitchTrack(
{
id,
disabled = false,
checked = false,
className,
ballStyle,
ballClassName,
containerProps,
children,
...props
},
ref
) {
return (
<span
{...containerProps}
className={cn(styles(), containerProps?.className)}
>
{id && (
<>
<input
{...props}
id={id}
ref={ref}
type="checkbox"
className={cn(styles("input"), className)}
disabled={disabled}
/>
<label
htmlFor={id}
aria-hidden
style={ballStyle}
className={cn(styles("ball"), ballClassName)}
>
{children}
</label>
</>
)}
{!id && (
<span
style={ballStyle}
className={cn(styles("ball", { checked }), ballClassName)}
/>
)}
</span>
);
}
);
14 changes: 14 additions & 0 deletions packages/form/src/toggle/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@
@include rmd-form-theme(top, toggle-dense-inset);
@include rmd-form-theme(height, indeterminate-dense-height);
}

&--checked {
@if $rmd-toggle-active-color != $rmd-toggle-inactive-color {
@include rmd-icon-theme-update-var(color, $rmd-toggle-active-color);
}

&::before {
opacity: 0;
}
}
}

/// Updates the checkbox and radio components to have a dense theme by updating
Expand Down Expand Up @@ -325,6 +335,10 @@
width: $rmd-switch-ball-size;
z-index: 1;
}

&--checked {
@include rmd-switch-ball-checked;
}
}

/// @access private
Expand Down

0 comments on commit d9278b3

Please sign in to comment.