|
| 1 | +import {ActionButton, ActionButtonProps} from './ActionButton'; |
| 2 | +import {AriaLabelingProps, DOMProps, FocusableRef} from '@react-types/shared'; |
| 3 | +import {ContentContext, FooterContext, HeadingContext} from './Content'; |
| 4 | +import {ContextValue, Dialog as RACDialog, DEFAULT_SLOT, Provider, TextContext, useContextProps} from 'react-aria-components'; |
| 5 | +import {ReactNode, createContext, forwardRef} from 'react'; |
| 6 | +import {dialogInner} from './Dialog'; |
| 7 | +import {DialogTrigger, DialogTriggerProps} from './DialogTrigger'; |
| 8 | +import {filterDOMProps, mergeProps, useLabels} from '@react-aria/utils'; |
| 9 | +import HelpIcon from '../s2wf-icons/assets/svg/S2_Icon_HelpCircle_20_N.svg'; |
| 10 | +import InfoIcon from '../s2wf-icons/assets/svg/S2_Icon_InfoCircle_20_N.svg'; |
| 11 | +import {mergeStyles} from '../style/runtime'; |
| 12 | +import {Popover, PopoverProps} from './Popover'; |
| 13 | +import {size as styleSize, style} from '../style/spectrum-theme' with {type: 'macro'}; |
| 14 | +import {StyleProps} from './style-utils' with { type: 'macro' }; |
| 15 | +import {useFocusableRef} from '@react-spectrum/utils'; |
| 16 | + |
| 17 | +export interface ContextualHelpStyleProps { |
| 18 | + /** |
| 19 | + * Indicates whether contents are informative or provides helpful guidance. |
| 20 | + * |
| 21 | + * @default 'help' |
| 22 | + */ |
| 23 | + variant?: 'info' | 'help' |
| 24 | +} |
| 25 | +export interface ContextualHelpProps extends |
| 26 | + Pick<DialogTriggerProps, 'isOpen' | 'defaultOpen' | 'onOpenChange' | 'shouldFlip' | 'offset' | 'crossOffset' | 'placement'>, |
| 27 | + Pick<PopoverProps, 'containerPadding'>, |
| 28 | + Pick<ActionButtonProps, 'size'>, |
| 29 | + ContextualHelpStyleProps, StyleProps, DOMProps, AriaLabelingProps { |
| 30 | + /** Contents of the Contextual Help popover. */ |
| 31 | + children?: ReactNode |
| 32 | +} |
| 33 | + |
| 34 | +const popover = style({ |
| 35 | + fontFamily: 'sans', |
| 36 | + minWidth: '[218px]', |
| 37 | + width: '[218px]', |
| 38 | + padding: 24 |
| 39 | +}); |
| 40 | + |
| 41 | +export const ContextualHelpContext = createContext<ContextValue<ContextualHelpProps, HTMLButtonElement>>({}); |
| 42 | + |
| 43 | +function ContextualHelp(props: ContextualHelpProps, ref: FocusableRef<HTMLButtonElement>) { |
| 44 | + let domRef = useFocusableRef(ref); |
| 45 | + [props, domRef] = useContextProps(props, domRef, ContextualHelpContext); |
| 46 | + let { |
| 47 | + children, |
| 48 | + defaultOpen, |
| 49 | + // containerPadding = 24, // See popover() above. Issue noted in Popover.tsx. |
| 50 | + size = 'XS', |
| 51 | + crossOffset, |
| 52 | + isOpen, |
| 53 | + offset = 8, |
| 54 | + onOpenChange, |
| 55 | + placement = 'bottom start', |
| 56 | + shouldFlip, |
| 57 | + UNSAFE_className, |
| 58 | + UNSAFE_style, |
| 59 | + variant = 'help' |
| 60 | + } = props; |
| 61 | + |
| 62 | + // In a FieldLabel we're getting the context's aria-labeledby, so we need to |
| 63 | + // manually set the aria-label after useLabels() to keep the order of label |
| 64 | + // then ContextualHelp variant |
| 65 | + let labelProps = useLabels(props); |
| 66 | + // Translate variant |
| 67 | + labelProps['aria-label'] = labelProps['aria-label'] ? labelProps['aria-label'] + ' ' + variant : variant; |
| 68 | + |
| 69 | + let buttonProps = filterDOMProps(props, {labelable: true}); |
| 70 | + |
| 71 | + return ( |
| 72 | + <DialogTrigger |
| 73 | + isOpen={isOpen} |
| 74 | + defaultOpen={defaultOpen} |
| 75 | + onOpenChange={onOpenChange}> |
| 76 | + <ActionButton |
| 77 | + slot={null} |
| 78 | + ref={ref} |
| 79 | + size={size} |
| 80 | + {...mergeProps(buttonProps, labelProps)} |
| 81 | + UNSAFE_style={UNSAFE_style} |
| 82 | + UNSAFE_className={UNSAFE_className} |
| 83 | + isQuiet> |
| 84 | + {variant === 'info' ? <InfoIcon /> : <HelpIcon />} |
| 85 | + </ActionButton> |
| 86 | + <Popover |
| 87 | + placement={placement} |
| 88 | + shouldFlip={shouldFlip} |
| 89 | + // not working => containerPadding={containerPadding} |
| 90 | + offset={offset} |
| 91 | + crossOffset={crossOffset} |
| 92 | + hideArrow |
| 93 | + UNSAFE_className={popover}> |
| 94 | + <RACDialog className={mergeStyles(dialogInner, style({borderRadius: 'none'}))}> |
| 95 | + <Provider |
| 96 | + values={[ |
| 97 | + [TextContext, { |
| 98 | + slots: { |
| 99 | + [DEFAULT_SLOT]: {} |
| 100 | + } |
| 101 | + }], |
| 102 | + [HeadingContext, {className: style({ |
| 103 | + fontSize: 'heading-xs', |
| 104 | + fontWeight: 'heading', |
| 105 | + lineHeight: 'heading', |
| 106 | + color: 'heading', |
| 107 | + margin: 0, |
| 108 | + marginBottom: styleSize(8) // This only makes it 10px on mobile and should be 12px |
| 109 | + })}], |
| 110 | + [ContentContext, {className: style({ |
| 111 | + fontSize: 'ui', |
| 112 | + fontWeight: 'normal', |
| 113 | + lineHeight: 'body', |
| 114 | + color: 'body' |
| 115 | + })}], |
| 116 | + [FooterContext, {className: style({ |
| 117 | + fontSize: 'ui', |
| 118 | + lineHeight: 'body', |
| 119 | + color: 'body', |
| 120 | + marginTop: 16 |
| 121 | + })}] |
| 122 | + ]}> |
| 123 | + {children} |
| 124 | + </Provider> |
| 125 | + </RACDialog> |
| 126 | + </Popover> |
| 127 | + </DialogTrigger> |
| 128 | + ); |
| 129 | +} |
| 130 | + |
| 131 | +/** |
| 132 | + * Contextual help shows a user extra information about the state of an adjacent component, or a total view. |
| 133 | + */ |
| 134 | +let _ContextualHelp = forwardRef(ContextualHelp); |
| 135 | +export {_ContextualHelp as ContextualHelp}; |
0 commit comments