diff --git a/system/core/src/components/FormBanner.ts b/system/core/src/components/FormBanner.ts new file mode 100644 index 000000000..9267e21a1 --- /dev/null +++ b/system/core/src/components/FormBanner.ts @@ -0,0 +1,56 @@ +import { css } from '../utils'; + +export const element = 'div'; +export const className = 'form-banner'; + +const variants = [ + 'success', + 'info', + 'error', + 'warning', + 'neutral', + 'purple', + 'orange' +] as const; + +export type FormBannerVariant = (typeof variants)[number]; + +export interface Props { + 'data-variant': FormBannerVariant; +} + +export const fullStyles = css` + display: flex; + gap: var(--spacing-l2); + color: var(--neutral-text); + background: var(--neutral-surface); + border-radius: var(--border-radius-small); + padding: var(--spacing-l4) var(--spacing-l3); + align-items: flex-start; + width: 100%; + + &[data-variant='error'] { + color: var(--error-text); + background: var(--error-surface); + } + &[data-variant='warning'] { + color: var(--warning-text); + background: var(--warning-surface); + } + &[data-variant='success'] { + color: var(--success-text); + background: var(--success-surface); + } + &[data-variant='info'] { + color: var(--info-text); + background: var(--info-surface); + } + &[data-variant='purple'] { + color: var(--purple-text); + background: var(--purple-surface); + } + &[data-variant='orange'] { + color: var(--orange-text); + background: var(--orange-surface); + } +`; diff --git a/system/core/src/index.ts b/system/core/src/index.ts index 26c7b5d1d..422dddabc 100644 --- a/system/core/src/index.ts +++ b/system/core/src/index.ts @@ -13,6 +13,7 @@ export * as checkboxRadioLabel from './components/CheckboxRadioLabel'; export * as childAnchors from './components/ChildAnchors'; export * as chip from './components/Chip'; export * as chipRow from './components/ChipRow'; +export * as formBanner from './components/FormBanner'; export * as iconButton from './components/IconButton'; export * as inputAlert from './components/InputAlert'; export * as inputCore from './components/InputCore'; diff --git a/system/react-css/src/config.tsx b/system/react-css/src/config.tsx index 52adb074c..dd7071eed 100644 --- a/system/react-css/src/config.tsx +++ b/system/react-css/src/config.tsx @@ -39,7 +39,7 @@ export function configureDefaults(defaults: CoreConfigDefaults): void { } export function getSentimentIcon( - variant: 'success' | 'error' | 'neutral' | 'info' | 'warning', + variant: 'success' | 'error' | 'neutral' | 'info' | 'warning' | 'default', size = getConfigDefault('iconSize') ): JSX.Element { switch (variant) { @@ -47,10 +47,11 @@ export function getSentimentIcon( return ; case 'error': return ; + case 'warning': + return ; case 'neutral': case 'info': + case 'default': return ; - case 'warning': - return ; } } diff --git a/system/react-css/src/index.ts b/system/react-css/src/index.ts index 660260cf3..5b74c8f90 100644 --- a/system/react-css/src/index.ts +++ b/system/react-css/src/index.ts @@ -75,6 +75,13 @@ export { export type { Props as CheckboxRadioLabelProps } from './structuredComponents/CheckboxRadioLabel'; export { Chip } from './structuredComponents/Chip'; export type { Props as ChipProps } from './structuredComponents/Chip'; +export { + FormBannerCore, + FormBannerMessage, + FormBannerIconWrapper, + FormBanner +} from './structuredComponents/FormBanner'; +export type { Props as FormBannerProps } from './structuredComponents/FormBanner'; export { Input } from './structuredComponents/Input'; export type { Props as InputProps } from './structuredComponents/Input'; export { InputAlert } from './structuredComponents/InputAlert'; diff --git a/system/react-css/src/structuredComponents/FormBanner.tsx b/system/react-css/src/structuredComponents/FormBanner.tsx new file mode 100644 index 000000000..3e228498d --- /dev/null +++ b/system/react-css/src/structuredComponents/FormBanner.tsx @@ -0,0 +1,69 @@ +import type { formBanner } from '@tablecheck/tablekit-core'; +import * as React from 'react'; + +import { getSentimentIcon } from '../config'; + +export type Props = formBanner.Props; + +export const FormBannerCore = React.forwardRef< + HTMLDivElement, + Props & React.HTMLAttributes +>((props, ref) => ( +
+)); + +export const FormBannerMessage = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>((props, ref) => ( +
+)); + +export const FormBannerIconWrapper = React.forwardRef< + HTMLSpanElement, + React.HTMLAttributes +>((props, ref) => ( + +)); + +interface ComposedProps extends Props { + /** + * Icon to display in the form banner. Pass `null` to display sentiment one. + */ + icon?: React.ReactNode; + children: React.ReactNode; +} + +function getIcon(variant: Props['data-variant']): JSX.Element { + if (variant === 'purple' || variant === 'orange') + return getSentimentIcon('default'); + return getSentimentIcon(variant); +} + +export const FormBanner = React.forwardRef< + HTMLDivElement, + ComposedProps & React.HTMLAttributes +>((props, ref) => { + const { icon, title, children, ...passThrough } = props; + return ( + + + {icon ?? getIcon(passThrough['data-variant'])} + + {children} + + ); +}); diff --git a/system/react/src/config.tsx b/system/react/src/config.tsx index 52adb074c..dd7071eed 100644 --- a/system/react/src/config.tsx +++ b/system/react/src/config.tsx @@ -39,7 +39,7 @@ export function configureDefaults(defaults: CoreConfigDefaults): void { } export function getSentimentIcon( - variant: 'success' | 'error' | 'neutral' | 'info' | 'warning', + variant: 'success' | 'error' | 'neutral' | 'info' | 'warning' | 'default', size = getConfigDefault('iconSize') ): JSX.Element { switch (variant) { @@ -47,10 +47,11 @@ export function getSentimentIcon( return ; case 'error': return ; + case 'warning': + return ; case 'neutral': case 'info': + case 'default': return ; - case 'warning': - return ; } } diff --git a/system/react/src/index.ts b/system/react/src/index.ts index 1d4025ef6..85f72311c 100644 --- a/system/react/src/index.ts +++ b/system/react/src/index.ts @@ -140,6 +140,13 @@ export { export type { Props as AlertProps } from './structuredComponents/Alert'; export { chipStyledComponents, Chip } from './structuredComponents/Chip'; export type { Props as ChipProps } from './structuredComponents/Chip'; +export { + FormBannerCore, + FormBannerMessage, + FormBannerIconWrapper, + FormBanner +} from './structuredComponents/FormBanner'; +export type { Props as FormBannerProps } from './structuredComponents/FormBanner'; export { Input } from './structuredComponents/Input'; export type { Props as InputProps } from './structuredComponents/Input'; export { InputAlertInner, InputAlert } from './structuredComponents/InputAlert'; diff --git a/system/react/src/structuredComponents/FormBanner.tsx b/system/react/src/structuredComponents/FormBanner.tsx new file mode 100644 index 000000000..2041b4ebd --- /dev/null +++ b/system/react/src/structuredComponents/FormBanner.tsx @@ -0,0 +1,56 @@ +import styled from '@emotion/styled'; +import { formBanner } from '@tablecheck/tablekit-core'; +import * as React from 'react'; + +import { getSentimentIcon } from '../config'; + +export type Props = formBanner.Props; + +export const FormBannerCore = styled.div` + ${formBanner.fullStyles} +`; + +export const FormBannerMessage = styled.div` + font: var(--body-2); +`; + +export const FormBannerIconWrapper = React.forwardRef< + HTMLSpanElement, + React.HTMLAttributes +>((props, ref) => ( + +)); + +interface ComposedProps extends Props { + /** + * Icon to display in the form banner. Pass `null` to display sentiment one. + */ + icon?: React.ReactNode; + children: React.ReactNode; +} + +function getIcon(variant: Props['data-variant']): JSX.Element { + if (variant === 'purple' || variant === 'orange') + return getSentimentIcon('default'); + return getSentimentIcon(variant); +} + +export const FormBanner = React.forwardRef< + HTMLDivElement, + ComposedProps & React.HTMLAttributes +>((props, ref) => { + const { icon, children, ...passThrough } = props; + return ( + + + {icon ?? getIcon(passThrough['data-variant'])} + + {children} + + ); +}); diff --git a/system/stories/src/FormBanner.stories.tsx b/system/stories/src/FormBanner.stories.tsx new file mode 100644 index 000000000..17e928df9 --- /dev/null +++ b/system/stories/src/FormBanner.stories.tsx @@ -0,0 +1,52 @@ +import { Meta, StoryFn } from '@storybook/react'; +import { formBanner } from '@tablecheck/tablekit-core'; +import * as emotion from '@tablecheck/tablekit-react'; +import * as css from '@tablecheck/tablekit-react-css'; +import * as React from 'react'; + +const contentVariants: emotion.FormBannerProps['data-variant'][] = [ + 'success', + 'info', + 'error', + 'warning', + 'neutral', + 'purple', + 'orange' +]; + +type LayoutComponents = Record<'FormBanner', React.ElementType>; + +export default { + title: 'Components/FormBanner', + component: emotion.FormBanner, + parameters: { + ...formBanner, + variants: contentVariants, + auxiliaryComponents: [ + emotion.FormBannerIconWrapper, + emotion.FormBannerMessage + ] + } +} as Meta; + +const Template: StoryFn = ({ FormBanner }) => ( + <> + {contentVariants.map((variant) => ( + + Important message to explain a form or a section of a form. + + ))} + +); + +export const Emotion: StoryFn = Template.bind({}); +Emotion.args = { + FormBanner: emotion.FormBanner +} satisfies LayoutComponents; +Emotion.parameters = { useEmotion: true }; + +export const Class: StoryFn = Template.bind({}); +Class.args = { + FormBanner: css.FormBanner +} satisfies LayoutComponents; +Class.parameters = { useEmotion: false };