|
- import * as React from 'react';
- import { Button, tailwind } from '@tesseract-design/web-base';
- import { useFallbackId } from '@modal-sh/react-utils';
-
- const { tw } = tailwind;
-
- const ToggleButtonDerivedElementComponent = 'input' as const;
-
- /**
- * Derived HTML element of the {@link ToggleButton} component.
- */
- export type ToggleButtonDerivedElement = HTMLElementTagNameMap[
- typeof ToggleButtonDerivedElementComponent
- ];
-
- /**
- * Props of the {@link ToggleButton} component.
- */
- export interface ToggleButtonProps extends Omit<React.InputHTMLAttributes<ToggleButtonDerivedElement>, 'type' | 'size'> {
- /**
- * Should the component occupy the whole width of its parent?
- */
- block?: boolean;
- /**
- * Should the component's content use minimal space?
- */
- compact?: boolean;
- /**
- * Size of the component.
- */
- size?: Button.Size;
- /**
- * Complementary content of the component.
- */
- subtext?: React.ReactNode;
- /**
- * Short complementary content displayed at the edge of the component.
- */
- badge?: React.ReactNode;
- /**
- * Variant of the component.
- */
- variant?: Button.Variant;
- /**
- * Is the component in an indeterminate state?
- */
- indeterminate?: boolean;
- }
-
- export const toggleButtonPlugin: tailwind.PluginCreator = ({ addComponents, }) => {
- addComponents({
- '.toggle-button': {
- '& + label > :first-child > :first-child': {
- 'display': 'none',
- },
- '&:checked + label > :first-child > :first-child': {
- 'display': 'block',
- },
- '& + label > :first-child > :first-child + *': {
- 'display': 'none',
- },
- '&:indeterminate + label > :first-child > :first-child + *': {
- 'display': 'block',
- },
- },
- });
- };
-
- /**
- * Component for toggling a Boolean value.
- */
- export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleButtonProps>((
- {
- children,
- block = false,
- compact = false,
- size = 'medium' as const,
- id: idProp,
- className,
- subtext,
- badge,
- variant = 'bare' as const,
- indeterminate = false,
- style,
- ...etcProps
- },
- forwardedRef,
- ) => {
- const defaultRef = React.useRef<ToggleButtonDerivedElement>(null);
- const ref = forwardedRef ?? defaultRef;
- const id = useFallbackId(idProp);
-
- React.useEffect(() => {
- if (typeof ref === 'function') {
- const defaultElement = defaultRef.current as ToggleButtonDerivedElement;
- defaultElement.indeterminate = indeterminate;
- ref(defaultElement);
- return;
- }
- const element = ref.current as ToggleButtonDerivedElement;
- element.indeterminate = indeterminate;
- }, [indeterminate, defaultRef, ref]);
-
- return (
- <span
- className="contents"
- >
- <ToggleButtonDerivedElementComponent
- {...etcProps}
- ref={typeof ref === 'function' ? defaultRef : ref}
- type="checkbox"
- id={id}
- className="sr-only peer toggle-button"
- />
- <label
- data-testid="button"
- htmlFor={id}
- className={tw(
- 'items-center justify-start rounded overflow-hidden ring-secondary/50 leading-none select-none cursor-pointer',
- 'peer-focus:outline-0 peer-focus:ring-4 peer-focus:ring-secondary/50',
- 'active:ring-tertiary/50 active:ring-4',
- 'peer-disabled:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:ring-0',
- 'text-primary peer-disabled:text-primary peer-focus:text-secondary peer-checked:text-tertiary active:text-tertiary',
- {
- 'flex w-full': block,
- 'inline-flex max-w-full align-middle': !block,
- },
- {
- 'pl-2 gap-2 pr-2': compact,
- 'pl-4 gap-4 pr-4': !compact,
- },
- {
- 'border-2 border-primary peer-disabled:border-primary peer-focus:border-secondary peer-checked:border-tertiary active:border-tertiary': variant !== 'bare',
- 'bg-negative text-primary peer-disabled:text-primary peer-focus:text-secondary peer-checked:text-tertiary active:text-tertiary': variant !== 'filled',
- 'bg-primary text-negative peer-disabled:bg-primary peer-focus:bg-secondary peer-checked:bg-tertiary active:bg-tertiary': variant === 'filled',
- },
- {
- 'h-10': size === 'small',
- 'h-12': size === 'medium',
- 'h-16': size === 'large',
- },
- className,
- )}
- style={style}
- >
- <span
- className={tw(
- 'w-6 h-6 block rounded border-2 p-0.5 box-border',
- {
- 'border-current': variant !== 'filled',
- 'border-negative': variant === 'filled',
- },
- )}
- >
- <svg
- className={tw(
- 'w-full h-full fill-none stroke-3 linejoin-round linecap-round',
- {
- 'stroke-negative': variant === 'filled',
- 'stroke-current': variant !== 'filled',
- },
- )}
- viewBox="0 0 24 24"
- role="presentation"
- >
- <polyline
- points="20 6 9 17 4 12"
- />
- </svg>
- <svg
- className={tw(
- 'w-full h-full fill-none stroke-3 linejoin-round linecap-round',
- {
- 'stroke-negative': variant === 'filled',
- 'stroke-current': variant !== 'filled',
- },
- )}
- viewBox="0 0 24 24"
- role="presentation"
- >
- <polyline
- points="20 12 4 12"
- />
- </svg>
- </span>
- <span
- className={tw(
- 'contents',
- {
- 'text-current': variant !== 'filled',
- 'text-negative': variant === 'filled',
- },
- )}
- >
- <span
- className={tw(
- 'flex-auto min-w-0',
- )}
- >
- <span
- className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
- data-testid="children"
- >
- {children}
- </span>
- {subtext && (
- <>
- <span className="sr-only">
- {' - '}
- </span>
- <span
- className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs"
- data-testid="subtext"
- >
- {subtext}
- </span>
- </>
- )}
- </span>
- {badge && (
- <>
- <span className="sr-only">
- {' - '}
- </span>
- <span
- data-testid="badge"
- >
- {badge}
- </span>
- </>
- )}
- </span>
- </label>
- </span>
- );
- });
-
- ToggleButton.displayName = 'ToggleButton';
-
- ToggleButton.defaultProps = {
- block: false,
- compact: false,
- size: 'medium',
- subtext: undefined,
- badge: undefined,
- indeterminate: false,
- variant: 'bare',
- };
|