diff --git a/UpgradeGuide.md b/UpgradeGuide.md index bb185205c..16c3e25b5 100644 --- a/UpgradeGuide.md +++ b/UpgradeGuide.md @@ -9,6 +9,8 @@ - Check your sizes, standalone props have been replaced by the `size` prop, such as `size=medium` instead of `medium`. - `Checkbox` - The old `theme` prop functionality has been replaced by the [global theme object](https://faithlife.github.io/styled-ui/#/theme) and Styled System props. +- `HelpBox` + - The old `theme` prop functionality has been replaced by the [global theme object](https://faithlife.github.io/styled-ui/#/theme) and Styled System props. - `Modal` - Subcomponents have been renamed, `ModalContent` has been replaced by `Modal.Content`, etc. - `Radio` diff --git a/catalog/help-box/documentation-v6.md b/catalog/help-box/documentation-v6.md new file mode 100644 index 000000000..27f665319 --- /dev/null +++ b/catalog/help-box/documentation-v6.md @@ -0,0 +1,11 @@ +For the next major version of Styled UI, the HelpBox component has been rebuilt to use Styled System's theme and variant APIs. + +You can opt in to the new API now by importing `{ HelpBox } from '@faithlife/styled-ui/v6'`. When v6 is released, the `/v6` entrypoint will continue to be supported with a deprecation warning until v7 is released. + +This documentation is automatically generated from jsdoc comments. + +```react +noSource: true +--- + +``` diff --git a/catalog/help-box/variations-v6.md b/catalog/help-box/variations-v6.md new file mode 100644 index 000000000..24357bc2e --- /dev/null +++ b/catalog/help-box/variations-v6.md @@ -0,0 +1,63 @@ +For the next major version of Styled UI, the HelpBox component has been rebuilt to use Styled System's theme and variant APIs. + +You can opt in to the new API now by importing `{ HelpBox } from '@faithlife/styled-ui/v6'`. When v6 is released, the `/v6` entrypoint will continue to be supported with a deprecation warning until v7 is released. + +## Colors and Their Meaning + +```react +showSource: true +--- + + true}> + This is a helpful alert. + + + true}> + This is an error alert. + + + true}> + This is a successful alert. + + + true}> + This is a cautious alert. + + + true}> + This is a minor alert. + + + +``` + +## Variations + +```react +showSource: true +--- + + true}>This is an alert with a light bulb. + true}>This alert has its icon hidden. + This alert is showing its icon on both sides. + true}> + This alert's contents are stacked. + + + + This alert doesn't handle closing. + + + +``` + +## Large Alerts + +```react +showSource: true +--- + + true}>This is a large alert. + true}>This is a large alert with a light bulb. + +``` diff --git a/catalog/index.js b/catalog/index.js index 7dfa3ed9c..5f9f22170 100644 --- a/catalog/index.js +++ b/catalog/index.js @@ -72,6 +72,7 @@ import { SegmentedButtonGroup, Checkbox as V6Checkbox, Dropdown as V6Dropdown, + HelpBox as V6HelpBox, Modal as V6Modal, Radio as V6Radio, SimpleToast as V6SimpleToast, @@ -586,6 +587,22 @@ const pages = [ content: pageLoader(() => import('./help-box/documentation.md')), imports: { HelpBox, DocgenTable }, }, + { + path: '/help-box/variations-v6', + title: 'v6 Help Box Variations', + content: pageLoader(() => import('./help-box/variations-v6.md')), + imports: { + HelpBox: V6HelpBox, + Button: V6Button, + Stack, + }, + }, + { + path: '/help-box/documentation-v6', + title: 'v6 Help Box Documentation', + content: pageLoader(() => import('./help-box/documentation-v6.md')), + imports: { HelpBox: V6HelpBox, DocgenTable }, + }, ], }, textInputPages, diff --git a/components/help-box/component.jsx b/components/help-box/component.jsx index eda47e49d..f9cdcf94e 100644 --- a/components/help-box/component.jsx +++ b/components/help-box/component.jsx @@ -6,8 +6,9 @@ import { CorrectCircle as CircleCheck, WarningCircle as Info, } from '../icons/12px'; -import { applyVariations } from '../utils'; import * as Styled from './styled'; +import { getVariation } from '../utils'; +import { DefaultThemeProvider } from '../DefaultThemeProvider'; /** Rectangular box containing tips on how to use our products */ export function HelpBox({ @@ -16,65 +17,61 @@ export function HelpBox({ hideIcon, showRightIcon, stacked, - className, - theme, handleClose, + variant, + primary, + success, + danger, + warning, + minor, ...helpBoxProps }) { - const { component: HelpBoxVariation, filteredProps } = applyVariations( - Styled.HelpBox, - Styled.variationMap, - helpBoxProps, - ); + const selectedVariant = + getVariation(variant, { primary, success, danger, warning, minor }) ?? undefined; return ( - - {(showLightBulb && ) || - (!hideIcon && ( - - {helpBoxProps.danger ? ( - - ) : helpBoxProps.success ? ( - - ) : helpBoxProps.minor ? null : ( - - )} - - ))} - {children} - {(handleClose && ( - - - - )) || - (showRightIcon && ( - - {helpBoxProps.danger ? ( - - ) : helpBoxProps.success ? ( - - ) : helpBoxProps.minor ? null : ( - - )} - - ))} - + + + {(showLightBulb && ) || + (!hideIcon && ( + + {selectedVariant === 'danger' ? ( + + ) : selectedVariant === 'success' ? ( + + ) : selectedVariant === 'minor' ? null : ( + + )} + + ))} + {children} + {(handleClose && ( + + + + )) || + (showRightIcon && ( + + {selectedVariant === 'danger' ? ( + + ) : selectedVariant === 'success' ? ( + + ) : selectedVariant === 'minor' ? null : ( + + )} + + ))} + + ); } HelpBox.propTypes = { - /** See the docs for how to override styles properly. */ - className: PropTypes.string, children: PropTypes.node.isRequired, /** The light bulb will override the other icon. */ showLightBulb: PropTypes.bool, @@ -84,22 +81,19 @@ HelpBox.propTypes = { showRightIcon: PropTypes.bool, /** Stacking will happen automatically on small viewports. */ stacked: PropTypes.bool, - /** Blue theme is the default. - * The icons are colored by foregroundColor. */ - theme: PropTypes.shape({ - foregroundColor: PropTypes.string, - backgroundColor: PropTypes.string, - closeIconColor: PropTypes.string, - }), - /** Green theme */ + /** Specifies the color variant (defaults to `primary`). */ + variant: PropTypes.oneOf(['primary', 'success', 'danger', 'warning', 'minor']), + /** Shortcut for setting `variant` to `primary` (the blue theme). */ + primary: PropTypes.bool, + /** Shortcut for setting `variant` to `success` (the green theme). */ success: PropTypes.bool, - /** Red theme */ + /** Shortcut for setting `variant` to `danger` (the red theme). */ danger: PropTypes.bool, - /** Yellow theme */ + /** Shortcut for setting `variant` to `warning` (the yellow theme). */ warning: PropTypes.bool, - /** Gray theme */ + /** Shortcut for setting `variant` to `minor` (the gray theme). */ minor: PropTypes.bool, - /** Height will be 230px */ + /** Sets height to `230px`. */ large: PropTypes.bool, /** If not handled, there will be no close icon. */ handleClose: PropTypes.func, diff --git a/components/help-box/index.js b/components/help-box/index.js index b208c374b..6928009ca 100644 --- a/components/help-box/index.js +++ b/components/help-box/index.js @@ -1 +1,2 @@ +export { HelpBox as LegacyHelpBox } from './legacy-component'; export { HelpBox } from './component'; diff --git a/components/help-box/legacy-component.jsx b/components/help-box/legacy-component.jsx new file mode 100644 index 000000000..cacc7d5f0 --- /dev/null +++ b/components/help-box/legacy-component.jsx @@ -0,0 +1,110 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + X as Close, + ErrorCircle as Exclamation, + CorrectCircle as CircleCheck, + WarningCircle as Info, +} from '../icons/12px'; +import { applyVariations } from '../utils'; +import * as Styled from './legacy-styled'; + +/** Rectangular box containing tips on how to use our products */ +export function HelpBox({ + children, + showLightBulb, + hideIcon, + showRightIcon, + stacked, + className, + theme, + handleClose, + ...helpBoxProps +}) { + const { component: HelpBoxVariation, filteredProps } = applyVariations( + Styled.HelpBox, + Styled.variationMap, + helpBoxProps, + ); + + return ( + + {(showLightBulb && ) || + (!hideIcon && ( + + {helpBoxProps.danger ? ( + + ) : helpBoxProps.success ? ( + + ) : helpBoxProps.minor ? null : ( + + )} + + ))} + {children} + {(handleClose && ( + + + + )) || + (showRightIcon && ( + + {helpBoxProps.danger ? ( + + ) : helpBoxProps.success ? ( + + ) : helpBoxProps.minor ? null : ( + + )} + + ))} + + ); +} + +HelpBox.propTypes = { + /** See the docs for how to override styles properly. */ + className: PropTypes.string, + children: PropTypes.node.isRequired, + /** The light bulb will override the other icon. */ + showLightBulb: PropTypes.bool, + /** Hides the left icon. */ + hideIcon: PropTypes.bool, + /** This icon will not show if closing is handled. */ + showRightIcon: PropTypes.bool, + /** Stacking will happen automatically on small viewports. */ + stacked: PropTypes.bool, + /** Blue theme is the default. + * The icons are colored by foregroundColor. */ + theme: PropTypes.shape({ + foregroundColor: PropTypes.string, + backgroundColor: PropTypes.string, + closeIconColor: PropTypes.string, + }), + /** Green theme */ + success: PropTypes.bool, + /** Red theme */ + danger: PropTypes.bool, + /** Yellow theme */ + warning: PropTypes.bool, + /** Gray theme */ + minor: PropTypes.bool, + /** Height will be 230px */ + large: PropTypes.bool, + /** If not handled, there will be no close icon. */ + handleClose: PropTypes.func, +}; + +HelpBox.Body = Styled.HelpBoxBody; + +HelpBox.Footer = Styled.HelpBoxFooter; diff --git a/components/help-box/legacy-styled.jsx b/components/help-box/legacy-styled.jsx new file mode 100644 index 000000000..05954a98c --- /dev/null +++ b/components/help-box/legacy-styled.jsx @@ -0,0 +1,141 @@ +import styled from 'styled-components'; +import { fonts, colors, thickness } from '../../components/shared-styles'; +import { LightBulbH } from '../icons'; +import { resetStyles } from '../utils'; +import { mediaSizes } from '../shared-styles'; + +export const HelpBoxContent = styled.div``; + +export const HelpBoxBody = styled.div``; + +export const HelpBoxFooter = styled.div``; + +export const BulbIcon = styled(LightBulbH)``; + +export const CloseButton = styled.button``; + +export const IconDiv = styled.div``; + +export const RightIconDiv = styled.div``; + +export const HelpBox = variantCreator( + colors.blueTint, + colors.blueLight, + colors.blueDark, +)(styled.div` + position: relative; + display: flex; + border-radius: 3px; + word-break: break-word; + + ${IconDiv} { + margin: ${({ hasIcon }) => (hasIcon ? '15px -4px 0px 16px' : '15px 4px 0px 0px')}; + + svg { + height: 18px; + width: 18px; + } + } + + ${BulbIcon} { + flex: none; + + width: ${props => (props.large ? '42px' : '24px')}; + height: ${props => (props.large ? '42px' : '24px')}; + margin: 12px 0px 0px 16px; + } + + ${HelpBoxContent} { + ${fonts.c16}; + display: flex; + flex: 1; + text-align: left; + line-height: 1.25; + font-size: 16px; + color: ${colors.flGray}; + + height: ${props => (props.large ? '230px' : '')}; + padding: 14px 16px 14px 12px; + + flex-direction: ${props => (props.stacked ? 'column' : 'row')}; + @media (max-width: ${mediaSizes.phone}) { + flex-direction: column; + } + + ${HelpBoxBody} { + ${fonts.c16}; + display: flex; + flex: 1; + order: 2; + } + + ${HelpBoxFooter} { + ${fonts.c16}; + display: flex; + order: 2; + align-items: center; + + margin: ${props => (props.stacked ? '12px 16px 0px 0px' : '-7px 0px -7px 16px')}; + @media (max-width: ${mediaSizes.phone}) { + margin: 12px 0px 0px 0px; + } + } + } + + ${CloseButton} { + cursor: pointer; + margin: ${props => (props.large ? '15px 16px 0px 16px' : '15px 16px 0px 12px')}; + margin-left: ${props => (!props.stacked ? '-4px' : '')}; + height: 18px; + background: transparent; + padding: 0; + border: none; + + &::-moz-focus-inner { + border: 0; + padding: 0; + } + } + + ${RightIconDiv} { + margin: ${props => (props.large ? '15px 16px 0px 16px' : '15px 16px 0px 12px')}; + margin-left: ${props => (!props.stacked ? '-4px' : '')}; + height: 18px; + background: transparent; + padding: 0; + border: none; + + &::-moz-focus-inner { + border: 0; + padding: 0; + } + } +`); + +export const variationMap = { + success: variantCreator(colors.greenTint, colors.greenLight, colors.greenDark), + danger: variantCreator(colors.redTint, colors.redLight, colors.redDark), + warning: variantCreator(colors.yellowTint, colors.yellowLight, colors.yellowDark), + minor: variantCreator(colors.gray4, colors.gray14, colors.gray34), +}; + +function variantCreator(backgroundColor, foregroundColor, closeIconColor) { + return component => styled(component)` + ${resetStyles}; + + background-color: ${props => props.theme.backgroundColor || backgroundColor}; + border-left: solid ${thickness.four} ${props => props.theme.foregroundColor || foregroundColor}; + + ${IconDiv} { + path { + fill: ${props => props.theme.foregroundColor || foregroundColor}; + } + } + + ${CloseButton} { + path { + fill: ${props => props.theme.closeIconColor || closeIconColor}; + } + } + `; +} diff --git a/components/help-box/styled.jsx b/components/help-box/styled.jsx index 05954a98c..b880d12bc 100644 --- a/components/help-box/styled.jsx +++ b/components/help-box/styled.jsx @@ -1,8 +1,8 @@ import styled from 'styled-components'; -import { fonts, colors, thickness } from '../../components/shared-styles'; +import { variant } from 'styled-system'; +import { themeGet } from '@styled-system/theme-get'; import { LightBulbH } from '../icons'; import { resetStyles } from '../utils'; -import { mediaSizes } from '../shared-styles'; export const HelpBoxContent = styled.div``; @@ -18,11 +18,7 @@ export const IconDiv = styled.div``; export const RightIconDiv = styled.div``; -export const HelpBox = variantCreator( - colors.blueTint, - colors.blueLight, - colors.blueDark, -)(styled.div` +export const HelpBox = styled.div` position: relative; display: flex; border-radius: 3px; @@ -46,37 +42,41 @@ export const HelpBox = variantCreator( } ${HelpBoxContent} { - ${fonts.c16}; + font-weight: ${themeGet('fontWeights.regular')}; display: flex; flex: 1; text-align: left; line-height: 1.25; font-size: 16px; - color: ${colors.flGray}; + color: ${themeGet('colors.flGray')}; height: ${props => (props.large ? '230px' : '')}; padding: 14px 16px 14px 12px; flex-direction: ${props => (props.stacked ? 'column' : 'row')}; - @media (max-width: ${mediaSizes.phone}) { + @media (max-width: ${themeGet('breakpoints.phone')}) { flex-direction: column; } ${HelpBoxBody} { - ${fonts.c16}; + font-size: ${themeGet('fontSizes.3')}; + font-weight: ${themeGet('fontWeights.regular')}; + line-height: ${themeGet('lineHeights.3')}; display: flex; flex: 1; order: 2; } ${HelpBoxFooter} { - ${fonts.c16}; + font-size: ${themeGet('fontSizes.3')}; + font-weight: ${themeGet('fontWeights.regular')}; + line-height: ${themeGet('lineHeights.3')}; display: flex; order: 2; align-items: center; margin: ${props => (props.stacked ? '12px 16px 0px 0px' : '-7px 0px -7px 16px')}; - @media (max-width: ${mediaSizes.phone}) { + @media (max-width: ${themeGet('breakpoints.phone')}) { margin: 12px 0px 0px 0px; } } @@ -85,7 +85,7 @@ export const HelpBox = variantCreator( ${CloseButton} { cursor: pointer; margin: ${props => (props.large ? '15px 16px 0px 16px' : '15px 16px 0px 12px')}; - margin-left: ${props => (!props.stacked ? '-4px' : '')}; + margin-left: ${props => !props.stacked && '-4px'}; height: 18px; background: transparent; padding: 0; @@ -99,7 +99,7 @@ export const HelpBox = variantCreator( ${RightIconDiv} { margin: ${props => (props.large ? '15px 16px 0px 16px' : '15px 16px 0px 12px')}; - margin-left: ${props => (!props.stacked ? '-4px' : '')}; + margin-left: ${props => !props.stacked && '-4px'}; height: 18px; background: transparent; padding: 0; @@ -110,32 +110,47 @@ export const HelpBox = variantCreator( padding: 0; } } -`); -export const variationMap = { - success: variantCreator(colors.greenTint, colors.greenLight, colors.greenDark), - danger: variantCreator(colors.redTint, colors.redLight, colors.redDark), - warning: variantCreator(colors.yellowTint, colors.yellowLight, colors.yellowDark), - minor: variantCreator(colors.gray4, colors.gray14, colors.gray34), + ${resetStyles} + + ${props => + variant({ + variants: { + primary: createColorVariant('primary', props), + success: createColorVariant('success', props), + danger: createColorVariant('danger', props), + warning: createColorVariant('warning', props), + minor: createColorVariant('minor', props), + }, + })} +`; + +HelpBox.defaultProps = { + variant: 'primary', }; -function variantCreator(backgroundColor, foregroundColor, closeIconColor) { - return component => styled(component)` - ${resetStyles}; - - background-color: ${props => props.theme.backgroundColor || backgroundColor}; - border-left: solid ${thickness.four} ${props => props.theme.foregroundColor || foregroundColor}; - - ${IconDiv} { - path { - fill: ${props => props.theme.foregroundColor || foregroundColor}; - } - } - - ${CloseButton} { - path { - fill: ${props => props.theme.closeIconColor || closeIconColor}; - } - } - `; +/** + * Generates the styles for a given color variant. + * + * @param {'primary'|'success'|'danger'|'warning'|'minor'} name - The name of the color variant. + * @param {object} props - `HelpBox`'s `props` object. + * @returns {object} That variant's style object. + */ +function createColorVariant(name, props) { + return { + backgroundColor: `helpBox.${name}Background`, + borderLeft: `solid ${themeGet('space.2')(props)} ${themeGet(`colors.helpBox.${name}Foreground`)( + props, + )}`, + [IconDiv]: { + path: { + fill: themeGet(`colors.helpBox.${name}Foreground`)(props), + }, + }, + [CloseButton]: { + path: { + fill: themeGet(`colors.helpBox.${name}Icon`)(props), + }, + }, + }; } diff --git a/components/utils/index.js b/components/utils/index.js index 2ba5311db..b3887bfc5 100644 --- a/components/utils/index.js +++ b/components/utils/index.js @@ -5,11 +5,18 @@ export { filterChildProps } from './filter-props'; export { deprecate, deprecateComponent, deprecateProp } from './deprecate'; export { getConfigProps, getConfigChild } from './get-config-props'; -export function getVariation(variant, obj) { +/** + * Chooses the correct variant from a main variant prop and a set of boolean shortcut props. + * + * @param {string|undefined} variant - The value of the component's `variant` prop. + * @param {{ [propName: string]: boolean }} shortcutProps - An object of shortcut prop names and boolean values, the names corresponding to variant name options. + * @returns {string|null} The name of the selected variant (or `null` if no variant has been selected). + */ +export function getVariation(variant, shortcutProps) { if (variant) { return variant; } - const match = [...Object.entries(obj)].find(entry => entry[1]); + const match = [...Object.entries(shortcutProps)].find(entry => entry[1]); return match ? match[0] : null; } diff --git a/index-v6.js b/index-v6.js index 3aa466382..f1b651ce9 100644 --- a/index-v6.js +++ b/index-v6.js @@ -1,6 +1,7 @@ export { Button, SegmentedButtonGroup } from './components/button'; export { Checkbox } from './components/check-box'; export { Dropdown } from './components/dropdown'; +export { HelpBox } from './components/help-box'; export { Modal } from './components/modal'; export { usePopover, Popover } from './components/popover-v6'; export { Radio } from './components/radio'; diff --git a/index.js b/index.js index 654d06e19..ab0e5bc31 100644 --- a/index.js +++ b/index.js @@ -14,7 +14,7 @@ export { DatePickerInput } from './components/date-picker-input'; export { DatePeriodPicker } from './components/date-period-picker'; export { DropZone } from './components/drop-zone'; export { FilesSection } from './components/files-section'; -export { HelpBox } from './components/help-box'; +export { LegacyHelpBox as HelpBox } from './components/help-box'; export { Input, FilterInput, NumberInput } from './components/input'; export { LoadingSpinner } from './components/loading-spinner'; export { diff --git a/theme/core.js b/theme/core.js index 543468029..8093476a2 100644 --- a/theme/core.js +++ b/theme/core.js @@ -205,6 +205,24 @@ colors.datePickerInput = { iconColor: colors.gray52, }; +colors.helpBox = { + primaryBackground: colors.blue1, + primaryForeground: colors.blue3, + primaryIcon: colors.blue5, + successBackground: colors.green1, + successForeground: colors.green2, + successIcon: colors.green5, + dangerBackground: colors.red1, + dangerForeground: colors.red3, + dangerIcon: colors.red5, + warningBackground: colors.yellow1, + warningForeground: colors.yellow3, + warningIcon: colors.yellow5, + minorBackground: colors.gray4, + minorForeground: colors.gray14, + minorIcon: colors.gray34, +}; + colors.radio = { primary: colors.blue4, border: colors.checkbox.border,