diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index bbe882a8..651e2440 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useLayoutEffect } from 'react'; import { I18nProvider } from 'react-aria-components'; import { Preview } from '@storybook/react'; import { themes } from '@storybook/theming'; @@ -44,12 +44,14 @@ export const withTheme = (Story, context) => { const { theme } = context.globals; const { Colors, font } = THEMES[theme]; + useLayoutEffect(() => { + document.body.style.fontFamily = font; + }, []); + return ( <> -
- -
+ ); }; @@ -106,13 +108,13 @@ export const preview: Preview = { container: props => { const scheme = useDarkMode() ? DarkTheme : LightTheme; const globals = props.context.store.globals.get(); - const { Colors, font } = THEMES[globals.theme]; + const { Colors } = THEMES[globals.theme]; return ( -
+ <> -
+ ); }, toc: { diff --git a/.stylelintrc b/.stylelintrc index 9db63eb9..e307d119 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -10,7 +10,7 @@ "rules": { "declaration-empty-line-before": null, "declaration-property-unit-whitelist": { - "/.*/": ["rem", "deg", "fr", "ms", "%", "px"] + "/.*/": ["rem", "deg", "fr", "ms", "%", "px", "vw"] }, "declaration-property-value-blacklist": { "/.*/": ["(\\d+[1]+px|[^1]+px)"] diff --git a/src/components/experimental/Backdrop/Backdrop.tsx b/src/components/experimental/Backdrop/Backdrop.tsx new file mode 100644 index 00000000..72fb9616 --- /dev/null +++ b/src/components/experimental/Backdrop/Backdrop.tsx @@ -0,0 +1,38 @@ +import styled from 'styled-components'; +import { ModalOverlayProps, ModalOverlay } from 'react-aria-components'; +import { getSemanticHslValue } from '../../../essentials/experimental'; +import { Elevation } from '../../../essentials'; + +type BackdropProps = ModalOverlayProps; + +const Backdrop = styled(ModalOverlay)` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: var(--visual-viewport-height); + background: hsla(${getSemanticHslValue('on-surface')}, 60%); + display: flex; + align-items: center; + justify-content: center; + z-index: ${Elevation.DIMMING}; + + &[data-entering] { + animation: backdrop-fade 200ms; + } + + &[data-exiting] { + animation: backdrop-fade 150ms reverse ease-in; + } + + @keyframes backdrop-fade { + from { + opacity: 0; + } + to { + opacity: 1; + } + } +`; + +export { Backdrop, BackdropProps }; diff --git a/src/components/experimental/Dialog/Dialog.tsx b/src/components/experimental/Dialog/Dialog.tsx new file mode 100644 index 00000000..1c28d029 --- /dev/null +++ b/src/components/experimental/Dialog/Dialog.tsx @@ -0,0 +1,69 @@ +import React, { ReactElement, ReactNode } from 'react'; +import { Heading } from 'react-aria-components'; +import styled from 'styled-components'; +import { Text, textStyles } from '../Text/Text'; +import { Modal } from '../Modal/Modal'; +import { Backdrop, BackdropProps } from '../Backdrop/Backdrop'; +import { getSemanticValue } from '../../../essentials/experimental'; + +const Card = styled.div` + display: grid; + gap: 0.5rem; +`; + +const ButtonsWrapper = styled.div` + padding-top: 1.5rem; + display: flex; + flex-direction: row; + justify-content: flex-end; + gap: 1rem; +`; + +const StyledModal = styled(Modal)` + width: 30rem; +`; + +const HeadlineText = styled(Heading)` + margin: 0; + ${textStyles.variants.headline} +`; + +const SubtitleText = styled(Text)` + color: ${getSemanticValue('on-surface-variant')}; +`; + +interface DialogProps extends Omit { + role?: 'dialog' | 'alertdialog'; + headline: ReactNode; + subtitle: ReactNode; + dismissButton: ReactNode; + actionButton: ReactNode; +} + +const Dialog = ({ + role = 'dialog', + headline, + subtitle, + dismissButton, + actionButton, + ...props +}: DialogProps): ReactElement => ( + + + + {headline} + + + {subtitle} + + + + {dismissButton} + {actionButton} + + + + +); + +export { Dialog, DialogProps }; diff --git a/src/components/experimental/Dialog/docs/Dialog.stories.tsx b/src/components/experimental/Dialog/docs/Dialog.stories.tsx new file mode 100644 index 00000000..6506058f --- /dev/null +++ b/src/components/experimental/Dialog/docs/Dialog.stories.tsx @@ -0,0 +1,86 @@ +import React, { useState } from 'react'; +import { DialogTrigger } from 'react-aria-components'; +import { StoryObj, Meta } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { Dialog } from '../Dialog'; +import { Button } from '../../Button/Button'; +import { WarningIcon } from '../../../../icons'; + +const meta: Meta = { + title: 'Experimental/Components/Dialog', + component: Dialog, + parameters: { + layout: 'centered' + } +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => { + const [isOpen, setIsOpen] = useState(false); + + return ( + <> + + setIsOpen(false)}> + Cancel + + } + actionButton={ + + } + /> + + ); + } +}; + +export const Alert: Story = { + render: () => { + const [isOpen, setIsOpen] = useState(false); + + return ( + <> + + setIsOpen(false)}> + Dismiss + + } + actionButton={ + + } + /> + + ); + } +}; diff --git a/src/components/experimental/Modal/Modal.tsx b/src/components/experimental/Modal/Modal.tsx new file mode 100644 index 00000000..62c7cf03 --- /dev/null +++ b/src/components/experimental/Modal/Modal.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Dialog, DialogProps, Modal as BaseModal } from 'react-aria-components'; +import { getSemanticValue } from '../../../essentials/experimental'; + +const ModalCard = styled(BaseModal)` + padding: 2rem; + border-radius: 1.5rem; + background: ${getSemanticValue('surface')}; + color: ${getSemanticValue('on-surface')}; + outline: none; + + &[data-entering] { + animation: modal-zoom 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275); + } + + @keyframes modal-zoom { + from { + transform: scale(0.8); + } + to { + transform: scale(1); + } + } +`; + +const StyledDialog = styled(Dialog)` + outline: none; +`; + +type ModalProps = Pick; + +const Modal = React.forwardRef((props, ref) => ( + + + +)); + +export { Modal, ModalProps }; diff --git a/src/components/experimental/Text/Text.tsx b/src/components/experimental/Text/Text.tsx index 77979ca7..54220354 100644 --- a/src/components/experimental/Text/Text.tsx +++ b/src/components/experimental/Text/Text.tsx @@ -1,11 +1,9 @@ -import React from 'react'; -import { Text as BaseText, TextContext, useContextProps, TextProps as BaseTextProps } from 'react-aria-components'; +import { Text as BaseText, TextProps as BaseTextProps } from 'react-aria-components'; import styled from 'styled-components'; import { compose, ResponsiveValue, variant } from 'styled-system'; import { theme } from '../../../essentials/experimental'; interface TextProps extends BaseTextProps { - as?: React.ElementType; variant?: ResponsiveValue<'display' | 'headline' | 'title1' | 'title2' | 'body1' | 'body2' | 'label1' | 'label2'>; } @@ -64,19 +62,15 @@ export const textStyles = { } }; -const variantStyles = variant(textStyles); - -const StyledText = styled(BaseText).attrs({ theme })` +const Text = styled(BaseText)` color: inherit; margin: 0; - ${compose(variantStyles)} + ${compose(variant(textStyles))} `; -const Text = React.forwardRef((textProps: TextProps, forwardedRef: React.ForwardedRef) => { - const [props, ref] = useContextProps(textProps, forwardedRef, TextContext); - - return ; -}); +Text.defaultProps = { + variant: 'body1' +}; export { Text, TextProps }; diff --git a/src/components/experimental/index.ts b/src/components/experimental/index.ts index 873ce580..bc0bca59 100644 --- a/src/components/experimental/index.ts +++ b/src/components/experimental/index.ts @@ -4,6 +4,7 @@ export { Chip } from './Chip/Chip'; export { ComboBox } from './ComboBox/ComboBox'; export { DateField } from './DateField/DateField'; export { DatePicker } from './DatePicker/DatePicker'; +export { Dialog } from './Dialog/Dialog'; export { Divider } from './Divider/Divider'; export { IconButton } from './IconButton/IconButton'; export { Label } from './Label/Label';