From 74416c3110e1d690be15b67791f6894354b8b01f Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Wed, 6 Jan 2021 15:31:32 -0600 Subject: [PATCH 01/20] Add type for AccordionItem 'pinned' prop --- components/accordion/accordion-item.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/accordion/accordion-item.jsx b/components/accordion/accordion-item.jsx index 7135ea1b4..bb176cdd3 100644 --- a/components/accordion/accordion-item.jsx +++ b/components/accordion/accordion-item.jsx @@ -46,4 +46,6 @@ AccordionItem.propTypes = { children: PropTypes.node.isRequired, /** This is supplied by the Accordion component. */ index: PropTypes.number, + /** If `true`, the item will remain permanenty expanded. */ + pinned: PropTypes.bool, }; From 4f0279334cf972e970c7c33985a7764ad8199f92 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Wed, 6 Jan 2021 15:32:19 -0600 Subject: [PATCH 02/20] Add Box/typography props to Accordion components --- components/accordion/accordion-header.jsx | 8 ++++++-- components/accordion/accordion-item.jsx | 13 ++++++++++--- components/accordion/accordion-panel.jsx | 3 +++ components/accordion/accordion.jsx | 4 ++++ 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/components/accordion/accordion-header.jsx b/components/accordion/accordion-header.jsx index 78fb0e942..8311bf098 100644 --- a/components/accordion/accordion-header.jsx +++ b/components/accordion/accordion-header.jsx @@ -5,6 +5,7 @@ import { Text } from '../Text'; import { Box } from '../Box'; import { UtilityButton } from '../button'; import { ChevronRight, ChevronDown } from '../icons/12px'; +import { typography } from '../../theme/system'; import { useAccordionContext, useAccordionItemContext } from './accordion-util'; export function AccordionHeader({ @@ -56,7 +57,7 @@ export function AccordionHeader({ }, [headerId, focusableChildList]); return ( - ) : null} - + ); } @@ -131,8 +132,11 @@ AccordionHeader.propTypes = { /** Receives an isExpanded boolean value. */ renderCustomIndicator: PropTypes.func, ...Box.propTypes, + ...typography.propTypes, }; +const HeaderBox = styled(Box)(typography); + const Heading = styled.header.attrs(({ ariaLevel }) => ({ role: 'heading', 'aria-level': ariaLevel, diff --git a/components/accordion/accordion-item.jsx b/components/accordion/accordion-item.jsx index bb176cdd3..422c7840f 100644 --- a/components/accordion/accordion-item.jsx +++ b/components/accordion/accordion-item.jsx @@ -1,10 +1,12 @@ import React, { useCallback, useMemo } from 'react'; +import styled from 'styled-components'; import PropTypes from 'prop-types'; import { useId } from '../shared-hooks'; import { Box } from '../Box'; +import { typography } from '../../theme/system'; import { useAccordionContext, AccordionItemContextProvider } from './accordion-util'; -export function AccordionItem({ children, index, pinned: isPinned }) { +export function AccordionItem({ children, index, pinned: isPinned, ...otherProps }) { const { expandedSections, onExpansion } = useAccordionContext(); const isExpanded = expandedSections.includes(index); @@ -29,15 +31,16 @@ export function AccordionItem({ children, index, pinned: isPinned }) { ); return ( - {children} - + ); } @@ -48,4 +51,8 @@ AccordionItem.propTypes = { index: PropTypes.number, /** If `true`, the item will remain permanenty expanded. */ pinned: PropTypes.bool, + ...Box.propTypes, + ...typography.propTypes, }; + +const ItemBox = styled(Box)(typography); diff --git a/components/accordion/accordion-panel.jsx b/components/accordion/accordion-panel.jsx index a6a4635be..8b82099e9 100644 --- a/components/accordion/accordion-panel.jsx +++ b/components/accordion/accordion-panel.jsx @@ -6,6 +6,7 @@ import { Collapse } from '../collapse'; import { Box } from '../Box'; import { useAccordionItemContext } from './accordion-util'; import { useAccordionContext } from './accordion-util'; +import { typography } from '../../theme/system'; export function AccordionPanel({ children, mountOnEnter, unmountOnExit, ...props }) { const ctx = useAccordionContext(); @@ -32,6 +33,7 @@ AccordionPanel.propTypes = { /** true if panel contents should be unmounted when the section is closed **/ unmountOnExit: PropTypes.bool, ...Box.propTypes, + ...typography.propTypes, }; export const Panel = styled(Box).attrs(({ headerId, panelId }) => ({ @@ -51,4 +53,5 @@ export const Panel = styled(Box).attrs(({ headerId, panelId }) => ({ }, }, }), + typography, ); diff --git a/components/accordion/accordion.jsx b/components/accordion/accordion.jsx index 79621f26b..d571e0ca5 100644 --- a/components/accordion/accordion.jsx +++ b/components/accordion/accordion.jsx @@ -5,6 +5,7 @@ import { system } from 'styled-system'; import { propType as styledPropType } from '@styled-system/prop-types'; import { Box } from '../Box'; import { useKeyboardNav, AccordionContextProvider } from './accordion-util'; +import { typography } from '../../theme/system'; import { Panel } from './accordion-panel'; export function Accordion({ @@ -95,6 +96,7 @@ Accordion.propTypes = { /** Overrides the `padding` style on all nested `Accordion.Panel`s */ panelPadding: styledPropType, ...Box.propTypes, + ...typography.propTypes, }; const AccordionBox = styled(Box)` @@ -106,4 +108,6 @@ const AccordionBox = styled(Box)` }, })} } + + ${typography} `; From c632cb1b28e5e19fd205a031e9b9a4d24d45145f Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Wed, 6 Jan 2021 15:47:21 -0600 Subject: [PATCH 03/20] Add typography props to SegmentedButtonGroup --- components/button/Button.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/button/Button.js b/components/button/Button.js index 8bd92a481..bda5075b0 100644 --- a/components/button/Button.js +++ b/components/button/Button.js @@ -141,6 +141,8 @@ const SegmentedButtonGroup = styled(Box).attrs(({ border }) => ({ border-bottom-right-radius: 0; } } + + ${typography} `; export { Button, SegmentedButtonGroup }; From caedf15686b5d2b164d616d31dc53811c0666ba8 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Wed, 6 Jan 2021 16:12:14 -0600 Subject: [PATCH 04/20] Add common/typography props to CheckBox components --- components/check-box/checkbox-content.jsx | 5 +++-- components/check-box/component.jsx | 3 +++ components/check-box/styled.jsx | 8 ++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/components/check-box/checkbox-content.jsx b/components/check-box/checkbox-content.jsx index 96f11c868..aec118a51 100644 --- a/components/check-box/checkbox-content.jsx +++ b/components/check-box/checkbox-content.jsx @@ -4,6 +4,7 @@ import styledSystemPropTypes from '@styled-system/prop-types'; import styled from 'styled-components'; import { getConfigChild } from '../utils'; import { Text } from '../Text'; +import { common } from '../../theme/system'; import * as Styled from './styled'; export function CheckboxContent({ isChecked, title, disabled, children, ...otherProps }) { @@ -30,8 +31,8 @@ CheckboxContent.propTypes = { title: PropTypes.string, isChecked: PropTypes.oneOf([true, false, 'mixed']), disabled: PropTypes.bool, + ...common.propTypes, ...styledSystemPropTypes.position, - ...styledSystemPropTypes.space, ...styledSystemPropTypes.layout, }; @@ -41,8 +42,8 @@ CheckboxContent.propTypes = { */ export const CheckboxBox = props => null; CheckboxBox.propTypes = { + ...common.propTypes, ...styledSystemPropTypes.position, - ...styledSystemPropTypes.space, ...styledSystemPropTypes.layout, }; CheckboxBox.childConfigComponent = 'CheckboxBox'; diff --git a/components/check-box/component.jsx b/components/check-box/component.jsx index d4f47d86b..179b72732 100644 --- a/components/check-box/component.jsx +++ b/components/check-box/component.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { CheckboxContent } from './checkbox-content'; import * as Styled from './styled'; import { DefaultThemeProvider } from '../DefaultThemeProvider'; +import { common, typography } from '../../theme/system'; /** Styled checkbox control with consistent styling across platforms */ export const Checkbox = function Checkbox({ @@ -67,6 +68,8 @@ Checkbox.propTypes = { /** Disables automatic blur. */ disableAutoBlur: PropTypes.bool, disabled: PropTypes.bool, + ...common.propTypes, + ...typography.propTypes, }; Checkbox.defaultProps = { diff --git a/components/check-box/styled.jsx b/components/check-box/styled.jsx index 42edf3de7..cb7286247 100644 --- a/components/check-box/styled.jsx +++ b/components/check-box/styled.jsx @@ -1,7 +1,8 @@ import styled, { css } from 'styled-components'; -import { position, space, layout } from 'styled-system'; +import { position, layout } from 'styled-system'; import { resetStyles } from '../utils'; import { Box } from '../Box'; +import { common, typography } from '../../theme/system'; export const CheckboxDiv = styled(Box)` display: inline; @@ -19,8 +20,8 @@ export const CheckboxDiv = styled(Box)` background-color: ${theme.colors.checkbox.disabledBackground}; `} + ${common} ${position} - ${space} ${layout} `; @@ -64,6 +65,9 @@ export const CheckboxContainer = styled.button` box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.checkbox.shadowFocused}; } } + + ${common} + ${typography} `; export const isCheckedStyles = css` From ce814ea8666af1726a5ade2367d6446b91a8e903 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Wed, 6 Jan 2021 16:22:59 -0600 Subject: [PATCH 05/20] Add Box prop types to Collapse component --- components/collapse/component.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/collapse/component.jsx b/components/collapse/component.jsx index c5b0e4d22..b93d29e14 100644 --- a/components/collapse/component.jsx +++ b/components/collapse/component.jsx @@ -8,6 +8,7 @@ const propTypes = { ...Transition.propTypes, isOpen: PropTypes.bool, children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]), + ...Box.propTypes, }; const defaultProps = { From 763b501b187dae746ee5e8795b5b80c3ae2355e9 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Wed, 6 Jan 2021 16:40:23 -0600 Subject: [PATCH 06/20] Add common/typography props to DatePicker --- components/date-picker/component.jsx | 9 ++++++--- components/date-picker/styled.jsx | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/components/date-picker/component.jsx b/components/date-picker/component.jsx index 5f9ecaeba..9a0d2cd1c 100644 --- a/components/date-picker/component.jsx +++ b/components/date-picker/component.jsx @@ -1,8 +1,9 @@ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { Caret } from '../icons'; import { colors } from '../shared-styles'; import { dateFunctionProps } from './date-function-props'; +import { common, typography } from '../../theme/system'; import * as Styled from './styled'; import { CalendarWeek } from './calendar-week'; @@ -55,6 +56,8 @@ export class DatePicker extends Component { dateFunctions: dateFunctionProps, minDate: PropTypes.instanceOf(Date), maxDate: PropTypes.instanceOf(Date), + ...common.propTypes, + ...typography.propTypes, }; UNSAFE_componentWillMount() { @@ -126,7 +129,7 @@ export class DatePicker extends Component { const { currentMonth, weeks } = this.state; return ( - + ))} - + ); } } diff --git a/components/date-picker/styled.jsx b/components/date-picker/styled.jsx index c470e8238..502faeedf 100644 --- a/components/date-picker/styled.jsx +++ b/components/date-picker/styled.jsx @@ -1,5 +1,8 @@ import styled from 'styled-components'; import { colors } from '../shared-styles'; +import { common, typography } from '../../theme/system'; + +export const Container = styled.div(common, typography); export const ChangeMonth = styled.button` display: flex; From 2a791d70f5966cdf93aac2318a1f2cb1935510cf Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Wed, 6 Jan 2021 17:00:06 -0600 Subject: [PATCH 07/20] Use global Styled System theme in DatePicker DatePicker was previously using colors from "shared-styles". --- components/date-picker/component.jsx | 119 ++++++++++++++------------- components/date-picker/styled.jsx | 17 ++-- theme/core.js | 8 ++ 3 files changed, 78 insertions(+), 66 deletions(-) diff --git a/components/date-picker/component.jsx b/components/date-picker/component.jsx index 9a0d2cd1c..b0a6fe05a 100644 --- a/components/date-picker/component.jsx +++ b/components/date-picker/component.jsx @@ -1,7 +1,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { Caret } from '../icons'; -import { colors } from '../shared-styles'; +import { theme } from '../../theme'; +import { DefaultThemeProvider } from '../DefaultThemeProvider'; import { dateFunctionProps } from './date-function-props'; import { common, typography } from '../../theme/system'; import * as Styled from './styled'; @@ -129,63 +130,65 @@ export class DatePicker extends Component { const { currentMonth, weeks } = this.state; return ( - - - - - - - {dateFunctions.format(currentMonth, 'MMMM yyyy')} - - - - - - S - M - T - W - T - F - S - - - {weeks.map(week => ( - - ))} - - + + + + + + + + {dateFunctions.format(currentMonth, 'MMMM yyyy')} + + + + + + S + M + T + W + T + F + S + + + {weeks.map(week => ( + + ))} + + + ); } } diff --git a/components/date-picker/styled.jsx b/components/date-picker/styled.jsx index 502faeedf..c3ef86458 100644 --- a/components/date-picker/styled.jsx +++ b/components/date-picker/styled.jsx @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { colors } from '../shared-styles'; +import { themeGet } from '@styled-system/theme-get'; import { common, typography } from '../../theme/system'; export const Container = styled.div(common, typography); @@ -17,7 +17,8 @@ export const ChangeMonth = styled.button` cursor: pointer; &:focus { - outline: ${({ visuallyDisabled }) => visuallyDisabled && `${colors.gray22} auto 1px`}; + outline: ${({ visuallyDisabled }) => + visuallyDisabled && `${themeGet('colors.datePicker.disabledOutline')} auto 1px`}; } `; @@ -27,8 +28,8 @@ export const Header = styled.div` align-items: center; white-space: nowrap; border-bottom: none; - background: ${colors.white}; - color: ${colors.gray66}; + background: ${themeGet('colors.datePicker.background')}; + color: ${themeGet('colors.datePicker.header')}; line-height: 32px; font-weight: bold; `; @@ -39,12 +40,12 @@ export const MonthLabel = styled.div` export const Week = styled.ul` display: flex; - border-bottom: 1px solid ${colors.gray14}; + border-bottom: 1px solid ${themeGet('colors.datePicker.weekBorder')}; padding: 8px 0; - background: ${colors.white}; + background: ${themeGet('colors.datePicker.background')}; list-style: none; margin: 0; - color: ${colors.gray22}; + color: ${themeGet('colors.datePicker.week')}; font-size: 12px; `; @@ -55,7 +56,7 @@ export const WeekDay = styled.li` `; export const Month = styled.div` - background: ${colors.white}; + background: ${themeGet('colors.datePicker.background')}; font-size: 14px; @media (hover: none) { diff --git a/theme/core.js b/theme/core.js index e2a9428cb..e3e4ce8d3 100644 --- a/theme/core.js +++ b/theme/core.js @@ -201,6 +201,14 @@ colors.checkbox = { shadowFocused: colors.input.shadowFocused, }; +colors.datePicker = { + disabledOutline: colors.gray22, + background: colors.white, + header: colors.gray66, + week: colors.gray22, + weekBorder: colors.gray14, +}; + colors.datePickerInput = { iconColor: colors.gray52, }; From 938df8a366890e28b44fe2347fb54b98c6e5fd19 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Wed, 6 Jan 2021 17:14:34 -0600 Subject: [PATCH 08/20] Copy updated filterProps function from #410 Final version from da75a5d2e4a3987f704b6496bead5b2743b6272b. --- components/utils/filter-props.js | 31 ++++++++++++++++++++++--------- components/utils/index.js | 2 +- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/components/utils/filter-props.js b/components/utils/filter-props.js index 845a5df25..83668ef48 100644 --- a/components/utils/filter-props.js +++ b/components/utils/filter-props.js @@ -1,15 +1,28 @@ -export function filterChildProps(props, childPropTypes) { - const parentProps = {}; - const childProps = {}; - const childPropNames = Object.keys(childPropTypes); +/** + * Filters a collection of props by a list of names (or a prop types object). + * + * @param {object} props - A props object. + * @param {(string[] | object)} filterPropNames - An array of prop names to filter by (or a prop + * types object with keys to filter by). + * @returns {{ matchingProps: object, remainingProps: object }} An object containing two props + * objects: one containing the props that match the filter and the other containing the props that + * don't match. + */ +export function filterProps(props, filterPropNames) { + const filterList = Array.isArray(filterPropNames) + ? filterPropNames + : Object.keys(filterPropNames); - for (const [propName, prop] of Object.entries(props)) { - if (childPropNames.includes(propName)) { - childProps[propName] = prop; + const matchingProps = {}; + const remainingProps = {}; + + for (const [propName, propValue] of Object.entries(props)) { + if (filterList.includes(propName)) { + matchingProps[propName] = propValue; } else { - parentProps[propName] = prop; + remainingProps[propName] = propValue; } } - return [parentProps, childProps]; + return { matchingProps, remainingProps }; } diff --git a/components/utils/index.js b/components/utils/index.js index b3887bfc5..4f5ad5393 100644 --- a/components/utils/index.js +++ b/components/utils/index.js @@ -1,7 +1,7 @@ export { BootstrapContainer, wrapBootstrap } from './bootstrap-container'; export { forwardClassRef } from './forwardref-wrapper'; export { TransitionStatuses, TransitionTimeouts } from './transition-group-utils'; -export { filterChildProps } from './filter-props'; +export { filterProps } from './filter-props'; export { deprecate, deprecateComponent, deprecateProp } from './deprecate'; export { getConfigProps, getConfigChild } from './get-config-props'; From eb7df0232c4555b41d13ef1cc0c6170dc8837074 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Fri, 8 Jan 2021 15:48:27 -0600 Subject: [PATCH 09/20] Revert 'typography' prop additions where unneeded --- components/accordion/accordion-header.jsx | 8 ++------ components/accordion/accordion-item.jsx | 9 ++------- components/accordion/accordion-panel.jsx | 3 --- components/accordion/accordion.jsx | 4 ---- components/button/Button.js | 2 -- components/check-box/component.jsx | 3 +-- components/check-box/styled.jsx | 3 +-- components/date-picker/component.jsx | 3 +-- components/date-picker/styled.jsx | 4 ++-- 9 files changed, 9 insertions(+), 30 deletions(-) diff --git a/components/accordion/accordion-header.jsx b/components/accordion/accordion-header.jsx index 8311bf098..78fb0e942 100644 --- a/components/accordion/accordion-header.jsx +++ b/components/accordion/accordion-header.jsx @@ -5,7 +5,6 @@ import { Text } from '../Text'; import { Box } from '../Box'; import { UtilityButton } from '../button'; import { ChevronRight, ChevronDown } from '../icons/12px'; -import { typography } from '../../theme/system'; import { useAccordionContext, useAccordionItemContext } from './accordion-util'; export function AccordionHeader({ @@ -57,7 +56,7 @@ export function AccordionHeader({ }, [headerId, focusableChildList]); return ( - ) : null} - + ); } @@ -132,11 +131,8 @@ AccordionHeader.propTypes = { /** Receives an isExpanded boolean value. */ renderCustomIndicator: PropTypes.func, ...Box.propTypes, - ...typography.propTypes, }; -const HeaderBox = styled(Box)(typography); - const Heading = styled.header.attrs(({ ariaLevel }) => ({ role: 'heading', 'aria-level': ariaLevel, diff --git a/components/accordion/accordion-item.jsx b/components/accordion/accordion-item.jsx index 422c7840f..fab3e199c 100644 --- a/components/accordion/accordion-item.jsx +++ b/components/accordion/accordion-item.jsx @@ -1,9 +1,7 @@ import React, { useCallback, useMemo } from 'react'; -import styled from 'styled-components'; import PropTypes from 'prop-types'; import { useId } from '../shared-hooks'; import { Box } from '../Box'; -import { typography } from '../../theme/system'; import { useAccordionContext, AccordionItemContextProvider } from './accordion-util'; export function AccordionItem({ children, index, pinned: isPinned, ...otherProps }) { @@ -31,7 +29,7 @@ export function AccordionItem({ children, index, pinned: isPinned, ...otherProps ); return ( - {children} - + ); } @@ -52,7 +50,4 @@ AccordionItem.propTypes = { /** If `true`, the item will remain permanenty expanded. */ pinned: PropTypes.bool, ...Box.propTypes, - ...typography.propTypes, }; - -const ItemBox = styled(Box)(typography); diff --git a/components/accordion/accordion-panel.jsx b/components/accordion/accordion-panel.jsx index 8b82099e9..a6a4635be 100644 --- a/components/accordion/accordion-panel.jsx +++ b/components/accordion/accordion-panel.jsx @@ -6,7 +6,6 @@ import { Collapse } from '../collapse'; import { Box } from '../Box'; import { useAccordionItemContext } from './accordion-util'; import { useAccordionContext } from './accordion-util'; -import { typography } from '../../theme/system'; export function AccordionPanel({ children, mountOnEnter, unmountOnExit, ...props }) { const ctx = useAccordionContext(); @@ -33,7 +32,6 @@ AccordionPanel.propTypes = { /** true if panel contents should be unmounted when the section is closed **/ unmountOnExit: PropTypes.bool, ...Box.propTypes, - ...typography.propTypes, }; export const Panel = styled(Box).attrs(({ headerId, panelId }) => ({ @@ -53,5 +51,4 @@ export const Panel = styled(Box).attrs(({ headerId, panelId }) => ({ }, }, }), - typography, ); diff --git a/components/accordion/accordion.jsx b/components/accordion/accordion.jsx index d571e0ca5..79621f26b 100644 --- a/components/accordion/accordion.jsx +++ b/components/accordion/accordion.jsx @@ -5,7 +5,6 @@ import { system } from 'styled-system'; import { propType as styledPropType } from '@styled-system/prop-types'; import { Box } from '../Box'; import { useKeyboardNav, AccordionContextProvider } from './accordion-util'; -import { typography } from '../../theme/system'; import { Panel } from './accordion-panel'; export function Accordion({ @@ -96,7 +95,6 @@ Accordion.propTypes = { /** Overrides the `padding` style on all nested `Accordion.Panel`s */ panelPadding: styledPropType, ...Box.propTypes, - ...typography.propTypes, }; const AccordionBox = styled(Box)` @@ -108,6 +106,4 @@ const AccordionBox = styled(Box)` }, })} } - - ${typography} `; diff --git a/components/button/Button.js b/components/button/Button.js index bda5075b0..8bd92a481 100644 --- a/components/button/Button.js +++ b/components/button/Button.js @@ -141,8 +141,6 @@ const SegmentedButtonGroup = styled(Box).attrs(({ border }) => ({ border-bottom-right-radius: 0; } } - - ${typography} `; export { Button, SegmentedButtonGroup }; diff --git a/components/check-box/component.jsx b/components/check-box/component.jsx index 179b72732..b0bb62bca 100644 --- a/components/check-box/component.jsx +++ b/components/check-box/component.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { CheckboxContent } from './checkbox-content'; import * as Styled from './styled'; import { DefaultThemeProvider } from '../DefaultThemeProvider'; -import { common, typography } from '../../theme/system'; +import { common } from '../../theme/system'; /** Styled checkbox control with consistent styling across platforms */ export const Checkbox = function Checkbox({ @@ -69,7 +69,6 @@ Checkbox.propTypes = { disableAutoBlur: PropTypes.bool, disabled: PropTypes.bool, ...common.propTypes, - ...typography.propTypes, }; Checkbox.defaultProps = { diff --git a/components/check-box/styled.jsx b/components/check-box/styled.jsx index cb7286247..355fd8c32 100644 --- a/components/check-box/styled.jsx +++ b/components/check-box/styled.jsx @@ -1,8 +1,8 @@ import styled, { css } from 'styled-components'; import { position, layout } from 'styled-system'; +import { common } from '../../theme/system'; import { resetStyles } from '../utils'; import { Box } from '../Box'; -import { common, typography } from '../../theme/system'; export const CheckboxDiv = styled(Box)` display: inline; @@ -67,7 +67,6 @@ export const CheckboxContainer = styled.button` } ${common} - ${typography} `; export const isCheckedStyles = css` diff --git a/components/date-picker/component.jsx b/components/date-picker/component.jsx index b0a6fe05a..2539300ae 100644 --- a/components/date-picker/component.jsx +++ b/components/date-picker/component.jsx @@ -4,7 +4,7 @@ import { Caret } from '../icons'; import { theme } from '../../theme'; import { DefaultThemeProvider } from '../DefaultThemeProvider'; import { dateFunctionProps } from './date-function-props'; -import { common, typography } from '../../theme/system'; +import { common } from '../../theme/system'; import * as Styled from './styled'; import { CalendarWeek } from './calendar-week'; @@ -58,7 +58,6 @@ export class DatePicker extends Component { minDate: PropTypes.instanceOf(Date), maxDate: PropTypes.instanceOf(Date), ...common.propTypes, - ...typography.propTypes, }; UNSAFE_componentWillMount() { diff --git a/components/date-picker/styled.jsx b/components/date-picker/styled.jsx index c3ef86458..374e56bb9 100644 --- a/components/date-picker/styled.jsx +++ b/components/date-picker/styled.jsx @@ -1,8 +1,8 @@ import styled from 'styled-components'; import { themeGet } from '@styled-system/theme-get'; -import { common, typography } from '../../theme/system'; +import { common } from '../../theme/system'; -export const Container = styled.div(common, typography); +export const Container = styled.div(common); export const ChangeMonth = styled.button` display: flex; From c76ca22c93693a8328c3752c7f7728a94c20b4c0 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Fri, 8 Jan 2021 16:35:58 -0600 Subject: [PATCH 10/20] Use global Styled System theme in DatePeriodPicker Instead of the old "shared-styles". --- components/date-period-picker/component.jsx | 87 +++++++++++---------- components/date-period-picker/styled.jsx | 28 ++++--- theme/core.js | 7 ++ 3 files changed, 67 insertions(+), 55 deletions(-) diff --git a/components/date-period-picker/component.jsx b/components/date-period-picker/component.jsx index 996c52540..b4db6e04f 100644 --- a/components/date-period-picker/component.jsx +++ b/components/date-period-picker/component.jsx @@ -4,6 +4,7 @@ import debounce from 'lodash.debounce'; import { DatePicker } from '../date-picker'; import { Input } from '../input'; import { dateFunctionProps } from '../date-picker/date-function-props'; +import { DefaultThemeProvider } from '../DefaultThemeProvider'; import * as Styled from './styled'; const DATE_FORMAT_STRING = 'M/d/yyyy'; @@ -187,49 +188,51 @@ export class DatePeriodPicker extends PureComponent { } = this.state; return ( - - {this.getUniqueDatePeriods().map(({ displayName, dateRange, originalIndex }) => ( - { - setSelectedDate(dateRange, originalIndex); - }} - > - {displayName} - - ))} - - - From - this.handleInputValueChange(event.target.value, 'start')} - small + + + {this.getUniqueDatePeriods().map(({ displayName, dateRange, originalIndex }) => ( + { + setSelectedDate(dateRange, originalIndex); + }} + > + {displayName} + + ))} + + + From + this.handleInputValueChange(event.target.value, 'start')} + small + /> + + + To + this.handleInputValueChange(event.target.value, 'end')} + small + /> + + + + + setSelectedDate(dateRange, this.getDatePeriodIndex(dateRange)) + } + validate={validate} + dateFunctions={dateFunctions} /> - - - To - this.handleInputValueChange(event.target.value, 'end')} - small - /> - - - - - setSelectedDate(dateRange, this.getDatePeriodIndex(dateRange)) - } - validate={validate} - dateFunctions={dateFunctions} - /> - - + + + ); } } diff --git a/components/date-period-picker/styled.jsx b/components/date-period-picker/styled.jsx index 199a298c0..48fa90806 100644 --- a/components/date-period-picker/styled.jsx +++ b/components/date-period-picker/styled.jsx @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { colors, thickness, fonts } from '../shared-styles'; +import { themeGet } from '@styled-system/theme-get'; export const Container = styled.div` display: flex; @@ -12,25 +12,26 @@ export const Container = styled.div` max-width: 324px; } - padding: ${thickness.four} 0 ${thickness.twelve} 0; + padding: ${themeGet('space.2')} 0 ${themeGet('space.4')} 0; overflow: hidden; `; export const DatePickerContainer = styled.div` - padding: ${thickness.eight}; + padding: ${themeGet('space.3')}; `; export const DatePeriod = styled.div` - font: ${fonts.ui14}; - padding: ${thickness.eight}; - background: ${colors.white}; + ${themeGet('textStyles.ui.14')} + + padding: ${themeGet('space.3')}; + background: ${themeGet('colors.datePeriodPicker.background')}; cursor: pointer; width: 100%; text-align: initial; &:hover { - background: ${colors.blueBase}; - color: ${colors.white}; + background: ${themeGet('colors.datePeriodPicker.hoverBackground')}; + color: ${themeGet('colors.datePeriodPicker.hoverText')}; transition: background 0.2s ease-out, color 0.2s ease-out; } `; @@ -38,8 +39,8 @@ export const DatePeriod = styled.div` export const DateInputContainer = styled.div` display: flex; flex-direction: row; - border-top: 1px solid ${colors.gray14}; - padding: ${thickness.twelve} ${thickness.eight}; + border-top: 1px solid ${themeGet('colors.datePeriodPicker.inputBorder')}; + padding: ${themeGet('space.4')} ${themeGet('space.3')}; `; export const Label = styled.label` @@ -47,13 +48,14 @@ export const Label = styled.label` text-align: initial; display: flex; flex-direction: column; - font: ${fonts.ui14}; + + ${themeGet('textStyles.ui.14')} &:first-of-type { - padding-right: ${thickness.eight}; + padding-right: ${themeGet('space.3')}; } `; export const LabelText = styled.span` - padding-bottom: ${thickness.four}; + padding-bottom: ${themeGet('space.2')}; `; diff --git a/theme/core.js b/theme/core.js index e3e4ce8d3..ce3ac5753 100644 --- a/theme/core.js +++ b/theme/core.js @@ -209,6 +209,13 @@ colors.datePicker = { weekBorder: colors.gray14, }; +colors.datePeriodPicker = { + background: colors.datePicker.background, + hoverBackground: colors.blue4, + hoverText: colors.white, + inputBorder: colors.datePicker.weekBorder, +}; + colors.datePickerInput = { iconColor: colors.gray52, }; From 5d5c1f81ce87420c051ebe977bbf24c496e5fd30 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Fri, 8 Jan 2021 17:08:13 -0600 Subject: [PATCH 11/20] Move new DatePicker to a separate v6 export And restore the v5 DatePicker to a "legacy" export. This also involved some adjustments DatePeriodPicker's and DatePickerInput's calls of DatePicker, since the v6 DatePicker no longer needs to be wrapped. I also added the Styled System 'layout' props to DatePicker so DatePickerInput could use a 'width' prop on it. --- UpgradeGuide.md | 2 + components/date-period-picker/component.jsx | 21 +- components/date-period-picker/styled.jsx | 4 - components/date-picker-input/component.jsx | 21 +- .../date-picker-input/legacy-component.jsx | 21 +- components/date-picker-input/styled.jsx | 4 - components/date-picker/index.js | 3 + components/date-picker/legacy-component.jsx | 188 ++++++++++++++++++ components/date-picker/legacy-styled.jsx | 61 ++++++ components/date-picker/styled.jsx | 3 +- index-v6.js | 1 + index.js | 2 +- 12 files changed, 288 insertions(+), 43 deletions(-) create mode 100644 components/date-picker/legacy-component.jsx create mode 100644 components/date-picker/legacy-styled.jsx diff --git a/UpgradeGuide.md b/UpgradeGuide.md index f2aaf953d..361722a07 100644 --- a/UpgradeGuide.md +++ b/UpgradeGuide.md @@ -11,6 +11,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. +- `DatePicker` + - `DatePicker` is now wrapped in a single `div` in the DOM, instead of being three separate DOM elements "wrapped" in a `React.Fragment`. - `DatePickerInput` - The `styleOverrides` prop has been removed in favor of Styled System props. Use style props for the input directly on the `DatePickerInput` component, and use style props for the calendar popover on a `DatePickerInput.Popover` child config component. - The `placement` prop has been removed—use `placement` on `DatePickerInput.Popover` instead. diff --git a/components/date-period-picker/component.jsx b/components/date-period-picker/component.jsx index b4db6e04f..7129ae0cd 100644 --- a/components/date-period-picker/component.jsx +++ b/components/date-period-picker/component.jsx @@ -220,17 +220,16 @@ export class DatePeriodPicker extends PureComponent { /> - - - setSelectedDate(dateRange, this.getDatePeriodIndex(dateRange)) - } - validate={validate} - dateFunctions={dateFunctions} - /> - + + setSelectedDate(dateRange, this.getDatePeriodIndex(dateRange)) + } + validate={validate} + dateFunctions={dateFunctions} + padding={3} + /> ); diff --git a/components/date-period-picker/styled.jsx b/components/date-period-picker/styled.jsx index 48fa90806..8bfc8a0a7 100644 --- a/components/date-period-picker/styled.jsx +++ b/components/date-period-picker/styled.jsx @@ -16,10 +16,6 @@ export const Container = styled.div` overflow: hidden; `; -export const DatePickerContainer = styled.div` - padding: ${themeGet('space.3')}; -`; - export const DatePeriod = styled.div` ${themeGet('textStyles.ui.14')} diff --git a/components/date-picker-input/component.jsx b/components/date-picker-input/component.jsx index 8421ae327..ccb10bb4c 100644 --- a/components/date-picker-input/component.jsx +++ b/components/date-picker-input/component.jsx @@ -5,7 +5,7 @@ import { Popover } from '../popover-v6'; import { Calendar as CalendarIcon } from '../icons'; import { Input } from '../input'; import { dateFunctionProps } from '../date-picker/date-function-props'; -import { DatePicker } from '../date-picker/component'; +import { DatePicker } from '../date-picker'; import { UtilityButton } from '../button'; import * as Styled from './styled'; import { DefaultThemeProvider } from '../DefaultThemeProvider'; @@ -139,16 +139,15 @@ export function DatePickerInput({ zIndex={3} {...popoverProps} > - - - + )} diff --git a/components/date-picker-input/legacy-component.jsx b/components/date-picker-input/legacy-component.jsx index 63d0bb41b..03833a86b 100644 --- a/components/date-picker-input/legacy-component.jsx +++ b/components/date-picker-input/legacy-component.jsx @@ -6,7 +6,7 @@ import { PlacementOptionsProps } from '../popover/popper-helpers'; import { Calendar as CalendarIcon } from '../icons'; import { Input } from '../input'; import { dateFunctionProps } from '../date-picker/date-function-props'; -import { DatePicker } from '../date-picker/component'; +import { DatePicker } from '../date-picker'; import * as Styled from './styled'; /** Flexible date picker input (with support for many date parsing libraries) */ @@ -145,16 +145,15 @@ export function DatePickerInput({ }} {...popoverProps} > - - - + )} diff --git a/components/date-picker-input/styled.jsx b/components/date-picker-input/styled.jsx index b32e26837..de3b4a0a7 100644 --- a/components/date-picker-input/styled.jsx +++ b/components/date-picker-input/styled.jsx @@ -26,7 +26,3 @@ export const CalendarButton = styled(UtilityButton).attrs(({ theme, color }) => export const CalendarIconContainer = styled.div` height: 18px; /* matches CalendarIcon */ `; - -export const DateTime = styled.div` - width: 204px; -`; diff --git a/components/date-picker/index.js b/components/date-picker/index.js index 5012bff02..3d959c10f 100644 --- a/components/date-picker/index.js +++ b/components/date-picker/index.js @@ -1 +1,4 @@ export { DatePicker } from './component'; + +/** @todo Remove legacy export upon v6 release. */ +export { DatePicker as LegacyDatePicker } from './legacy-component'; diff --git a/components/date-picker/legacy-component.jsx b/components/date-picker/legacy-component.jsx new file mode 100644 index 000000000..74f74dec3 --- /dev/null +++ b/components/date-picker/legacy-component.jsx @@ -0,0 +1,188 @@ +import React, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { Caret } from '../icons'; +import { colors } from '../shared-styles'; +import { dateFunctionProps } from './date-function-props'; +import * as Styled from './legacy-styled'; +import { CalendarWeek } from './calendar-week'; + +function generateWeek(sunday, { getYear, getMonth, getDate }) { + const week = []; + const year = getYear(sunday); + const month = getMonth(sunday); + const date = getDate(sunday); + + for (let index = 0; index < 7; index++) { + week.push(new Date(year, month, date + index)); + } + + return week; +} + +function generateWeeks(month, dateFunctions) { + const { startOfWeek, startOfMonth, endOfWeek, endOfMonth, isBefore, addWeeks } = dateFunctions; + const firstDay = startOfWeek(startOfMonth(month)); + const lastDay = endOfWeek(endOfMonth(month)); + const weeks = []; + + for ( + let walker = new Date(firstDay.getTime()); + isBefore(walker, lastDay); + walker = addWeeks(walker, 1) + ) { + weeks.push(generateWeek(walker, dateFunctions)); + } + + return weeks; +} + +/** Standard date picker control (with support for many different date parsing libraries) */ +export class DatePicker extends Component { + static propTypes = { + /** Sets the selected date */ + selectedDate: PropTypes.instanceOf(Date), + /** Sets the selected date range (use with asDateRange prop) */ + selectedDateRange: PropTypes.shape({ + start: PropTypes.instanceOf(Date), + end: PropTypes.instanceOf(Date), + }), + /** A callback that retrieves the currently selected date or date range whenever the the selected dates change. */ + setSelectedDate: PropTypes.func.isRequired, + /** Specifies that the component should function as a date range picker */ + asDateRangePicker: PropTypes.bool, + /** Takes a date as a parameter and returns false if that date is invalid */ + validate: PropTypes.func, + dateFunctions: dateFunctionProps, + minDate: PropTypes.instanceOf(Date), + maxDate: PropTypes.instanceOf(Date), + }; + + UNSAFE_componentWillMount() { + const selectedDate = this.props.selectedDate; + const date = selectedDate ? this.props.selectedDate : new Date(); + this.setMonth(date); + } + + setMonth = currentMonth => { + this.setState({ + currentMonth, + weeks: generateWeeks(currentMonth, this.props.dateFunctions), + }); + }; + + setSelectedDate = date => { + const { dateFunctions, selectedDateRange, asDateRangePicker, setSelectedDate } = this.props; + if (asDateRangePicker) { + let newDateRange; + if (selectedDateRange && selectedDateRange.start && !selectedDateRange.end) { + if (dateFunctions.isBefore(selectedDateRange.start, date)) { + newDateRange = { start: selectedDateRange.start, end: date }; + } else { + newDateRange = { start: date, end: selectedDateRange.start }; + } + } else { + newDateRange = { start: date }; + } + setSelectedDate(newDateRange); + } else { + setSelectedDate(date); + } + }; + + decrementMonth = () => { + if (!this.canDecrementMonth(this.state.weeks)) { + return; + } + this.setMonth(this.props.dateFunctions.subMonths(this.state.currentMonth, 1)); + }; + + incrementMonth = () => { + if (!this.canIncrementMonth(this.state.weeks)) { + return; + } + this.setMonth(this.props.dateFunctions.addMonths(this.state.currentMonth, 1)); + }; + + canDecrementMonth = weeks => { + const firstDay = weeks[0][0]; + return !this.props.minDate || this.props.minDate < firstDay; + }; + + canIncrementMonth = weeks => { + const lastDay = weeks[weeks.length - 1][weeks[weeks.length - 1].length - 1]; + return !this.props.maxDate || this.props.maxDate > lastDay; + }; + + render() { + const { + selectedDate, + dateFunctions, + selectedDateRange, + asDateRangePicker, + validate, + minDate, + maxDate, + } = this.props; + const { currentMonth, weeks } = this.state; + + return ( + + + + + + + {dateFunctions.format(currentMonth, 'MMMM yyyy')} + + + + + + S + M + T + W + T + F + S + + + {weeks.map(week => ( + + ))} + + + ); + } +} diff --git a/components/date-picker/legacy-styled.jsx b/components/date-picker/legacy-styled.jsx new file mode 100644 index 000000000..c470e8238 --- /dev/null +++ b/components/date-picker/legacy-styled.jsx @@ -0,0 +1,61 @@ +import styled from 'styled-components'; +import { colors } from '../shared-styles'; + +export const ChangeMonth = styled.button` + display: flex; + justify-content: space-around; + align-items: center; + border: none; + margin: 0; + padding: 0; + height: 34px; + width: 22px; + background: none; + cursor: pointer; + + &:focus { + outline: ${({ visuallyDisabled }) => visuallyDisabled && `${colors.gray22} auto 1px`}; + } +`; + +export const Header = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + white-space: nowrap; + border-bottom: none; + background: ${colors.white}; + color: ${colors.gray66}; + line-height: 32px; + font-weight: bold; +`; + +export const MonthLabel = styled.div` + display: inline-block; +`; + +export const Week = styled.ul` + display: flex; + border-bottom: 1px solid ${colors.gray14}; + padding: 8px 0; + background: ${colors.white}; + list-style: none; + margin: 0; + color: ${colors.gray22}; + font-size: 12px; +`; + +export const WeekDay = styled.li` + flex: 1; + text-align: center; + text-transform: uppercase; +`; + +export const Month = styled.div` + background: ${colors.white}; + font-size: 14px; + + @media (hover: none) { + max-width: 308px; + } +`; diff --git a/components/date-picker/styled.jsx b/components/date-picker/styled.jsx index 374e56bb9..be6843fbf 100644 --- a/components/date-picker/styled.jsx +++ b/components/date-picker/styled.jsx @@ -1,8 +1,9 @@ import styled from 'styled-components'; +import { layout } from 'styled-system'; import { themeGet } from '@styled-system/theme-get'; import { common } from '../../theme/system'; -export const Container = styled.div(common); +export const Container = styled.div(common, layout); export const ChangeMonth = styled.button` display: flex; diff --git a/index-v6.js b/index-v6.js index 6397d67b7..f3e974eda 100644 --- a/index-v6.js +++ b/index-v6.js @@ -1,6 +1,7 @@ export { Accordion } from './components/accordion'; export { Button, SegmentedButtonGroup } from './components/button'; export { Checkbox } from './components/check-box'; +export { DatePicker } from './components/date-picker'; export { DatePickerInput } from './components/date-picker-input'; export { Dropdown } from './components/dropdown'; export { HelpBox } from './components/help-box'; diff --git a/index.js b/index.js index a26cf8ffa..f1a6fba3d 100644 --- a/index.js +++ b/index.js @@ -9,7 +9,7 @@ export { Bootstrap } from './components/bootstrap'; export { LegacyButton as Button, UtilityButton } from './components/button'; export { LegacyCheckbox as Checkbox } from './components/check-box'; export { Collapse } from './components/collapse'; -export { DatePicker } from './components/date-picker'; +export { LegacyDatePicker as DatePicker } from './components/date-picker'; export { LegacyDatePickerInput as DatePickerInput } from './components/date-picker-input'; export { DatePeriodPicker } from './components/date-period-picker'; export { DropZone } from './components/drop-zone'; From ae9bf3e0680ab34c27ac30750c0da7f18553a7b4 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Fri, 8 Jan 2021 17:12:18 -0600 Subject: [PATCH 12/20] Fix Styled.DatePeriod overflowing its container `width: 100%` and `padding` together were causing the component to overflow its container on the right side by an amount equal to the sum of the left and right padding. `box-sizing: border-box` fixes that. --- components/date-period-picker/styled.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/date-period-picker/styled.jsx b/components/date-period-picker/styled.jsx index 8bfc8a0a7..ce321f0ec 100644 --- a/components/date-period-picker/styled.jsx +++ b/components/date-period-picker/styled.jsx @@ -23,6 +23,7 @@ export const DatePeriod = styled.div` background: ${themeGet('colors.datePeriodPicker.background')}; cursor: pointer; width: 100%; + box-sizing: border-box; text-align: initial; &:hover { From ac76cb25cb5e4adb4c6e5b0bd0e44f750abeb1e5 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Fri, 8 Jan 2021 17:51:07 -0600 Subject: [PATCH 13/20] Fix font weights in textStyles --- theme/textStyles.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/theme/textStyles.js b/theme/textStyles.js index 023105694..e0859dba5 100644 --- a/theme/textStyles.js +++ b/theme/textStyles.js @@ -52,59 +52,59 @@ export const textStyles = { '12': { fontSize: '12px', lineHeight: '16px', - fontWeight: fontWeights.normal, + fontWeight: fontWeights.regular, }, '13': { fontSize: '13px', lineHeight: '18px', - fontWeight: fontWeights.normal, + fontWeight: fontWeights.regular, }, '14': { fontSize: '14px', lineHeight: '20px', - fontWeight: fontWeights.normal, + fontWeight: fontWeights.regular, }, '16': { fontSize: '16px', lineHeight: '22px', - fontWeight: fontWeights.normal, + fontWeight: fontWeights.regular, }, '18': { fontSize: '18px', lineHeight: '24px', - fontWeight: fontWeights.normal, + fontWeight: fontWeights.regular, }, }, ui: { '12': { fontSize: '12px', lineHeight: '12px', - fontWeight: fontWeights.normal, + fontWeight: fontWeights.regular, }, '13': { fontSize: '13px', lineHeight: '13px', - fontWeight: fontWeights.normal, + fontWeight: fontWeights.regular, }, '14': { fontSize: '14px', lineHeight: '14px', - fontWeight: fontWeights.normal, + fontWeight: fontWeights.regular, }, '16': { fontSize: '16px', lineHeight: '16px', - fontWeight: fontWeights.normal, + fontWeight: fontWeights.regular, }, '18': { fontSize: '18px', lineHeight: '18px', - fontWeight: fontWeights.normal, + fontWeight: fontWeights.regular, }, '24': { fontSize: '24px', lineHeight: '24px', - fontWeight: fontWeights.normal, + fontWeight: fontWeights.regular, }, }, }; From ce5dca43d9f93dcdd7b957846e622e976acc0dd6 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Fri, 8 Jan 2021 18:10:09 -0600 Subject: [PATCH 14/20] Actually pass style props to DatePicker component --- components/date-picker/component.jsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/date-picker/component.jsx b/components/date-picker/component.jsx index 2539300ae..db5f1a9fe 100644 --- a/components/date-picker/component.jsx +++ b/components/date-picker/component.jsx @@ -1,10 +1,12 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import styledSystemPropTypes from '@styled-system/prop-types'; import { Caret } from '../icons'; import { theme } from '../../theme'; import { DefaultThemeProvider } from '../DefaultThemeProvider'; import { dateFunctionProps } from './date-function-props'; import { common } from '../../theme/system'; +import { filterProps } from '../utils'; import * as Styled from './styled'; import { CalendarWeek } from './calendar-week'; @@ -58,6 +60,7 @@ export class DatePicker extends Component { minDate: PropTypes.instanceOf(Date), maxDate: PropTypes.instanceOf(Date), ...common.propTypes, + ...styledSystemPropTypes.layout, }; UNSAFE_componentWillMount() { @@ -126,11 +129,15 @@ export class DatePicker extends Component { minDate, maxDate, } = this.props; + const { matchingProps: styleProps } = filterProps(this.props, { + ...common.propTypes, + ...styledSystemPropTypes.layout, + }); const { currentMonth, weeks } = this.state; return ( - + Date: Fri, 8 Jan 2021 18:11:47 -0600 Subject: [PATCH 15/20] Add `common` style props to DatePeriodPicker --- components/date-period-picker/component.jsx | 6 +++++- components/date-period-picker/styled.jsx | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/components/date-period-picker/component.jsx b/components/date-period-picker/component.jsx index 7129ae0cd..d57249c66 100644 --- a/components/date-period-picker/component.jsx +++ b/components/date-period-picker/component.jsx @@ -4,6 +4,8 @@ import debounce from 'lodash.debounce'; import { DatePicker } from '../date-picker'; import { Input } from '../input'; import { dateFunctionProps } from '../date-picker/date-function-props'; +import { filterProps } from '../utils'; +import { common } from '../../theme/system'; import { DefaultThemeProvider } from '../DefaultThemeProvider'; import * as Styled from './styled'; @@ -41,6 +43,7 @@ export class DatePeriodPicker extends PureComponent { dateFunctions: dateFunctionProps, /** Debounce value for date inputs. Defaults to 500ms */ debounce: PropTypes.number, + ...common.propTypes, }; static defaultProps = { @@ -182,6 +185,7 @@ export class DatePeriodPicker extends PureComponent { render() { const { setSelectedDate, validate, dateFunctions } = this.props; + const { matchingProps: styleProps } = filterProps(this.props, common.propTypes); const { inputValues: { start, end }, selectedDateRange, @@ -189,7 +193,7 @@ export class DatePeriodPicker extends PureComponent { return ( - + {this.getUniqueDatePeriods().map(({ displayName, dateRange, originalIndex }) => ( Date: Fri, 8 Jan 2021 18:22:34 -0600 Subject: [PATCH 16/20] Add Paragraph style props to DropZone --- components/Paragraph.js | 8 ++++---- components/drop-zone/component.jsx | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/components/Paragraph.js b/components/Paragraph.js index 32e83ceaf..ed2a28853 100644 --- a/components/Paragraph.js +++ b/components/Paragraph.js @@ -9,11 +9,11 @@ export const Paragraph = styled.p` display: block; margin: 0; color: ${themeGet('colors.foregroundPrimary')}; - ${themeGet('textStyles.c.16')}; + ${themeGet('textStyles.c.16')} - ${textStyle}; - ${box}; - ${typography}; + ${textStyle} + ${box} + ${typography} `; Paragraph.defaultProps = { theme }; diff --git a/components/drop-zone/component.jsx b/components/drop-zone/component.jsx index e57b5de65..bf346d33f 100644 --- a/components/drop-zone/component.jsx +++ b/components/drop-zone/component.jsx @@ -13,6 +13,7 @@ export class DropZone extends PureComponent { onDrop: PropTypes.func.isRequired, /** The contents of the drop zone. Children will be rendered into a flex container with align-items set to center. */ children: PropTypes.node, + ...Paragraph.propTypes, }; state = { @@ -48,7 +49,7 @@ export class DropZone extends PureComponent { }; render() { - const { children } = this.props; + const { children, onDrop, ...otherProps } = this.props; const { showHighlight } = this.state; return ( @@ -71,6 +72,7 @@ export class DropZone extends PureComponent { alignItems="center" backgroundColor={showHighlight ? 'blue2' : null} color={showHighlight ? 'blue4' : null} + {...otherProps} > {children} From baa44444a72dae225b5743de956ca36da10242c1 Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Sat, 9 Jan 2021 13:39:56 -0600 Subject: [PATCH 17/20] Add propTypes to Dropdown and Button components I started with just Dropdown, as I was intending to add `common` style props to that family of components. Once I added propTypes for all Dropdown components, I found that each already accepted `common` props via one or another component they wrapped. Adding Dropdown propTypes created the necessity to add propTypes to the Button components, and in typing the `children` prop of SegmentedButtonGroup, I decided I wanted to make sure that that component's children were only `Button`s, so I created a new utility function: `elementOfType`. I used this function in DropdownMenu's propTypes as well. I also added some additional prop types to Popover. --- components/Text.js | 3 +- components/button/Button.js | 35 +++++++++++++++++ components/dropdown/dropdown-children.jsx | 47 +++++++++++++++++++++++ components/dropdown/dropdown-menu.jsx | 28 ++++++++++++++ components/dropdown/dropdown-toggle.jsx | 34 ++++++++++++++++ components/popover-v6/index.jsx | 3 ++ components/utils/index.js | 1 + components/utils/prop-types.js | 15 ++++++++ 8 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 components/utils/prop-types.js diff --git a/components/Text.js b/components/Text.js index a11ca081f..dfdb13fd8 100644 --- a/components/Text.js +++ b/components/Text.js @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import systemPropTypes from '@styled-system/prop-types'; import styled from 'styled-components'; import { textStyle, layout, border } from 'styled-system'; @@ -23,7 +22,7 @@ Text.defaultProps = { theme }; Text.propTypes = { ...common.propTypes, ...typography.propTypes, + ...systemPropTypes.textStyle, ...systemPropTypes.layout, ...systemPropTypes.border, - textStyle: PropTypes.string, }; diff --git a/components/button/Button.js b/components/button/Button.js index 8bd92a481..a7ff503ff 100644 --- a/components/button/Button.js +++ b/components/button/Button.js @@ -1,12 +1,15 @@ import React from 'react'; +import PropTypes from 'prop-types'; import styled, { css } from 'styled-components'; import { variant, layout, flexbox, position, textStyle, border, background } from 'styled-system'; +import styledSystemPropTypes from '@styled-system/prop-types'; import 'focus-visible'; import { Box } from '../Box'; import { common, typography } from '../../theme/system'; import { buttonSizes, buttons } from '../../theme/buttons'; import { theme } from '../../theme'; import { LoadingSpinner } from '../loading-spinner'; +import { elementOfType } from '../utils'; const sizeVariant = variant({ prop: 'size', @@ -113,6 +116,33 @@ const Button = React.forwardRef( ), ); +Button.propTypes = { + children: PropTypes.node, + icon: PropTypes.element, + disabled: PropTypes.bool, + loading: PropTypes.bool, + active: PropTypes.bool, + size: PropTypes.oneOf(['small', 'medium', 'large']), + variant: PropTypes.oneOf([ + 'primary', + 'secondary', + 'minor', + 'transparent', + 'minorTransparent', + 'link', + 'danger', + 'dangerSpecial', + ]), + ...common.propTypes, + ...typography.propTypes, + ...styledSystemPropTypes.textStyle, + ...styledSystemPropTypes.layout, + ...styledSystemPropTypes.flexbox, + ...styledSystemPropTypes.position, + ...styledSystemPropTypes.border, + ...styledSystemPropTypes.background, +}; + const SegmentedButtonGroup = styled(Box).attrs(({ border }) => ({ border: border ?? 1, borderColor: 'button.segmentedButtonGroupBorder', @@ -143,4 +173,9 @@ const SegmentedButtonGroup = styled(Box).attrs(({ border }) => ({ } `; +SegmentedButtonGroup.propTypes = { + children: PropTypes.arrayOf(elementOfType(Button)), + ...Box.propTypes, +}; + export { Button, SegmentedButtonGroup }; diff --git a/components/dropdown/dropdown-children.jsx b/components/dropdown/dropdown-children.jsx index b48ff1e47..08614d82d 100644 --- a/components/dropdown/dropdown-children.jsx +++ b/components/dropdown/dropdown-children.jsx @@ -1,8 +1,10 @@ import React, { useCallback } from 'react'; +import PropTypes from 'prop-types'; import { getConfigChild } from '../utils'; import { Box } from '../Box'; import { Text } from '../Text'; import { Checkbox } from '../check-box'; +import { UtilityButton } from '../button'; import { handledKeys, useDropdownContext } from './utils'; import * as Styled from './styled'; @@ -25,6 +27,12 @@ export function MenuItemIcon({ src, variant, children, ...iconProps }) { MenuItemIcon.defaultProps = { variant: 'icon', }; +MenuItemIcon.propTypes = { + src: PropTypes.string, + variant: PropTypes.oneOf(['thumbnail', 'icon', 'avatar']), + children: PropTypes.node, + ...Box.propTypes, +}; MenuItemIcon.childConfigComponent = 'MenuItemIcon'; export function MenuItemPrimaryText({ hasSecondaryText, children, ...textProps }) { @@ -40,6 +48,11 @@ export function MenuItemPrimaryText({ hasSecondaryText, children, ...textProps } ); } +MenuItemPrimaryText.propTypes = { + hasSecondaryText: PropTypes.bool, + children: PropTypes.node.isRequired, + ...Text.propTypes, +}; MenuItemPrimaryText.childConfigComponent = 'MenuItemPrimaryText'; export function MenuItemSecondaryText({ children, ...textProps }) { @@ -49,6 +62,10 @@ export function MenuItemSecondaryText({ children, ...textProps }) { ); } +MenuItemSecondaryText.propTypes = { + children: PropTypes.node.isRequired, + ...Text.propTypes, +}; MenuItemSecondaryText.childConfigComponent = 'MenuItemSecondaryText'; export const MenuItem = React.forwardRef(function MenuItem( @@ -123,6 +140,17 @@ export const MenuItem = React.forwardRef(function MenuItem( ); }); +MenuItem.propTypes = { + keyboardFocused: PropTypes.bool, + /** @type {(event: React.MouseEvent) => void} */ + onClick: PropTypes.func, + preventDefaultOnClick: PropTypes.bool, + /** @type {(event: React.KeyboardEvent) => void} */ + onKeyDown: PropTypes.func, + disabled: PropTypes.bool, + children: PropTypes.node.isRequired, + ...UtilityButton.propTypes, +}; MenuItem.isFocusableChild = true; export const MenuItemCheckbox = React.forwardRef(function MenuItemCheckbox( @@ -155,6 +183,14 @@ export const MenuItemCheckbox = React.forwardRef(function MenuItemCheckbox( ); }); +MenuItemCheckbox.propTypes = { + isChecked: PropTypes.bool, + /** @type {() => void} */ + onToggle: PropTypes.func, + disabled: PropTypes.bool, + children: PropTypes.node.isRequired, + ...MenuItem.propTypes, +}; MenuItemCheckbox.isFocusableChild = true; export const MenuItemLink = React.forwardRef(function MenuItemLink( @@ -189,6 +225,12 @@ export const MenuItemLink = React.forwardRef(function MenuItemLink( ); }); +MenuItemLink.propTypes = { + disabled: PropTypes.bool, + href: PropTypes.string, + children: PropTypes.node.isRequired, + ...MenuItem.propTypes, +}; MenuItemLink.isFocusableChild = true; export function MenuItemSeparator(hrProps) { @@ -206,6 +248,7 @@ export function MenuItemSeparator(hrProps) { /> ); } +MenuItemSeparator.propTypes = Box.propTypes; export function MenuItemTitle({ children, ...textProps }) { return ( @@ -214,3 +257,7 @@ export function MenuItemTitle({ children, ...textProps }) { ); } +MenuItemTitle.propTypes = { + children: PropTypes.node.isRequired, + ...Text.propTypes, +}; diff --git a/components/dropdown/dropdown-menu.jsx b/components/dropdown/dropdown-menu.jsx index 99861b825..9b030fcd4 100644 --- a/components/dropdown/dropdown-menu.jsx +++ b/components/dropdown/dropdown-menu.jsx @@ -1,6 +1,18 @@ import React, { useCallback } from 'react'; +import PropTypes from 'prop-types'; import { Popover } from '../popover-v6'; import { useDropdownContext, useKeyboardNavigate } from './utils'; +import { elementOfType } from '../utils'; +import { + MenuItem, + MenuItemCheckbox, + MenuItemLink, + MenuItemSeparator, + MenuItemIcon, + MenuItemPrimaryText, + MenuItemSecondaryText, + MenuItemTitle, +} from './dropdown-children'; import * as Styled from './styled'; export function DropdownMenu({ children, ...popoverProps }) { @@ -53,3 +65,19 @@ export function DropdownMenu({ children, ...popoverProps }) { ) ); } + +DropdownMenu.propTypes = { + children: PropTypes.arrayOf( + PropTypes.oneOfType([ + elementOfType(MenuItem), + elementOfType(MenuItemCheckbox), + elementOfType(MenuItemLink), + elementOfType(MenuItemSeparator), + elementOfType(MenuItemIcon), + elementOfType(MenuItemPrimaryText), + elementOfType(MenuItemSecondaryText), + elementOfType(MenuItemTitle), + ]), + ), + ...Popover.propTypes, +}; diff --git a/components/dropdown/dropdown-toggle.jsx b/components/dropdown/dropdown-toggle.jsx index 2d49a29f3..aca38d4ae 100644 --- a/components/dropdown/dropdown-toggle.jsx +++ b/components/dropdown/dropdown-toggle.jsx @@ -1,4 +1,5 @@ import React, { useMemo } from 'react'; +import PropTypes from 'prop-types'; import { getConfigChild } from '../utils'; import { Button, SegmentedButtonGroup } from '../button'; import { useDropdownContext, useKeyboardActivate } from './utils'; @@ -69,6 +70,23 @@ DropdownToggle.defaultProps = { size: 'small', variant: 'primary', }; +DropdownToggle.propTypes = { + hideCarrot: PropTypes.bool, + size: PropTypes.oneOf(['small', 'medium', 'large']), + variant: PropTypes.oneOf([ + 'primary', + 'secondary', + 'minor', + 'transparent', + 'minorTransparent', + 'link', + 'danger', + 'dangerSpecial', + ]), + disabled: PropTypes.bool, + children: PropTypes.node.isRequired, + ...Button.propTypes, +}; export function DropdownActionButton({ defaultSize, @@ -84,3 +102,19 @@ export function DropdownActionButton({ ); } DropdownActionButton.childConfigComponent = 'DropdownActionButton'; +DropdownActionButton.propTypes = { + defaultSize: PropTypes.oneOf(['small', 'medium', 'large']), + defaultVariant: PropTypes.oneOf([ + 'primary', + 'secondary', + 'minor', + 'transparent', + 'minorTransparent', + 'link', + 'danger', + 'dangerSpecial', + ]), + defaultDisabled: PropTypes.bool, + children: PropTypes.element.isRequired, + ...Button.propTypes, +}; diff --git a/components/popover-v6/index.jsx b/components/popover-v6/index.jsx index 7380e434a..8740088e9 100644 --- a/components/popover-v6/index.jsx +++ b/components/popover-v6/index.jsx @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import { usePopper } from 'faithlife-react-popper'; import { useFocusAwayHandler } from '../shared-hooks/use-focus-away-handler'; import { mergeRefs } from '../utils/merge-refs'; +import { box } from '../../theme/system'; import { PopoverContainer, PopoverArrow } from './styled'; export function usePopover(reference, options) { @@ -99,4 +100,6 @@ Popover.propTypes = { hideArrow: PropTypes.bool, /** handler useful for closing popover when clicking away */ onFocusAway: PropTypes.func, + children: PropTypes.node.required, + ...box.propTypes, }; diff --git a/components/utils/index.js b/components/utils/index.js index 4f5ad5393..5232d95c3 100644 --- a/components/utils/index.js +++ b/components/utils/index.js @@ -4,6 +4,7 @@ export { TransitionStatuses, TransitionTimeouts } from './transition-group-utils export { filterProps } from './filter-props'; export { deprecate, deprecateComponent, deprecateProp } from './deprecate'; export { getConfigProps, getConfigChild } from './get-config-props'; +export { elementOfType } from './prop-types'; /** * Chooses the correct variant from a main variant prop and a set of boolean shortcut props. diff --git a/components/utils/prop-types.js b/components/utils/prop-types.js new file mode 100644 index 000000000..abb5a4ae3 --- /dev/null +++ b/components/utils/prop-types.js @@ -0,0 +1,15 @@ +import PropTypes from 'prop-types'; + +/** + * Creates a prop-type-checking function that validates whether the prop value is an element of a + * given component type. + * + * @param {React.ElementType} Component - The component type to check for. + * @returns A PropTypes validation function to use in a component's `propTypes` object. + */ +export function elementOfType(Component) { + // Not very intuitive, but this is the best way to check with PropTypes (see https://github.com/facebook/react/issues/2979) + return PropTypes.shape({ + type: PropTypes.oneOf([Component]), + }); +} From 75825b579bf543d7812e7714036305b73ee6111b Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Sat, 9 Jan 2021 13:49:59 -0600 Subject: [PATCH 18/20] Remove unused semicolons --- components/Text.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/Text.js b/components/Text.js index dfdb13fd8..8d237aaaa 100644 --- a/components/Text.js +++ b/components/Text.js @@ -9,13 +9,13 @@ export const Text = styled.span` display: inline-flex; align-items: baseline; color: ${themeGet('colors.foregroundPrimary')}; - ${themeGet('textStyles.c.16')}; + ${themeGet('textStyles.c.16')} - ${textStyle}; - ${common}; - ${layout}; - ${typography}; - ${border}; + ${textStyle} + ${common} + ${layout} + ${typography} + ${border} `; Text.defaultProps = { theme }; From 12949e37722770d50d3e5eeee7ce81ffb748d65b Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Sat, 9 Jan 2021 14:10:33 -0600 Subject: [PATCH 19/20] Make `type` property required in elementOfType --- components/utils/prop-types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/utils/prop-types.js b/components/utils/prop-types.js index abb5a4ae3..bc165b690 100644 --- a/components/utils/prop-types.js +++ b/components/utils/prop-types.js @@ -10,6 +10,6 @@ import PropTypes from 'prop-types'; export function elementOfType(Component) { // Not very intuitive, but this is the best way to check with PropTypes (see https://github.com/facebook/react/issues/2979) return PropTypes.shape({ - type: PropTypes.oneOf([Component]), + type: PropTypes.oneOf([Component]).isRequired, }); } From 5e50f0d6c3f6b6b24d2998597ecf91efd2d3e2fa Mon Sep 17 00:00:00 2001 From: Ty Mick Date: Sat, 9 Jan 2021 14:11:34 -0600 Subject: [PATCH 20/20] Require children in SegmentedButtonGroup/MenuItem --- components/button/Button.js | 2 +- components/dropdown/dropdown-menu.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/button/Button.js b/components/button/Button.js index a7ff503ff..0699e2584 100644 --- a/components/button/Button.js +++ b/components/button/Button.js @@ -174,7 +174,7 @@ const SegmentedButtonGroup = styled(Box).attrs(({ border }) => ({ `; SegmentedButtonGroup.propTypes = { - children: PropTypes.arrayOf(elementOfType(Button)), + children: PropTypes.arrayOf(elementOfType(Button)).isRequired, ...Box.propTypes, }; diff --git a/components/dropdown/dropdown-menu.jsx b/components/dropdown/dropdown-menu.jsx index 9b030fcd4..10fddbe5c 100644 --- a/components/dropdown/dropdown-menu.jsx +++ b/components/dropdown/dropdown-menu.jsx @@ -78,6 +78,6 @@ DropdownMenu.propTypes = { elementOfType(MenuItemSecondaryText), elementOfType(MenuItemTitle), ]), - ), + ).isRequired, ...Popover.propTypes, };