From 6df094bf2e3d18719816d32e3dbc7e423064c803 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Thu, 22 Feb 2024 10:45:19 +0700 Subject: [PATCH] [T-4861: feat] Inline notification component (#86) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ 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 --- .../shared/CustomIcon/InfoSquareIcon.tsx | 21 +++++ .../src/components/shared/CustomIcon/index.ts | 1 + .../InlineNotification.theme.ts | 78 +++++++++++++++++++ .../InlineNotification/InlineNotification.tsx | 68 ++++++++++++++++ .../shared/InlineNotification/index.ts | 1 + .../frontend/src/pages/components/index.tsx | 19 ++++- .../renders/inlineNotifications.tsx | 43 ++++++++++ 7 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 packages/frontend/src/components/shared/CustomIcon/InfoSquareIcon.tsx create mode 100644 packages/frontend/src/components/shared/InlineNotification/InlineNotification.theme.ts create mode 100644 packages/frontend/src/components/shared/InlineNotification/InlineNotification.tsx create mode 100644 packages/frontend/src/components/shared/InlineNotification/index.ts create mode 100644 packages/frontend/src/pages/components/renders/inlineNotifications.tsx diff --git a/packages/frontend/src/components/shared/CustomIcon/InfoSquareIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/InfoSquareIcon.tsx new file mode 100644 index 00000000..273131d6 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/InfoSquareIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const InfoSquareIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index 6479a1dd..18dbf249 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -4,6 +4,7 @@ export * from './CheckIcon'; export * from './ChevronGrabberHorizontal'; export * from './ChevronLeft'; export * from './ChevronRight'; +export * from './InfoSquareIcon'; export * from './WarningIcon'; export * from './SearchIcon'; export * from './CrossIcon'; diff --git a/packages/frontend/src/components/shared/InlineNotification/InlineNotification.theme.ts b/packages/frontend/src/components/shared/InlineNotification/InlineNotification.theme.ts new file mode 100644 index 00000000..43536b55 --- /dev/null +++ b/packages/frontend/src/components/shared/InlineNotification/InlineNotification.theme.ts @@ -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 +>; diff --git a/packages/frontend/src/components/shared/InlineNotification/InlineNotification.tsx b/packages/frontend/src/components/shared/InlineNotification/InlineNotification.tsx new file mode 100644 index 00000000..b3b2845c --- /dev/null +++ b/packages/frontend/src/components/shared/InlineNotification/InlineNotification.tsx @@ -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 + */ + icon?: ReactNode; +} + +/** + * A notification that is displayed inline with the content + * + * @example + * ```tsx + * + * ``` + */ +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 ; + return cloneIcon(icon, { className: iconClass() }); + }, [icon]); + + return ( +
+ {renderIcon()} +
+

{title}

+ {description &&

{description}

} +
+
+ ); +}; diff --git a/packages/frontend/src/components/shared/InlineNotification/index.ts b/packages/frontend/src/components/shared/InlineNotification/index.ts new file mode 100644 index 00000000..fbe7bbd4 --- /dev/null +++ b/packages/frontend/src/components/shared/InlineNotification/index.ts @@ -0,0 +1 @@ +export * from './InlineNotification'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index ab6be949..b2c81d31 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -17,6 +17,10 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; +import { + renderInlineNotificationWithDescriptions, + renderInlineNotifications, +} from './renders/inlineNotifications'; import { renderInputs } from './renders/input'; const Page = () => { @@ -25,7 +29,7 @@ const Page = () => { return (
-
+

Manual Storybook

Get started by editing{' '} @@ -125,6 +129,19 @@ const Page = () => {

+ {/* Inline notification */} +
+

Inline Notification

+
+ {renderInlineNotifications()} +
+
+ {renderInlineNotificationWithDescriptions()} +
+
+ +
+ {/* Link */}

Link

diff --git a/packages/frontend/src/pages/components/renders/inlineNotifications.tsx b/packages/frontend/src/pages/components/renders/inlineNotifications.tsx new file mode 100644 index 00000000..fc36b914 --- /dev/null +++ b/packages/frontend/src/pages/components/renders/inlineNotifications.tsx @@ -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) => ( +
+ {inlineNotificationSizes.map((size) => ( + + ))} +
+ )); +}; + +export const renderInlineNotificationWithDescriptions = () => { + return inlineNotificationVariants.map((variant) => ( +
+ {inlineNotificationSizes.map((size) => ( + + ))} +
+ )); +};