From e1847b276c6a058ac6b56d7d97de44d1baf74794 Mon Sep 17 00:00:00 2001 From: Haider Alshamma Date: Thu, 14 Nov 2024 15:12:31 -0500 Subject: [PATCH] feat: further refine the API, stories, and documentation --- src/BottomSheet/BottomSheet.story.tsx | 216 ------------------ src/BottomSheet/BottomSheet.styled.tsx | 34 ++- src/BottomSheet/BottomSheet.tsx | 53 +++-- src/BottomSheet/BottomSheetProvider.tsx | 5 +- src/BottomSheet/README.md | 70 +++--- .../stories/BottomSheet.actions.story.tsx | 36 +++ .../stories/BottomSheet.content.story.tsx | 39 ++++ .../stories/BottomSheet.features.story.tsx | 128 +++++++++++ .../stories/BottomSheet.parts.story.tsx | 61 +++++ src/BottomSheet/stories/BottomSheet.story.tsx | 15 ++ src/Breadcrumbs/Breadcrumbs.story.tsx | 4 - src/Button/IconicButton.story.tsx | 3 - src/Checkbox/Checkbox.story.tsx | 3 +- src/Link/Link.story.tsx | 2 - src/Link/Link.tsx | 13 +- src/Radio/Radio.story.tsx | 2 - src/StyledProps/index.ts | 24 ++ src/Toggle/Toggle.story.tsx | 2 +- src/Type/Headings.tsx | 55 +++-- src/Type/Text.tsx | 6 + src/utils/DeprecatedComponent.js | 14 -- src/utils/conditionallyRequiredProp.js | 11 - src/utils/deprecatedProp.js | 11 - src/utils/{ => story}/dashed.tsx | 6 +- src/utils/story/placeholder.tsx | 37 +++ 25 files changed, 472 insertions(+), 378 deletions(-) delete mode 100644 src/BottomSheet/BottomSheet.story.tsx create mode 100644 src/BottomSheet/stories/BottomSheet.actions.story.tsx create mode 100644 src/BottomSheet/stories/BottomSheet.content.story.tsx create mode 100644 src/BottomSheet/stories/BottomSheet.features.story.tsx create mode 100644 src/BottomSheet/stories/BottomSheet.parts.story.tsx create mode 100644 src/BottomSheet/stories/BottomSheet.story.tsx delete mode 100644 src/utils/DeprecatedComponent.js delete mode 100644 src/utils/conditionallyRequiredProp.js delete mode 100644 src/utils/deprecatedProp.js rename src/utils/{ => story}/dashed.tsx (81%) create mode 100644 src/utils/story/placeholder.tsx diff --git a/src/BottomSheet/BottomSheet.story.tsx b/src/BottomSheet/BottomSheet.story.tsx deleted file mode 100644 index ea2cc1e48..000000000 --- a/src/BottomSheet/BottomSheet.story.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import React from "react"; -import { Box } from "../Box"; -import { Button, PrimaryButton, QuietButton } from "../Button"; -import { Flex } from "../Flex"; -import { Link } from "../Link"; -import { toast, ToastContainer } from "../ToastContainer"; -import BottomSheet from "./BottomSheet"; -import { BottomSheetParts } from "./BottomSheet.parts"; - -export default { - title: "Touch Components/BottomSheet", -}; - -const contentPlaceholder = ; - -export const WithTitle = () => { - const [isOpen, setIsOpen] = React.useState(false); - - function open() { - setIsOpen(true); - } - - function close() { - setIsOpen(false); - } - - return ( - - - - {contentPlaceholder} - - - ); -}; - -export const WithHelpText = () => { - const [isOpen, setIsOpen] = React.useState(false); - - function open() { - setIsOpen(true); - } - - function close() { - setIsOpen(false); - } - - return ( - - - - {contentPlaceholder} - - - ); -}; - -export const WithClickableOverlay = () => { - const [isOpen, setIsOpen] = React.useState(false); - - function open() { - setIsOpen(true); - } - - function close() { - setIsOpen(false); - } - - return ( - - - - {contentPlaceholder} - - - ); -}; - -export const WithCustomWidths = () => { - const [isOpen, setIsOpen] = React.useState(false); - - function open() { - setIsOpen(true); - } - - function close() { - setIsOpen(false); - } - - return ( - - - - {contentPlaceholder} - - - ); -}; - -export const WithAllProps = () => { - const [isOpen, setIsOpen] = React.useState(false); - - function open() { - setIsOpen(true); - } - - function close() { - setIsOpen(false); - } - - return ( - <> - - - - - Help text can further explain the purpose and content of the BottomSheet{" "} - Learn more - - } - closeActionLabel="Dismiss" - primaryAction={({ onClose }) => ( - { - toast.informative("Primary action clicked"); - onClose(); - }} - > - Primary action - - )} - secondaryAction={() => Secondary action} - isOpen={isOpen} - onClose={close} - closeOnOverlayClick - > - {contentPlaceholder} - - - - ); -}; - -export const WithParts = () => { - const [isOpen, setIsOpen] = React.useState(false); - - function open() { - setIsOpen(true); - } - - function close() { - setIsOpen(false); - } - - return ( - - - - - - - - - - Title - - <> - Help text can further explains the purpose and content of the BottomSheet{" "} - Learn more - - - - - {contentPlaceholder} - - - - - - - Secondary action - Primary action - - - - - - - - - ); -}; diff --git a/src/BottomSheet/BottomSheet.styled.tsx b/src/BottomSheet/BottomSheet.styled.tsx index e4dbcb125..75b168373 100644 --- a/src/BottomSheet/BottomSheet.styled.tsx +++ b/src/BottomSheet/BottomSheet.styled.tsx @@ -7,9 +7,10 @@ import type { AnimationProps } from "framer-motion"; import { motion } from "framer-motion"; import { transparentize } from "polished"; import styled from "styled-components"; -import { height, layout, maxHeight, maxWidth, space, width } from "styled-system"; +import { compose, height, layout, maxHeight, maxWidth, space, styleFn, width } from "styled-system"; import type { HeightProps, LayoutProps, MaxHeightProps, MaxWidthProps, SpaceProps, WidthProps } from "styled-system"; import { Heading2, Text } from "../Type"; +import { excludeStyledProps } from "../StyledProps"; const Overlay = styled(motion(ReachDialogOverlay))(({ theme }) => ({ position: "fixed", @@ -21,16 +22,20 @@ const Overlay = styled(motion(ReachDialogOverlay))(({ theme }) => ({ })); interface SheetProps - extends WidthProps, + extends DialogContentProps, + AnimationProps, + WidthProps, MaxWidthProps, HeightProps, MaxHeightProps, - DialogContentProps, SpaceProps, - LayoutProps, - AnimationProps {} + LayoutProps {} -const Sheet = styled(motion(ReachDialogContent))( +const styleFns = [width, maxWidth, height, maxHeight, space, layout]; + +const Sheet = styled(motion(ReachDialogContent)).withConfig({ + shouldForwardProp: excludeStyledProps(...styleFns), +})( ({ theme }) => ({ ":focus": { outline: "none", @@ -51,27 +56,14 @@ const Sheet = styled(motion(ReachDialogContent))( WebkitFontSmoothing: "antialiased", WebkitTapHighlightColor: "transparent", MozOsxFontSmoothing: "grayscale", - position: "relative", overflow: "hidden", display: "flex", flexDirection: "column", background: "white", - width: "100%", - maxHeight: `calc(100dvh - ${theme.space.x7})`, boxShadow: theme.shadows.large, - - [`@media (min-width: ${theme.breakpoints.small})`]: { - maxWidth: `calc(100% - ${theme.space.x8})`, - maxHeight: "85.4dvh", // Golden Ratio - }, }), - width, - maxWidth, - height, - maxHeight, - space, - layout + compose(...styleFns) ); const ContentContainer = styled.div((_) => ({ @@ -99,7 +91,7 @@ const Footer = styled.div(({ theme }) => ({ })); const Header = styled.div(({ theme }) => ({ - textAlign: "center", + textAlign: "left", paddingTop: theme.space.x3, paddingLeft: theme.space.x3, paddingRight: theme.space.x3, diff --git a/src/BottomSheet/BottomSheet.tsx b/src/BottomSheet/BottomSheet.tsx index 18cbbb817..e236ec321 100644 --- a/src/BottomSheet/BottomSheet.tsx +++ b/src/BottomSheet/BottomSheet.tsx @@ -1,23 +1,24 @@ import React from "react"; import { useTranslation } from "react-i18next"; +import { useTheme } from "styled-components"; import { WidthProps } from "styled-system"; import { Box } from "../Box"; -import { Button } from "../Button"; +import { QuietButton } from "../Button"; import { Flex } from "../Flex"; import { noop } from "../utils/noop"; import { BottomSheetParts } from "./BottomSheet.parts"; interface Props { - isOpen: boolean; + isOpen?: boolean; "aria-label"?: string; onClose?: () => void; title?: string; helpText?: React.ReactNode; - closeActionLabel?: string; + closeButtonLabel?: string; + hideCloseButton?: boolean; secondaryAction?: (props: { onClose: () => void }) => React.ReactElement; primaryAction?: (props: { onClose: () => void }) => React.ReactElement; - onCloseEnd?: () => void; - closeOnOverlayClick?: boolean; + disableCloseOnOverlayClick?: boolean; sheetWidth?: WidthProps["width"]; contentWidth?: WidthProps["width"]; children?: React.ReactNode; @@ -26,40 +27,54 @@ interface Props { export default function BottomSheet({ title, helpText, - closeActionLabel: closeButtonLabel, - secondaryAction: secondaryButton, - primaryAction: primaryButton, - isOpen, + closeButtonLabel, + secondaryAction, + primaryAction, + isOpen = false, onClose = noop, - closeOnOverlayClick, - sheetWidth, + sheetWidth = "100%", contentWidth = { small: "100%", medium: 704 }, + disableCloseOnOverlayClick = false, + hideCloseButton = false, children, ...props }: Props) { const { t } = useTranslation(); + const theme = useTheme(); + closeButtonLabel ||= t("close"); + const closeOnClick = !disableCloseOnOverlayClick; return ( - - + + {title && {title}} - {helpText && {helpText}} + {helpText && + (typeof helpText === "string" ? ( + {helpText} + ) : ( + helpText + ))} {children} - - - - {secondaryButton && secondaryButton({ onClose })} - {primaryButton && primaryButton({ onClose })} + + {!hideCloseButton && {closeButtonLabel}} + + {secondaryAction && secondaryAction({ onClose })} + {primaryAction && primaryAction({ onClose })} diff --git a/src/BottomSheet/BottomSheetProvider.tsx b/src/BottomSheet/BottomSheetProvider.tsx index 55fcb75e7..32195321a 100644 --- a/src/BottomSheet/BottomSheetProvider.tsx +++ b/src/BottomSheet/BottomSheetProvider.tsx @@ -1,5 +1,6 @@ import React from "react"; import { createContext, useContext } from "react"; +import { noop } from "../utils/noop"; interface BottomSheetContextType { isOpen: boolean; @@ -17,8 +18,8 @@ function useBottomSheet() { } function BottomSheetProvider({ - isOpen, - onClose, + isOpen = false, + onClose = noop, children, }: { isOpen: boolean; diff --git a/src/BottomSheet/README.md b/src/BottomSheet/README.md index 3fca67bcf..50ba7bfd0 100644 --- a/src/BottomSheet/README.md +++ b/src/BottomSheet/README.md @@ -1,4 +1,4 @@ -# BottomSheet Component +# BottomSheet The BottomSheet component is a modal interface element that slides up from the bottom of the screen, providing additional content or actions while maintaining context with the main view. @@ -12,7 +12,7 @@ The BottomSheet is ideal for: ## Basic Usage ```tsx -import { BottomSheet } from '@/components'; +import { BottomSheet } from '@nulogy/components'; function MyComponent() { const [isOpen, setIsOpen] = useState(false); @@ -31,47 +31,49 @@ function MyComponent() { ## Props -| Prop | Type | Description | -|------|------|-------------| -| `isOpen` | boolean | Controls the visibility of the bottom sheet | -| `onClose` | function | Callback function when the sheet is closed using either the close action or an overlay| -| `title` | string | Main heading of the sheet. Used as an `aria-label` when it is not passed| -| `helpText` | ReactNode | Optional explanatory text below the title | -| `closeOnOverlayClick` | boolean | Enables closing the sheet by clicking the overlay | -| `aria-label` | string | Accessibility label for the sheet | -| `sheetWidth` | ResponsiveWidth | Responsive width configuration | -| `contentWidth` | ResponsiveWidth | Width of the content area | -| `closeActionLabel` | string | Label for close action. Default: `Close` | -| `primaryAction` | Function | Renders primary action button | -| `secondaryAction` | Function | Renders secondary action button | +| Prop | Type | Description | Default | +|------|------|-------------|---------| +| `isOpen` | boolean | Controls the visibility of the bottom sheet | `false` | +| `onClose` | function | Callback function when the sheet is closed using either the close action or an overlay | `noop` | +| `title` | string | Main heading of the sheet. Used as an `aria-label` when an `aria-label` it is not passed | - | +| `helpText` | ReactNode | Optional explanatory content below the title | - | +| `disableCloseOnOverlayClick` | boolean | Enables closing the sheet by clicking the overlay | `false` | +| `hideCloseButton` | boolean | Hides the close button | `false` | +| `aria-label` | string | Accessibility label for the sheet | - | +| `sheetWidth` | ResponsiveWidth | Controls the width of the entire sheet | `"100%"` | +| `contentWidth` | ResponsiveWidth | Controls the width of the content container inside the sheet | `{ small: "100%", medium: 704 }` | +| `closeButtonLabel` | string | Label for close action. Internationalized `Close` is used if no value is supplied | `t("close")` | +| `primaryAction` | Function | Renders primary action | - | +| `secondaryAction` | Function | Renders secondary action | - | +| `children` | ReactNode | Content to be displayed inside the sheet | - | ## Compositional API -For more complex use cases, the BottomSheet can be composed using parts: +For more complex use cases, the BottomSheet can be composed using parts. You can override the styles, or replace the default parts with custom components. ```tsx -import { BottomSheetParts } from '@/components'; - - - - - - - - - +import { BottomSheetParts as BottomSheet } from '@nulogy/components'; + + + + + + + + + {/* Content */} - + {/* Actions */} - - - - - + + + + + ``` ## Accessibility -- Automatically focuses on the first focusable element inside the Sheet. The focus is returned to the last focused element when the sheet is closed. +- Automatically focuses on the first focusable element inside the Sheet, the focus is then returned to the last focused element when the sheet is closed - Always provide a clear `title` or `aria-label` that describes the sheet's purpose ## Best Practices @@ -80,7 +82,7 @@ import { BottomSheetParts } from '@/components'; 2. Consider mobile viewports when setting widths 3. Implement proper error handling and loading states 4. Use appropriate action buttons that clearly communicate their purpose -5. Consider implementing `closeOnOverlayClick` for better user experience +5. Only toggle `disableCloseOnOverlayClick` and `hideCloseAction` on if there is an alternate way to close the sheet ## Technical Considerations diff --git a/src/BottomSheet/stories/BottomSheet.actions.story.tsx b/src/BottomSheet/stories/BottomSheet.actions.story.tsx new file mode 100644 index 000000000..923e180a0 --- /dev/null +++ b/src/BottomSheet/stories/BottomSheet.actions.story.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { PrimaryButton, QuietButton } from "../../Button"; +import { Placeholder } from "../../utils/story/placeholder"; +import BottomSheet from "../BottomSheet"; + +export default { + title: "Components/BottomSheet/Actions", +}; + +export const WithCTAButton = () => { + return ( + Submit} + > + + + ); +}; + +export const WithButtons = () => { + return ( + Next} + secondaryAction={() => Previous} + > + + + ); +}; diff --git a/src/BottomSheet/stories/BottomSheet.content.story.tsx b/src/BottomSheet/stories/BottomSheet.content.story.tsx new file mode 100644 index 000000000..c4e0fdb4b --- /dev/null +++ b/src/BottomSheet/stories/BottomSheet.content.story.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { Link } from "../../Link"; +import { Text } from "../../Type"; +import { Placeholder } from "../../utils/story/placeholder"; +import BottomSheet from "../BottomSheet"; + +export default { + title: "Components/BottomSheet/Content", +}; + +export const WithHelpText = () => { + return ( + + + + ); +}; + +export const WithHelpContent = () => { + return ( + + Update your profile information to access exclusive features. Learn more + + } + isOpen + > + + + ); +}; diff --git a/src/BottomSheet/stories/BottomSheet.features.story.tsx b/src/BottomSheet/stories/BottomSheet.features.story.tsx new file mode 100644 index 000000000..f5c45c6e5 --- /dev/null +++ b/src/BottomSheet/stories/BottomSheet.features.story.tsx @@ -0,0 +1,128 @@ +import React from "react"; +import { Box } from "../../Box"; +import { Button, IconicButton } from "../../Button"; +import { Flex } from "../../Flex"; +import { Form, FormSection } from "../../Form"; +import { Icon } from "../../Icon"; +import { Input } from "../../Input"; +import { Link } from "../../Link"; +import { toast, ToastContainer } from "../../ToastContainer"; +import { Text } from "../../Type"; +import { Placeholder } from "../../utils/story/placeholder"; +import BottomSheet from "../BottomSheet"; + +export default { + title: "Components/BottomSheet/Features", +}; + +export const WithCustomWidths = () => { + return ( + + + + ); +}; + +export const DisableCloseOnOverlayClick = () => { + const [isOpen, setIsOpen] = React.useState(true); + + function open() { + setIsOpen(true); + } + + function close() { + setIsOpen(false); + } + + return ( + + + + + + + ); +}; + +export const AdvancedUsage = () => { + const [isOpen, setIsOpen] = React.useState(true); + + function open() { + setIsOpen(true); + } + + function close() { + setIsOpen(false); + } + + return ( + <> + + + + + + Not everything demonstrated in this story is recommended as best practice usage. + + } + primaryAction={({ onClose }) => ( + { + toast.informative("Primary action clicked"); + onClose(); + }} + > + Get started + + )} + secondaryAction={() => ( + + Need more information? Ask for help + + )} + closeButtonLabel="Dismiss" + isOpen={isOpen} + onClose={close} + sheetWidth={{ small: "100%" }} + contentWidth="100%" + > +
+ + + + + + + + + +
+
+
+ + ); +}; diff --git a/src/BottomSheet/stories/BottomSheet.parts.story.tsx b/src/BottomSheet/stories/BottomSheet.parts.story.tsx new file mode 100644 index 000000000..30108a119 --- /dev/null +++ b/src/BottomSheet/stories/BottomSheet.parts.story.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import { Box } from "../../Box"; +import { Button, QuietButton, PrimaryButton } from "../../Button"; +import { Flex } from "../../Flex"; +import { Link } from "../../Link"; +import { Placeholder } from "../../utils/story/placeholder"; +import { BottomSheetParts as BottomSheet } from "../BottomSheet.parts"; + +export default { + title: "Components/BottomSheet/Parts", +}; + +export const RenderedUsingCompositionalAPI = () => { + const [isOpen, setIsOpen] = React.useState(true); + + function open() { + setIsOpen(true); + } + + function close() { + setIsOpen(false); + } + + return ( + + + + + + + + + + Title + + <> + Help text can further explains the purpose and content of the BottomSheet{" "} + Learn more + + + + + + + + + + + + Secondary action + Primary action + + + + + + + + + ); +}; diff --git a/src/BottomSheet/stories/BottomSheet.story.tsx b/src/BottomSheet/stories/BottomSheet.story.tsx new file mode 100644 index 000000000..bc806acc1 --- /dev/null +++ b/src/BottomSheet/stories/BottomSheet.story.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { Placeholder } from "../../utils/story/placeholder"; +import BottomSheet from "../BottomSheet"; + +export default { + title: "Components/BottomSheet", +}; + +export const BasicUsage = () => { + return ( + + + + ); +}; diff --git a/src/Breadcrumbs/Breadcrumbs.story.tsx b/src/Breadcrumbs/Breadcrumbs.story.tsx index 6eaa481d5..9972453c4 100644 --- a/src/Breadcrumbs/Breadcrumbs.story.tsx +++ b/src/Breadcrumbs/Breadcrumbs.story.tsx @@ -2,8 +2,6 @@ import React from "react"; import { BrowserRouter, Link as ReactRouterLink } from "react-router-dom"; import { Link } from "../Link"; import { Text } from "../Type"; -import { Flex } from "../Flex"; -import dashed from "../utils/dashed"; import { Breadcrumbs } from "./index"; export default { @@ -22,8 +20,6 @@ export const _Breadcrumbs = () => ( ); -const DashedBreadcrumbs = dashed(Breadcrumbs); - export const WithoutLink = () => ( Home diff --git a/src/Button/IconicButton.story.tsx b/src/Button/IconicButton.story.tsx index ef28472fa..0c28e6b28 100644 --- a/src/Button/IconicButton.story.tsx +++ b/src/Button/IconicButton.story.tsx @@ -3,7 +3,6 @@ import { IconicButton } from "../index"; import { Flex } from "../Flex"; import { StatusIndicator } from "../StatusIndicator"; import { Box } from "../Box"; -import dashed from "../utils/dashed"; export default { title: "Components/IconicButton", @@ -125,8 +124,6 @@ export const WithACustomFontSize = () => ( ); -const DashedIconicButton = dashed(IconicButton); - WithACustomFontSize.story = { name: "with a custom font size", }; diff --git a/src/Checkbox/Checkbox.story.tsx b/src/Checkbox/Checkbox.story.tsx index eae79c952..0723cb6c6 100644 --- a/src/Checkbox/Checkbox.story.tsx +++ b/src/Checkbox/Checkbox.story.tsx @@ -1,6 +1,5 @@ import React, { useRef } from "react"; -import { Checkbox, Button, Flex } from "../index"; -import dashed from "../utils/dashed"; +import { Checkbox, Button } from "../index"; type CheckboxState = { checkbox1: boolean; diff --git a/src/Link/Link.story.tsx b/src/Link/Link.story.tsx index d0fdc4de9..e61b7bc23 100644 --- a/src/Link/Link.story.tsx +++ b/src/Link/Link.story.tsx @@ -1,8 +1,6 @@ import React from "react"; import { BrowserRouter, Link as ReactRouterLink } from "react-router-dom"; import { Link } from "../index"; -import { Flex } from "../Flex"; -import dashed from "../utils/dashed"; export default { title: "Components/Link", diff --git a/src/Link/Link.tsx b/src/Link/Link.tsx index c2d84e6a6..e26489f1a 100644 --- a/src/Link/Link.tsx +++ b/src/Link/Link.tsx @@ -5,7 +5,7 @@ import { variant } from "styled-system"; import React from "react"; import { DefaultNDSThemeType } from "../theme.type"; import { addStyledProps, StyledProps } from "../StyledProps"; -import { ComponentVariant, useComponentVariant } from "../NDSProvider/ComponentVariantContext"; +import { ComponentVariant } from "../NDSProvider/ComponentVariantContext"; export type LinkProps = React.ComponentPropsWithRef<"a"> & Partial & { @@ -35,8 +35,8 @@ function getColor(props: StyledLinkProps) { const getHoverColor = (props: StyledLinkProps) => (props.hover ? getColor(props) : darken("0.1", getColor(props))); -const StyledLink = styled.a( - ({ underline, as, ...props }) => ({ +const Link = styled.a( + ({ underline = true, as, ...props }) => ({ ...resetButtonStyles, padding: as === "button" ? "0" : undefined, textDecoration: underline ? "underline" : "none", @@ -50,16 +50,9 @@ const StyledLink = styled.a( variant({ variants: { touch: {}, - medium: {}, }, }), addStyledProps ); -const Link = React.forwardRef(({ variant, underline = true, ...props }, ref) => { - const componentVariant = useComponentVariant(variant); - - return ; -}); - export default Link; diff --git a/src/Radio/Radio.story.tsx b/src/Radio/Radio.story.tsx index 57ee62dbf..372646ce6 100644 --- a/src/Radio/Radio.story.tsx +++ b/src/Radio/Radio.story.tsx @@ -1,8 +1,6 @@ import React, { useRef } from "react"; import { action } from "@storybook/addon-actions"; import { Radio, Button } from "../index"; -import { Flex } from "../Flex"; -import dashed from "../utils/dashed"; export default { title: "Components/Radio", diff --git a/src/StyledProps/index.ts b/src/StyledProps/index.ts index 686c74a86..fed74a5b9 100644 --- a/src/StyledProps/index.ts +++ b/src/StyledProps/index.ts @@ -1,3 +1,4 @@ +import { useTheme } from "styled-components"; import { space, margin, @@ -74,6 +75,8 @@ import { backgroundRepeat, backgroundPosition, backgroundImage, + styleFn, + variant as styledSystemVariant, } from "styled-system"; import type { @@ -152,6 +155,26 @@ import type { LayoutProps, } from "styled-system"; import { CSSProperties } from "react"; +import { useComponentVariant } from "../NDSProvider/ComponentVariantContext"; + +export function getStyledPropNames(...styleFns: styleFn[]): string[] { + return styleFns.reduce( + (acc: string[], fn: styleFn) => (fn.propNames ? acc.concat(fn.propNames) : acc), + [] as string[] + ); +} + +export const excludeStyledProps = + (...styleFns: styleFn[]) => + (prop: string | number): boolean => + !getStyledPropNames(...styleFns).includes(String(prop)); + +export const variant: typeof styledSystemVariant = (variants) => () => { + const componentVariant = useComponentVariant(); + const theme = useTheme(); + + return styledSystemVariant(variants)({ theme, variant: componentVariant }); +}; export const addStyledProps = compose( // After @@ -237,6 +260,7 @@ export const addStyledProps = compose( cursor: true, }) ); + interface TransitionProps { transition?: CSSProperties["transition"]; transitionDelay?: CSSProperties["transitionDelay"]; diff --git a/src/Toggle/Toggle.story.tsx b/src/Toggle/Toggle.story.tsx index 162b84e4e..c64a8d476 100644 --- a/src/Toggle/Toggle.story.tsx +++ b/src/Toggle/Toggle.story.tsx @@ -2,7 +2,7 @@ import React, { useRef } from "react"; import { action } from "@storybook/addon-actions"; import { boolean } from "@storybook/addon-knobs"; import { Toggle, Button, Box } from "../index"; -import dashed from "../utils/dashed"; +import { dashed } from "../utils/story/dashed"; const DashedBox = dashed(Box); diff --git a/src/Type/Headings.tsx b/src/Type/Headings.tsx index bb6d1f6a6..08f484b73 100644 --- a/src/Type/Headings.tsx +++ b/src/Type/Headings.tsx @@ -1,24 +1,7 @@ -import { variant } from "styled-system"; import styled from "styled-components"; -import { DefaultNDSThemeType } from "../theme.type"; -import { addStyledProps } from "../StyledProps"; -import { useComponentVariant } from "../NDSProvider/ComponentVariantContext"; +import { addStyledProps, variant } from "../StyledProps"; import Text, { TextProps } from "./Text"; -const useVariantStyles = ({ theme }: { theme: DefaultNDSThemeType }) => { - const componentVariant = useComponentVariant(); - - return variant({ - variants: { - touch: { - fontWeight: theme.fontWeights.bold, - marginBottom: theme.space.none, - }, - }, - prop: "variant", - })({ theme, variant: componentVariant }); -}; - export const Heading1 = styled(Text).attrs(() => ({ as: "h1", }))( @@ -29,7 +12,14 @@ export const Heading1 = styled(Text).attrs(() => ({ marginTop: 0, marginBottom: theme.space.x6, }), - useVariantStyles, + variant({ + variants: { + touch: { + fontWeight: "medium", + marginBottom: "none", + }, + }, + }), addStyledProps ); @@ -43,7 +33,14 @@ export const Heading2 = styled(Text).attrs(() => ({ marginTop: 0, marginBottom: theme.space.x2, }), - useVariantStyles, + variant({ + variants: { + touch: { + fontWeight: "medium", + marginBottom: "none", + }, + }, + }), addStyledProps ); @@ -57,7 +54,14 @@ export const Heading3 = styled(Text).attrs(() => ({ marginTop: 0, marginBottom: theme.space.x1, }), - useVariantStyles, + variant({ + variants: { + touch: { + fontWeight: "medium", + marginBottom: "none", + }, + }, + }), addStyledProps ); @@ -71,6 +75,13 @@ export const Heading4 = styled(Text).attrs(() => ({ marginTop: 0, marginBottom: theme.space.x1, }), - useVariantStyles, + variant({ + variants: { + touch: { + fontWeight: "medium", + marginBottom: "none", + }, + }, + }), addStyledProps ); diff --git a/src/Type/Text.tsx b/src/Type/Text.tsx index f663a1785..e8b04e01d 100644 --- a/src/Type/Text.tsx +++ b/src/Type/Text.tsx @@ -1,4 +1,5 @@ import styled from "styled-components"; +import { Link } from "../Link"; import { ComponentVariant } from "../NDSProvider/ComponentVariantContext"; import { addStyledProps, StyledProps } from "../StyledProps"; @@ -32,6 +33,11 @@ const Text = styled.p( lineHeight: theme.lineHeights.base, opacity: disabled ? "0.7" : undefined, display: inline ? "inline" : undefined, + + [`${Link}`]: { + fontSize: "inherit", + lineHeight: "inherit", + }, }), addStyledProps ); diff --git a/src/utils/DeprecatedComponent.js b/src/utils/DeprecatedComponent.js deleted file mode 100644 index 349b5bfd8..000000000 --- a/src/utils/DeprecatedComponent.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable no-console */ -import React, { useEffect } from "react"; - -export const Deprecated = - (Component, message = "") => - (props) => { - useEffect(() => { - if (process.env.NODE_ENV === "development") { - console.error("Deprecated NDS Component:", message); - } - }); - - return ; - }; diff --git a/src/utils/conditionallyRequiredProp.js b/src/utils/conditionallyRequiredProp.js deleted file mode 100644 index 94c14ab94..000000000 --- a/src/utils/conditionallyRequiredProp.js +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable no-console */ -export const conditionallyRequiredProp = (propType, dependsOnPropName) => { - return (props, propName, componentName, ...rest) => { - if (props[propName] === undefined && props[dependsOnPropName] !== undefined) { - const message = `NDS Warning: "${propName}" prop of "${componentName}" is required when ${dependsOnPropName} prop is set`; - console.error(message); - } - - return propType(props, propName, componentName, ...rest); - }; -}; diff --git a/src/utils/deprecatedProp.js b/src/utils/deprecatedProp.js deleted file mode 100644 index 3558d6b7e..000000000 --- a/src/utils/deprecatedProp.js +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable no-console */ -export const deprecatedProp = (propType, newPropName) => { - return (props, propName, componentName, ...rest) => { - if (props[propName] != null) { - const message = `NDS Warning: "${propName}" prop of "${componentName}" has been deprecated.\n Please use the "${newPropName}" prop instead. If you need assistance upgrading please message #design-system`; - console.error(message); - } - - return propType(props, propName, componentName, ...rest); - }; -}; diff --git a/src/utils/dashed.tsx b/src/utils/story/dashed.tsx similarity index 81% rename from src/utils/dashed.tsx rename to src/utils/story/dashed.tsx index 9ccb1035d..208b6def2 100644 --- a/src/utils/dashed.tsx +++ b/src/utils/story/dashed.tsx @@ -1,12 +1,12 @@ import styled, { StyledComponent } from "styled-components"; import { ComponentType } from "react"; -import { DefaultNDSThemeType } from "../theme.type"; +import { DefaultNDSThemeType } from "../../theme.type"; /** * A styled utility that adds a dashed border around a component * to highlight its boundaries. To be used in Storybook exclusively. */ -const dashed =

( +export const dashed =

( component: ComponentType

): StyledComponent, DefaultNDSThemeType> => styled(component)` @@ -15,5 +15,3 @@ const dashed =

( border-style: dashed; border-color: ${({ theme }) => theme.colors.lightBlue}; `; - -export default dashed; diff --git a/src/utils/story/placeholder.tsx b/src/utils/story/placeholder.tsx new file mode 100644 index 000000000..7ab4dfc01 --- /dev/null +++ b/src/utils/story/placeholder.tsx @@ -0,0 +1,37 @@ +import { transparentize } from "polished"; +import React from "react"; +import styled from "styled-components"; + +const Contianer = styled.div(({ theme }) => ({ + overflow: "hidden", + position: "relative", + borderRadius: "0.25rem", + borderWidth: "1px", + borderColor: theme.colors.grey, + borderStyle: "dashed", + height: "16rem", + opacity: "0.75", +})); + +const Svg = styled.svg(({ theme }) => ({ + position: "absolute", + inset: 0, + width: "100%", + height: "100%", + stroke: transparentize(0.85, theme.colors.darkBlue), +})); + +export function Placeholder() { + return ( + + + + + + + + + + + ); +}