[T-4861: feat] Inline notification component (#86)
* ⚡️ feat: create info square icon component * ⚡️ feat: create inline notification component * 📝 docs: add js doc comment and add inline notification component to the example page * 🐛 fix: use the right method of `useCallback
This commit is contained in:
parent
2369f4498a
commit
6df094bf2e
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||||
|
|
||||||
|
export const InfoSquareIcon = (props: CustomIconProps) => {
|
||||||
|
return (
|
||||||
|
<CustomIcon
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M5.96786 2.5C5.52858 2.49999 5.14962 2.49997 4.83748 2.52548C4.50802 2.55239 4.18034 2.61182 3.86503 2.77249C3.39462 3.01217 3.01217 3.39462 2.77249 3.86503C2.61182 4.18034 2.55239 4.50802 2.52548 4.83748C2.49997 5.14962 2.49999 5.52857 2.5 5.96785V14.0321C2.49999 14.4714 2.49997 14.8504 2.52548 15.1625C2.55239 15.492 2.61182 15.8197 2.77249 16.135C3.01217 16.6054 3.39462 16.9878 3.86503 17.2275C4.18034 17.3882 4.50802 17.4476 4.83748 17.4745C5.14962 17.5 5.52858 17.5 5.96786 17.5H14.0321C14.4714 17.5 14.8504 17.5 15.1625 17.4745C15.492 17.4476 15.8197 17.3882 16.135 17.2275C16.6054 16.9878 16.9878 16.6054 17.2275 16.135C17.3882 15.8197 17.4476 15.492 17.4745 15.1625C17.5 14.8504 17.5 14.4714 17.5 14.0321V5.96786C17.5 5.52858 17.5 5.14962 17.4745 4.83748C17.4476 4.50802 17.3882 4.18034 17.2275 3.86503C16.9878 3.39462 16.6054 3.01217 16.135 2.77249C15.8197 2.61182 15.492 2.55239 15.1625 2.52548C14.8504 2.49997 14.4714 2.49999 14.0322 2.5H5.96786ZM8.33333 9.16667C8.33333 8.70643 8.70643 8.33333 9.16667 8.33333H10C10.4602 8.33333 10.8333 8.70643 10.8333 9.16667V13.3333C10.8333 13.7936 10.4602 14.1667 10 14.1667C9.53976 14.1667 9.16667 13.7936 9.16667 13.3333V10C8.70643 10 8.33333 9.6269 8.33333 9.16667ZM10 5.83333C9.53976 5.83333 9.16667 6.20643 9.16667 6.66667C9.16667 7.1269 9.53976 7.5 10 7.5C10.4602 7.5 10.8333 7.1269 10.8333 6.66667C10.8333 6.20643 10.4602 5.83333 10 5.83333Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</CustomIcon>
|
||||||
|
);
|
||||||
|
};
|
@ -4,6 +4,7 @@ export * from './CheckIcon';
|
|||||||
export * from './ChevronGrabberHorizontal';
|
export * from './ChevronGrabberHorizontal';
|
||||||
export * from './ChevronLeft';
|
export * from './ChevronLeft';
|
||||||
export * from './ChevronRight';
|
export * from './ChevronRight';
|
||||||
|
export * from './InfoSquareIcon';
|
||||||
export * from './WarningIcon';
|
export * from './WarningIcon';
|
||||||
export * from './SearchIcon';
|
export * from './SearchIcon';
|
||||||
export * from './CrossIcon';
|
export * from './CrossIcon';
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
import { VariantProps, tv } from 'tailwind-variants';
|
||||||
|
|
||||||
|
export const inlineNotificationTheme = tv({
|
||||||
|
slots: {
|
||||||
|
wrapper: ['rounded-xl', 'flex', 'gap-2', 'items-start', 'w-full', 'border'],
|
||||||
|
content: ['flex', 'flex-col', 'gap-1'],
|
||||||
|
title: [],
|
||||||
|
description: [],
|
||||||
|
icon: ['flex', 'items-start'],
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
info: {
|
||||||
|
wrapper: ['border-border-info-light', 'bg-base-bg-emphasized-info'],
|
||||||
|
title: ['text-elements-on-emphasized-info'],
|
||||||
|
description: ['text-elements-on-emphasized-info'],
|
||||||
|
icon: ['text-elements-info'],
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
wrapper: ['border-border-danger-light', 'bg-base-bg-emphasized-danger'],
|
||||||
|
title: ['text-elements-on-emphasized-danger'],
|
||||||
|
description: ['text-elements-on-emphasized-danger'],
|
||||||
|
icon: ['text-elements-danger'],
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
wrapper: [
|
||||||
|
'border-border-warning-light',
|
||||||
|
'bg-base-bg-emphasized-warning',
|
||||||
|
],
|
||||||
|
title: ['text-elements-on-emphasized-warning'],
|
||||||
|
description: ['text-elements-on-emphasized-warning'],
|
||||||
|
icon: ['text-elements-warning'],
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
wrapper: [
|
||||||
|
'border-border-success-light',
|
||||||
|
'bg-base-bg-emphasized-success',
|
||||||
|
],
|
||||||
|
title: ['text-elements-on-emphasized-success'],
|
||||||
|
description: ['text-elements-on-emphasized-success'],
|
||||||
|
icon: ['text-elements-success'],
|
||||||
|
},
|
||||||
|
generic: {
|
||||||
|
wrapper: ['border-border-separator', 'bg-base-bg-emphasized'],
|
||||||
|
title: ['text-elements-high-em'],
|
||||||
|
description: ['text-elements-on-emphasized-info'],
|
||||||
|
icon: ['text-elements-high-em'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
sm: {
|
||||||
|
wrapper: ['px-2', 'py-2'],
|
||||||
|
title: ['leading-4', 'text-xs'],
|
||||||
|
description: ['leading-4', 'text-xs'],
|
||||||
|
icon: ['h-4', 'w-4'],
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
wrapper: ['px-3', 'py-3'],
|
||||||
|
title: ['leading-5', 'tracking-[-0.006em]', 'text-sm'],
|
||||||
|
description: ['leading-5', 'tracking-[-0.006em]', 'text-sm'],
|
||||||
|
icon: ['h-5', 'w-5'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hasDescription: {
|
||||||
|
true: {
|
||||||
|
title: ['font-medium'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'generic',
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InlineNotificationTheme = VariantProps<
|
||||||
|
typeof inlineNotificationTheme
|
||||||
|
>;
|
@ -0,0 +1,68 @@
|
|||||||
|
import React, { ReactNode, useCallback } from 'react';
|
||||||
|
import { ComponentPropsWithoutRef } from 'react';
|
||||||
|
import {
|
||||||
|
InlineNotificationTheme,
|
||||||
|
inlineNotificationTheme,
|
||||||
|
} from './InlineNotification.theme';
|
||||||
|
import { InfoSquareIcon } from 'components/shared/CustomIcon';
|
||||||
|
import { cloneIcon } from 'utils/cloneIcon';
|
||||||
|
|
||||||
|
export interface InlineNotificationProps
|
||||||
|
extends ComponentPropsWithoutRef<'div'>,
|
||||||
|
InlineNotificationTheme {
|
||||||
|
/**
|
||||||
|
* The title of the notification
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
/**
|
||||||
|
* The description of the notification
|
||||||
|
*/
|
||||||
|
description?: string;
|
||||||
|
/**
|
||||||
|
* The icon to display in the notification
|
||||||
|
* @default <InfoSquareIcon />
|
||||||
|
*/
|
||||||
|
icon?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A notification that is displayed inline with the content
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <InlineNotification title="Notification title goes here" />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const InlineNotification = ({
|
||||||
|
className,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
size,
|
||||||
|
variant,
|
||||||
|
icon,
|
||||||
|
...props
|
||||||
|
}: InlineNotificationProps) => {
|
||||||
|
const {
|
||||||
|
wrapper,
|
||||||
|
content,
|
||||||
|
title: titleClass,
|
||||||
|
description: descriptionClass,
|
||||||
|
icon: iconClass,
|
||||||
|
} = inlineNotificationTheme({ size, variant, hasDescription: !!description });
|
||||||
|
|
||||||
|
// Render custom icon or default icon
|
||||||
|
const renderIcon = useCallback(() => {
|
||||||
|
if (!icon) return <InfoSquareIcon className={iconClass()} />;
|
||||||
|
return cloneIcon(icon, { className: iconClass() });
|
||||||
|
}, [icon]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...props} className={wrapper({ className })}>
|
||||||
|
{renderIcon()}
|
||||||
|
<div className={content()}>
|
||||||
|
<p className={titleClass()}>{title}</p>
|
||||||
|
{description && <p className={descriptionClass()}>{description}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './InlineNotification';
|
@ -17,6 +17,10 @@ import {
|
|||||||
renderTabs,
|
renderTabs,
|
||||||
renderVerticalTabs,
|
renderVerticalTabs,
|
||||||
} from './renders/tabs';
|
} from './renders/tabs';
|
||||||
|
import {
|
||||||
|
renderInlineNotificationWithDescriptions,
|
||||||
|
renderInlineNotifications,
|
||||||
|
} from './renders/inlineNotifications';
|
||||||
import { renderInputs } from './renders/input';
|
import { renderInputs } from './renders/input';
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
@ -25,7 +29,7 @@ const Page = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full min-h-full">
|
<div className="relative h-full min-h-full">
|
||||||
<div className="flex flex-col items-center justify-center max-w-7xl mx-auto px-20 py-20">
|
<div className="flex flex-col items-center justify-center container mx-auto px-20 py-20">
|
||||||
<h1 className="text-4xl font-bold">Manual Storybook</h1>
|
<h1 className="text-4xl font-bold">Manual Storybook</h1>
|
||||||
<p className="mt-4 text-lg text-center text-gray-500">
|
<p className="mt-4 text-lg text-center text-gray-500">
|
||||||
Get started by editing{' '}
|
Get started by editing{' '}
|
||||||
@ -125,6 +129,19 @@ const Page = () => {
|
|||||||
|
|
||||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||||
|
|
||||||
|
{/* Inline notification */}
|
||||||
|
<div className="flex flex-col gap-10 items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-bold">Inline Notification</h1>
|
||||||
|
<div className="flex gap-1 flex-wrap">
|
||||||
|
{renderInlineNotifications()}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-1 flex-wrap">
|
||||||
|
{renderInlineNotificationWithDescriptions()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||||
|
|
||||||
{/* Link */}
|
{/* Link */}
|
||||||
<div className="flex flex-col gap-10 items-center justify-between">
|
<div className="flex flex-col gap-10 items-center justify-between">
|
||||||
<h1 className="text-2xl font-bold">Link</h1>
|
<h1 className="text-2xl font-bold">Link</h1>
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
import { InlineNotification } from 'components/shared/InlineNotification';
|
||||||
|
import { InlineNotificationTheme } from 'components/shared/InlineNotification/InlineNotification.theme';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const inlineNotificationVariants = [
|
||||||
|
'info',
|
||||||
|
'danger',
|
||||||
|
'warning',
|
||||||
|
'success',
|
||||||
|
'generic',
|
||||||
|
];
|
||||||
|
const inlineNotificationSizes = ['md', 'sm'];
|
||||||
|
|
||||||
|
export const renderInlineNotifications = () => {
|
||||||
|
return inlineNotificationVariants.map((variant) => (
|
||||||
|
<div className="space-y-2" key={variant}>
|
||||||
|
{inlineNotificationSizes.map((size) => (
|
||||||
|
<InlineNotification
|
||||||
|
size={size as InlineNotificationTheme['size']}
|
||||||
|
variant={variant as InlineNotificationTheme['variant']}
|
||||||
|
key={`${variant}-${size}`}
|
||||||
|
title="Notification title goes here"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderInlineNotificationWithDescriptions = () => {
|
||||||
|
return inlineNotificationVariants.map((variant) => (
|
||||||
|
<div className="space-y-2" key={variant}>
|
||||||
|
{inlineNotificationSizes.map((size) => (
|
||||||
|
<InlineNotification
|
||||||
|
size={size as InlineNotificationTheme['size']}
|
||||||
|
variant={variant as InlineNotificationTheme['variant']}
|
||||||
|
key={`${variant}-${size}`}
|
||||||
|
title="Notification title goes here"
|
||||||
|
description="Description goes here"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user