Skip to content

Commit

Permalink
ContextualHelp (#162)
Browse files Browse the repository at this point in the history
* initial ContextualHelp implementation

* Adding ContextualHelp to FieldLabel

* fix crash in textfield contextual help story

* Updating ContextualHelp positioning in FieldLabel

* field label and contextual help button sizes match

* adding contextualhelp to monopackage for release

* storybook autodocs working

* Improving ContextualHelp positioning

* adding combobox contextualhelp story

* Improving popover focus

* Improving popover focus

* Improving contextual help placement in field and aria labeling

* updating links to have hrefs

* Fixing button context and popover style

* updated useId and aria-label in field label

---------

Co-authored-by: Kyle Taborski <[email protected]>
Co-authored-by: danilu <[email protected]>
  • Loading branch information
3 people authored and GitHub Enterprise committed Jun 12, 2024
1 parent 386dcc4 commit eab002f
Show file tree
Hide file tree
Showing 15 changed files with 383 additions and 79 deletions.
5 changes: 3 additions & 2 deletions packages/@react-spectrum/s2/src/CheckboxGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {StyleProps, field, getAllowedOverrides} from './style-utils' with {type:
import {useDOMRef} from '@react-spectrum/utils';
import {CheckboxContext} from './Checkbox';

export interface CheckboxGroupProps extends Omit<AriaCheckboxGroupProps, 'className' | 'style' | 'children'>, StyleProps, Omit<SpectrumLabelableProps, 'contextualHelp'>, HelpTextProps {
export interface CheckboxGroupProps extends Omit<AriaCheckboxGroupProps, 'className' | 'style' | 'children'>, StyleProps, SpectrumLabelableProps, HelpTextProps {
/**
* The size of the Checkboxes in the CheckboxGroup.
*
Expand Down Expand Up @@ -91,7 +91,8 @@ function CheckboxGroup(props: CheckboxGroupProps, ref: DOMRef<HTMLDivElement>) {
size={size}
labelPosition={labelPosition}
labelAlign={labelAlign}
necessityIndicator={necessityIndicator}>
necessityIndicator={necessityIndicator}
contextualHelp={props.contextualHelp}>
{label}
</FieldLabel>
<div
Expand Down
5 changes: 3 additions & 2 deletions packages/@react-spectrum/s2/src/ColorField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {Ref, forwardRef, useContext, useImperativeHandle, useRef} from 'react';
import {TextFieldRef} from '@react-types/textfield';
import {createFocusableRef} from '@react-spectrum/utils';

export interface ColorFieldProps extends Omit<AriaColorFieldProps, 'children' | 'className' | 'style'>, StyleProps, Omit<SpectrumLabelableProps, 'contextualHelp'>, HelpTextProps {
export interface ColorFieldProps extends Omit<AriaColorFieldProps, 'children' | 'className' | 'style'>, StyleProps, SpectrumLabelableProps, HelpTextProps {
/**
* The size of the color field.
*
Expand Down Expand Up @@ -81,7 +81,8 @@ function ColorField(props: ColorFieldProps, ref: Ref<TextFieldRef>) {
size={props.size}
labelPosition={labelPosition}
labelAlign={labelAlign}
necessityIndicator={necessityIndicator}>
necessityIndicator={necessityIndicator}
contextualHelp={props.contextualHelp}>
{label}
</FieldLabel>
<FieldGroup role="presentation" isDisabled={isDisabled} isInvalid={isInvalid} size={props.size}>
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-spectrum/s2/src/ColorHandle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

import {ColorThumb} from 'react-aria-components';
import {style} from '../style/spectrum-theme' with {type: 'macro'};
import {RefObject, cloneElement, useId, useState} from 'react';
import {useLayoutEffect} from '@react-aria/utils';
import {RefObject, cloneElement, useState} from 'react';
import {useId, useLayoutEffect} from '@react-aria/utils';
import {createPortal} from 'react-dom';
import {keyframes} from '../style/style-macro' with {type: 'macro'};

Expand Down
5 changes: 3 additions & 2 deletions packages/@react-spectrum/s2/src/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export interface ComboBoxProps<T extends object> extends
Omit<AriaComboBoxProps<T>, 'children' | 'style' | 'className' | 'defaultFilter' | 'allowsEmptyCollection'>,
ComboboxStyleProps,
StyleProps,
Omit<SpectrumLabelableProps, 'contextualHelp'>,
SpectrumLabelableProps,
HelpTextProps,
Pick<ListBoxProps<T>, 'items'>,
Pick<AriaPopoverProps, 'shouldFlip'> {
Expand Down Expand Up @@ -217,7 +217,8 @@ function ComboBox<T extends object>(props: ComboBoxProps<T>, ref: FocusableRef<H
size={size}
labelPosition={labelPosition}
labelAlign={labelAlign}
necessityIndicator={necessityIndicator}>
necessityIndicator={necessityIndicator}
contextualHelp={props.contextualHelp}>
{label}
</FieldLabel>
<FieldGroup
Expand Down
135 changes: 135 additions & 0 deletions packages/@react-spectrum/s2/src/ContextualHelp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {ActionButton, ActionButtonProps} from './ActionButton';
import {AriaLabelingProps, DOMProps, FocusableRef} from '@react-types/shared';
import {ContentContext, FooterContext, HeadingContext} from './Content';
import {ContextValue, Dialog as RACDialog, DEFAULT_SLOT, Provider, TextContext, useContextProps} from 'react-aria-components';
import {ReactNode, createContext, forwardRef} from 'react';
import {dialogInner} from './Dialog';
import {DialogTrigger, DialogTriggerProps} from './DialogTrigger';
import {filterDOMProps, mergeProps, useLabels} from '@react-aria/utils';
import HelpIcon from '../s2wf-icons/assets/svg/S2_Icon_HelpCircle_20_N.svg';
import InfoIcon from '../s2wf-icons/assets/svg/S2_Icon_InfoCircle_20_N.svg';
import {mergeStyles} from '../style/runtime';
import {Popover, PopoverProps} from './Popover';
import {size as styleSize, style} from '../style/spectrum-theme' with {type: 'macro'};
import {StyleProps} from './style-utils' with { type: 'macro' };
import {useFocusableRef} from '@react-spectrum/utils';

export interface ContextualHelpStyleProps {
/**
* Indicates whether contents are informative or provides helpful guidance.
*
* @default 'help'
*/
variant?: 'info' | 'help'
}
export interface ContextualHelpProps extends
Pick<DialogTriggerProps, 'isOpen' | 'defaultOpen' | 'onOpenChange' | 'shouldFlip' | 'offset' | 'crossOffset' | 'placement'>,
Pick<PopoverProps, 'containerPadding'>,
Pick<ActionButtonProps, 'size'>,
ContextualHelpStyleProps, StyleProps, DOMProps, AriaLabelingProps {
/** Contents of the Contextual Help popover. */
children?: ReactNode
}

const popover = style({
fontFamily: 'sans',
minWidth: '[218px]',
width: '[218px]',
padding: 24
});

export const ContextualHelpContext = createContext<ContextValue<ContextualHelpProps, HTMLButtonElement>>({});

function ContextualHelp(props: ContextualHelpProps, ref: FocusableRef<HTMLButtonElement>) {
let domRef = useFocusableRef(ref);
[props, domRef] = useContextProps(props, domRef, ContextualHelpContext);
let {
children,
defaultOpen,
// containerPadding = 24, // See popover() above. Issue noted in Popover.tsx.
size = 'XS',
crossOffset,
isOpen,
offset = 8,
onOpenChange,
placement = 'bottom start',
shouldFlip,
UNSAFE_className,
UNSAFE_style,
variant = 'help'
} = props;

// In a FieldLabel we're getting the context's aria-labeledby, so we need to
// manually set the aria-label after useLabels() to keep the order of label
// then ContextualHelp variant
let labelProps = useLabels(props);
// Translate variant
labelProps['aria-label'] = labelProps['aria-label'] ? labelProps['aria-label'] + ' ' + variant : variant;

let buttonProps = filterDOMProps(props, {labelable: true});

return (
<DialogTrigger
isOpen={isOpen}
defaultOpen={defaultOpen}
onOpenChange={onOpenChange}>
<ActionButton
slot={null}
ref={ref}
size={size}
{...mergeProps(buttonProps, labelProps)}
UNSAFE_style={UNSAFE_style}
UNSAFE_className={UNSAFE_className}
isQuiet>
{variant === 'info' ? <InfoIcon /> : <HelpIcon />}
</ActionButton>
<Popover
placement={placement}
shouldFlip={shouldFlip}
// not working => containerPadding={containerPadding}
offset={offset}
crossOffset={crossOffset}
hideArrow
UNSAFE_className={popover}>
<RACDialog className={mergeStyles(dialogInner, style({borderRadius: 'none'}))}>
<Provider
values={[
[TextContext, {
slots: {
[DEFAULT_SLOT]: {}
}
}],
[HeadingContext, {className: style({
fontSize: 'heading-xs',
fontWeight: 'heading',
lineHeight: 'heading',
color: 'heading',
margin: 0,
marginBottom: styleSize(8) // This only makes it 10px on mobile and should be 12px
})}],
[ContentContext, {className: style({
fontSize: 'ui',
fontWeight: 'normal',
lineHeight: 'body',
color: 'body'
})}],
[FooterContext, {className: style({
fontSize: 'ui',
lineHeight: 'body',
color: 'body',
marginTop: 16
})}]
]}>
{children}
</Provider>
</RACDialog>
</Popover>
</DialogTrigger>
);
}

/**
* Contextual help shows a user extra information about the state of an adjacent component, or a total view.
*/
let _ContextualHelp = forwardRef(ContextualHelp);
export {_ContextualHelp as ContextualHelp};
2 changes: 1 addition & 1 deletion packages/@react-spectrum/s2/src/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ function Dialog(props: DialogProps, ref: DOMRef) {
let _Dialog = forwardRef(Dialog);
export {_Dialog as Dialog};

const dialogInner = style({
export const dialogInner = style({
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
Expand Down
Loading

0 comments on commit eab002f

Please sign in to comment.