Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
28 changes: 28 additions & 0 deletions src/components/Checkbox/Checkbox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,31 @@ export const Disabled = () => <>
{renderDisabledCheckboxes('light', 1.6)}
{renderDisabledCheckboxes('light', 2)}
</>

const VerticalCheckboxGroup = styled.div`
text-transform: capitalize;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
& + & {
margin-top: 3.2rem;
}
`;

export const WithTooltip = () => <>
<VerticalCheckboxGroup>
<h2>Primary</h2>
<Checkbox variant="primary" tooltipText="Tooltip text for checkbox goes here">Checkbox label</Checkbox>
<Checkbox variant="primary" defaultChecked tooltipText="Tooltip text for checkbox goes here">Checked label with tooltip</Checkbox>
<Checkbox variant="primary" disabled tooltipText="This option is unavailable">Disabled label with tooltip</Checkbox>
<Checkbox variant="primary" disabled defaultChecked tooltipText="This option is unavailable">Disabled checked label with tooltip</Checkbox>
</VerticalCheckboxGroup>
<VerticalCheckboxGroup>
<h2>Light</h2>
<Checkbox variant="light" tooltipText="Tooltip text for checkbox goes here">Checkbox label</Checkbox>
<Checkbox variant="light" defaultChecked tooltipText="Tooltip text for checkbox goes here">Checked label with tooltip</Checkbox>
<Checkbox variant="light" disabled tooltipText="This option is unavailable">Disabled label with tooltip</Checkbox>
<Checkbox variant="light" disabled defaultChecked tooltipText="This option is unavailable">Disabled checked label with tooltip</Checkbox>
</VerticalCheckboxGroup>
</>;
29 changes: 22 additions & 7 deletions src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,44 @@ import { LabelHTMLAttributes, PropsWithChildren } from "react";
import { checkboxLabelStyles, checkboxInputStyles, CheckboxVariant, CheckboxSize } from "./sharedCheckboxStyles";
import styled from "styled-components";
import { InputHTMLAttributes } from "react";
import { useLabelTooltip } from '../Tooltip';

const StyledLabel = styled.label<{ bold: boolean; variant: CheckboxVariant; isDisabled?: boolean; }>`
${checkboxLabelStyles}
position: relative;
`;

// https://moderncss.dev/pure-css-custom-checkbox-style/
const StyledInput = styled.input<{ variant: CheckboxVariant; checkboxSize: CheckboxSize; isDisabled?: boolean; }>`
${checkboxInputStyles}
`;

const LabelWithTooltipWrapper = styled.div`
display: inline-block;
`;

type CheckboxProps = PropsWithChildren<
Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> & {
variant?: CheckboxVariant;
size?: CheckboxSize;
bold?: boolean;
labelProps?: LabelHTMLAttributes<HTMLLabelElement>;
tooltipText?: string;
}>;

export const Checkbox = ({ children, disabled, variant = 'primary', bold = false, size = 1.6, labelProps, ...props }: CheckboxProps) => {
return (
<StyledLabel bold={bold} variant={variant} isDisabled={disabled} {...labelProps}>
<StyledInput {...props} type="checkbox" variant={variant} checkboxSize={size} isDisabled={disabled} disabled={disabled} />
{children}
</StyledLabel>
);
export const Checkbox = ({ children, disabled, variant = 'primary', bold = false, size = 1.6, labelProps, tooltipText, ...props }: CheckboxProps) => {
const { triggerRef, triggerProps, openTooltip, tooltip } = useLabelTooltip(tooltipText);

return tooltipText
? <LabelWithTooltipWrapper>
<StyledLabel ref={triggerRef} bold={bold} variant={variant} isDisabled={disabled} {...triggerProps} {...labelProps}>
<StyledInput {...props} type="checkbox" onFocus={openTooltip} variant={variant} checkboxSize={size} isDisabled={disabled} disabled={disabled} />
{children}
{tooltip}
</StyledLabel>
</LabelWithTooltipWrapper>
Comment on lines +32 to +43

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

tooltip behavior has not changed. Visually inspected and used VoiceOver to check accessibility.

: <StyledLabel bold={bold} variant={variant} isDisabled={disabled} {...labelProps}>
<StyledInput {...props} type="checkbox" variant={variant} checkboxSize={size} isDisabled={disabled} disabled={disabled} />
{children}
</StyledLabel>;
};
16 changes: 8 additions & 8 deletions src/components/Checkbox/__snapshots__/Checkbox.spec.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
exports[`Checkbox allows setting props on label 1`] = `
<label
aria-label="custom label"
className="sc-bczRLJ XfrOB"
className="sc-hKMtZM bMKdZ"
>
<input
className="sc-gsnTZi dskNep"
className="sc-eCYdqJ gpoUA"
type="checkbox"
/>
Click Me
Expand All @@ -15,10 +15,10 @@ exports[`Checkbox allows setting props on label 1`] = `

exports[`Checkbox handles disabled state 1`] = `
<label
className="sc-bczRLJ jDFcYw"
className="sc-hKMtZM lmlvsI"
>
<input
className="sc-gsnTZi dJvfbo"
className="sc-eCYdqJ fBIvaF"
disabled={true}
type="checkbox"
/>
Expand All @@ -28,10 +28,10 @@ exports[`Checkbox handles disabled state 1`] = `

exports[`Checkbox handles options 1`] = `
<label
className="sc-bczRLJ XfrOB"
className="sc-hKMtZM bMKdZ"
>
<input
className="sc-gsnTZi dskNep"
className="sc-eCYdqJ gpoUA"
type="checkbox"
/>
Click Me
Expand All @@ -40,10 +40,10 @@ exports[`Checkbox handles options 1`] = `

exports[`Checkbox matches snapshot 1`] = `
<label
className="sc-bczRLJ fIuhsq"
className="sc-hKMtZM jKnytq"
>
<input
className="sc-gsnTZi ehAXiz"
className="sc-eCYdqJ glqLW"
type="checkbox"
/>
Click Me
Expand Down
23 changes: 7 additions & 16 deletions src/components/Radio.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import React from 'react';
import { PropsWithChildren } from "react";
import { colors } from "../theme";
import styled from "styled-components";
import { InputHTMLAttributes } from "react";
import {useTooltipTriggerState} from 'react-stately';
import {useTooltipTrigger} from 'react-aria';
import { CustomTooltip } from './Tooltip';
import { useLabelTooltip } from './Tooltip';

export const StyledLabel = styled.label<{isDisabled?: boolean}>`
font-size: 1.6rem;
Expand Down Expand Up @@ -54,25 +51,19 @@ const LabelWithTooltipWrapper = styled.div`
type RadioProps = PropsWithChildren<
Omit<InputHTMLAttributes<HTMLInputElement>, 'type'>>;

export const Radio = ({ children, disabled, labelAs, ...props }: RadioProps & {
export const Radio = ({ children, disabled, labelAs, tooltipText, ...props }: RadioProps & {
tooltipText?: string;
labelAs?: string;
}) => {
const { triggerRef, triggerProps, openTooltip, tooltip } = useLabelTooltip(tooltipText);

const state = useTooltipTriggerState({delay: 0});
const ref = React.useRef(null);

const { triggerProps, tooltipProps } = useTooltipTrigger({delay: 0}, state, ref);

return props.tooltipText
return tooltipText
? <div>
<LabelWithTooltipWrapper>
<StyledLabel ref={ref} as={labelAs as any} isDisabled={disabled} {...triggerProps}>
<StyledInput type="radio" onFocus={() => state.open()} isDisabled={disabled} disabled={disabled} {...props} />
<StyledLabel ref={triggerRef} as={labelAs as any} isDisabled={disabled} {...triggerProps}>
<StyledInput type="radio" onFocus={openTooltip} isDisabled={disabled} disabled={disabled} {...props} />
{children}
{state.isOpen && (
<CustomTooltip state={state} {...tooltipProps} placement='right'>{props.tooltipText}</CustomTooltip>
)}
{tooltip}
</StyledLabel>
</LabelWithTooltipWrapper>
</div>
Expand Down
22 changes: 20 additions & 2 deletions src/components/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useRef } from 'react';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

npx tsc --noEmit passes clean. False positive.

import styled from 'styled-components';
import { colors } from '../theme';
import { Button, OverlayArrow, Tooltip as AriaTooltip, TooltipTrigger } from 'react-aria-components';
import { Info } from './svgs/Info';
import {mergeProps, Placement, useTooltip} from 'react-aria';
import { useTooltipTriggerState } from 'react-stately';
import {mergeProps, Placement, useTooltip, useTooltipTrigger} from 'react-aria';

const tooltipStyles = `
box-shadow: 0 0.8rem 2rem rgba(0 0 0 / 0.1);
Expand Down Expand Up @@ -113,4 +115,20 @@ export const CustomTooltip = ({ state, ...props }: any) => {
</OverlayArrow>
</StyledCustomTooltip>
);
}
}

// Shared logic for showing a CustomTooltip on a label-wrapped input (Checkbox, Radio).
export const useLabelTooltip = (tooltipText?: string, placement: Placement = 'right') => {
const state = useTooltipTriggerState({ delay: 0 });
const triggerRef = useRef(null);
const { triggerProps, tooltipProps } = useTooltipTrigger({ delay: 0 }, state, triggerRef);

return {
triggerRef,
triggerProps,
openTooltip: () => state.open(),
tooltip: tooltipText && state.isOpen
? <CustomTooltip state={state} {...tooltipProps} placement={placement}>{tooltipText}</CustomTooltip>
: null,
};
Comment on lines +124 to +135

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

stripping aria-describedby does not prevent assistive tech from announcing the tooltip. Also a disabled input isn't focusable, and describedby only speaks on focus so disabled checkboxes and radio inputs tooltips aren't announced.

};
4 changes: 2 additions & 2 deletions src/components/Tree/__snapshots__/Tree.spec.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ exports[`Tree matches snapshot 1`] = `
style="display: contents;"
>
<label
class="sc-jSMfEi fnAjTL"
class="sc-ftvSup dQOzVx"
>
<input
class="sc-gKXOVf bROlip"
class="sc-papXJ eMMRLt"
slot="check"
type="checkbox"
value="one"
Expand Down
Loading