diff --git a/src/components/experimental/Divider/Divider.spec.tsx b/src/components/experimental/Divider/Divider.spec.tsx new file mode 100644 index 00000000..1fa10946 --- /dev/null +++ b/src/components/experimental/Divider/Divider.spec.tsx @@ -0,0 +1,89 @@ +import * as React from 'react'; +import { render, screen } from '@testing-library/react'; +import { Divider } from './Divider'; + +describe('Experimental: Divider', () => { + it('renders a horizontal divider by default when not passing any props', () => { + render(); + expect(screen.getByTestId('horizontal-divider-experimental')).toBeInTheDocument(); + expect(screen.queryByTestId('vertical-divider-experimental')).not.toBeInTheDocument(); + }); + + it('renders a vertical divider when passing vertical prop', () => { + render(); + expect(screen.getByTestId('vertical-divider-experimental')).toBeInTheDocument(); + expect(screen.queryByTestId('horizontal-divider-experimental')).not.toBeInTheDocument(); + }); + + it('renders horizontal divider with 1rem top and bottom offset', () => { + render(); + const dividerInstance = screen.getByTestId('horizontal-divider-experimental'); + const dividerStyle = window.getComputedStyle(dividerInstance); + + expect(dividerStyle.marginTop).toBe('1rem'); + expect(dividerStyle.marginBottom).toBe('1rem'); + }); + + it('renders vertical divider with 1rem left and right offset', () => { + render(); + const dividerInstance = screen.getByTestId('vertical-divider-experimental'); + const dividerStyle = window.getComputedStyle(dividerInstance); + + expect(dividerStyle.marginLeft).toBe('1rem'); + expect(dividerStyle.marginRight).toBe('1rem'); + }); + + it('renders vertical divider with 1rem top and bottom offset when variant is middle-inset', () => { + render(); + const dividerInstance = screen.getByTestId('vertical-divider-experimental'); + const dividerStyle = window.getComputedStyle(dividerInstance); + + expect(dividerStyle.marginTop).toBe('1rem'); + expect(dividerStyle.marginBottom).toBe('1rem'); + }); + + it('renders vertical divider with 1rem offset when variant is inset', () => { + render(); + const dividerInstance = screen.getByTestId('vertical-divider-experimental'); + const dividerStyle = window.getComputedStyle(dividerInstance); + + expect(dividerStyle.marginTop).toBe('1rem'); + expect(dividerStyle.marginBottom).toBe(''); + }); + + it('renders horizontal divider with 1rem left offset when variant is inset', () => { + render(); + const dividerInstance = screen.getByTestId('horizontal-divider-experimental'); + const dividerStyle = window.getComputedStyle(dividerInstance); + + expect(dividerStyle.marginRight).toBe(''); + expect(dividerStyle.marginLeft).toBe('1rem'); + }); + + it('renders horizontal divider with overridden offset when variant is inset', () => { + render(); + const dividerInstance = screen.getByTestId('horizontal-divider-experimental'); + const dividerStyle = window.getComputedStyle(dividerInstance); + + expect(dividerStyle.marginRight).toBe(''); + expect(dividerStyle.marginLeft).toBe('2rem'); + }); + + it('renders horizontal divider with 1rem left and right offset when variant is middle-inset', () => { + render(); + const dividerInstance = screen.getByTestId('horizontal-divider-experimental'); + const dividerStyle = window.getComputedStyle(dividerInstance); + + expect(dividerStyle.marginRight).toBe('1rem'); + expect(dividerStyle.marginLeft).toBe('1rem'); + }); + + it('renders horizontal divider with overridden offset when variant is middle-inset', () => { + render(); + const dividerInstance = screen.getByTestId('horizontal-divider-experimental'); + const dividerStyle = window.getComputedStyle(dividerInstance); + + expect(dividerStyle.marginRight).toBe('2rem'); + expect(dividerStyle.marginLeft).toBe('1rem'); + }); +}); diff --git a/src/components/experimental/Divider/Divider.tsx b/src/components/experimental/Divider/Divider.tsx new file mode 100644 index 00000000..15a27bf2 --- /dev/null +++ b/src/components/experimental/Divider/Divider.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { compose, LayoutProps, space, SpaceProps, variant } from 'styled-system'; +import styled from 'styled-components'; +import { getSemanticValue } from '../../../essentials/experimental'; + +type DividerProps = SpaceProps & + LayoutProps & { + /** + * Set the direction of the divider to vertical + */ + vertical?: boolean; + /** + * Set the variant of the divider + */ + variant?: 'full-width' | 'inset' | 'middle-inset'; + }; + +const horizontalVariants = variant({ + prop: 'variant', + variants: { + 'full-width': { + minWidth: '100%' + }, + inset: { + marginLeft: '1rem' + }, + 'middle-inset': { + marginLeft: '1rem', + marginRight: '1rem' + } + } +}); + +const verticalVariants = variant({ + prop: 'variant', + variants: { + 'full-width': { + minHeight: '100%' + }, + inset: { + marginTop: '1rem' + }, + 'middle-inset': { + marginTop: '1rem', + marginBottom: '1rem' + } + } +}); + +const HorizontalLine = styled.div` + border-top: 1px solid ${getSemanticValue('divider')}; + ${compose(space, horizontalVariants)} +`; + +const VerticalLine = styled.div` + display: inline-block; + height: 100%; + border-left: 1px solid ${getSemanticValue('divider')}; + ${compose(space, verticalVariants)} +`; + +export const Divider: React.FC = ({ + vertical = false, + variant: variantVal = 'full-width', + ...props +}: DividerProps) => + vertical ? ( + + ) : ( + + ); diff --git a/src/components/experimental/Divider/docs/Divider.stories.tsx b/src/components/experimental/Divider/docs/Divider.stories.tsx new file mode 100644 index 00000000..49e81218 --- /dev/null +++ b/src/components/experimental/Divider/docs/Divider.stories.tsx @@ -0,0 +1,131 @@ +import React from 'react'; +import { StoryObj, Meta } from '@storybook/react'; +import { Divider } from '../Divider'; +import { Text } from '../../Text/Text'; + +const meta: Meta = { + title: 'Experimental/Components/Divider', + component: Divider, + parameters: { + layout: 'centered' + } +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + decorators: [ + Story => ( +
+ One + + Two +
+ ) + ], + args: { + vertical: false + } +}; + +export const Inset: Story = { + decorators: [ + Story => ( +
+ One + + Two +
+ ) + ], + args: { + vertical: false, + variant: 'inset' + } +}; + +export const MiddleInset: Story = { + decorators: [ + Story => ( +
+ One + + Two +
+ ) + ], + args: { + vertical: false, + variant: 'middle-inset' + } +}; + +export const VerticalDivider: Story = { + args: { + vertical: true + }, + decorators: [ + Story => ( +
+ One + + Two +
+ ) + ] +}; + +export const VerticalInset: Story = { + decorators: [ + Story => ( +
+ One + + Two +
+ ) + ], + args: { + vertical: true, + variant: 'inset' + } +}; + +export const VerticalMiddleInset: Story = { + decorators: [ + Story => ( +
+ One + + Two +
+ ) + ], + args: { + vertical: true, + variant: 'middle-inset' + } +}; diff --git a/src/essentials/experimental/Colors.ts b/src/essentials/experimental/Colors.ts index b48384c4..15537dff 100644 --- a/src/essentials/experimental/Colors.ts +++ b/src/essentials/experimental/Colors.ts @@ -101,6 +101,7 @@ export const ColorPalette = { export const SemanticColorsLight = { // Accent accent: 'transparent', + divider: ColorPalette.neutral['90'], 'on-accent': 'transparent', // Interactive interactive: ColorPalette.marooned['50'], @@ -130,6 +131,7 @@ export const SemanticColorsLight = { export const SemanticColorsDark = { // Accent accent: 'transparent', + divider: ColorPalette.neutral['40'], 'on-accent': 'transparent', // Interactive interactive: ColorPalette.marooned['70'], diff --git a/src/essentials/experimental/types.ts b/src/essentials/experimental/types.ts index 66ba5f53..81c2ba26 100644 --- a/src/essentials/experimental/types.ts +++ b/src/essentials/experimental/types.ts @@ -29,6 +29,7 @@ export type SemanticColorsSchema = { 'on-interactive-container': Color; // Neutrals surface: Color; + divider: Color; 'on-surface': Color; 'surface-variant': Color; 'on-surface-variant': Color;