diff --git a/package.json b/package.json index c1681ca..998c81c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@internxt/ui", - "version": "0.1.20", + "version": "0.1.21", "description": "Library of Internxt components", "repository": { "type": "git", diff --git a/src/components/feedback/notifications/usageBanner/UsageWarningBanner.tsx b/src/components/feedback/notifications/usageBanner/UsageWarningBanner.tsx new file mode 100644 index 0000000..43b2d33 --- /dev/null +++ b/src/components/feedback/notifications/usageBanner/UsageWarningBanner.tsx @@ -0,0 +1,101 @@ +import { CloudWarningIcon, XIcon } from '@phosphor-icons/react'; +import React from 'react'; +import { Button } from '../../../input/button'; + +export interface UsageWarningBannerProps { + usage: string; + limit: string; + percentage: number; + titleLabel: string; + descriptionLabelLine1: string; + descriptionLabelLine2: string; + upgradeLabel: string; + onUpgradeClick: () => void; + onCloseButtonClick: () => void; + isLoading?: boolean; + darkMode?: boolean; +} + +type StorageLevel = 'low warning' | 'middle warning' | 'high warning'; + +const getStorageLevel = (percentage: number): StorageLevel => { + if (percentage >= 95) return 'high warning'; + if (percentage >= 80) return 'middle warning'; + return 'low warning'; +}; + +const STORAGE_LEVEL_STYLES: Record = { + 'low warning': { bar: 'bg-yellow-60' }, + 'middle warning': { bar: 'bg-orange-60' }, + 'high warning': { bar: 'bg-danger' }, +}; + +/** + * Renders text marking the segments wrapped in `**...**` as bold. + */ +const renderWithBold = (text: string): React.ReactNode => + text.split(/(\*\*[^*]+\*\*)/g).map((segment, index) => { + if (segment.startsWith('**') && segment.endsWith('**')) { + return {segment.slice(2, -2)}; + } + return {segment}; + }); + +const UsageWarningBanner: React.FC = ({ + usage, + limit, + percentage, + titleLabel, + descriptionLabelLine1, + descriptionLabelLine2, + upgradeLabel, + onUpgradeClick, + onCloseButtonClick, + isLoading = true, +}) => { + const level = getStorageLevel(percentage); + const styles = STORAGE_LEVEL_STYLES[level]; + + return ( +
+
+
+ + +

{titleLabel}

+
+ +
+ +

{renderWithBold(descriptionLabelLine1)}

+

{renderWithBold(descriptionLabelLine2)}

+
+
+
+
+
+
+
+ {isLoading ? ( +
+
+
+
+
+ ) : ( + +

{usage}

+

/

+

{limit}

+
+ )} +
+ +
+
+ ); +}; + +export default UsageWarningBanner; diff --git a/src/components/feedback/notifications/usageBanner/index.ts b/src/components/feedback/notifications/usageBanner/index.ts new file mode 100644 index 0000000..28aaf20 --- /dev/null +++ b/src/components/feedback/notifications/usageBanner/index.ts @@ -0,0 +1,2 @@ +export { default as UsageWarningBanner } from './UsageWarningBanner'; +export type { UsageWarningBannerProps } from './UsageWarningBanner'; diff --git a/src/components/index.ts b/src/components/index.ts index db5baa0..98376dd 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -8,6 +8,7 @@ export * from './data-display/table/Table'; export * from './feedback/empty'; export * from './feedback/loader'; export * from './feedback/skeletonLoader'; +export * from './feedback/notifications/usageBanner'; // input export * from './input/button'; diff --git a/src/components/navigation/sidenav/SidenavStorage.tsx b/src/components/navigation/sidenav/SidenavStorage.tsx index 94a8c00..15d7859 100644 --- a/src/components/navigation/sidenav/SidenavStorage.tsx +++ b/src/components/navigation/sidenav/SidenavStorage.tsx @@ -1,3 +1,5 @@ +import { CloudWarningIcon } from '@phosphor-icons/react'; + interface SidenavStorageProps { usage: string; limit: string; @@ -5,8 +7,26 @@ interface SidenavStorageProps { onUpgradeClick: () => void; upgradeLabel?: string; isLoading?: boolean; + advertisementMessage?: string; } +type StorageLevel = 'normal' | 'low warning' | 'middle warning' | 'high warning'; + +const getStorageLevel = (percentage: number): StorageLevel => { + if (percentage >= 95) return 'high warning'; + if (percentage >= 80) return 'middle warning'; + if (percentage >= 60) return 'low warning'; + return 'normal'; +}; + +const STORAGE_LEVEL_STYLES: Record = { + normal: { bar: 'bg-gray-60', container: '' }, + 'low warning': { bar: 'bg-yellow-60', container: '' }, + 'middle warning': { bar: 'bg-orange-60', container: '' }, + 'high warning': { bar: 'bg-danger', container: 'bg-alert rounded-xl border border-alert-1 p-3 gap-2' }, +}; + + const SidenavStorage = ({ usage, limit, @@ -14,9 +34,23 @@ const SidenavStorage = ({ onUpgradeClick, upgradeLabel, isLoading = true, + advertisementMessage, }: SidenavStorageProps): JSX.Element => { + const level = getStorageLevel(percentage); + const styles = STORAGE_LEVEL_STYLES[level]; + const showWarning = level === 'middle warning' || level === 'high warning'; + return ( -
+
+ {showWarning && advertisementMessage && ( +
+ +

{advertisementMessage}

+
+ )}
{isLoading ? ( @@ -39,9 +73,10 @@ const SidenavStorage = ({ )}
+
= { + title: 'Feedback/Banner', + component: UsageWarningBanner, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + percentage: { + control: { type: 'range', min: 0, max: 100, step: 1 }, + }, + isLoading: { + control: { type: 'boolean' }, + }, + }, + args: { + onUpgradeClick: fn(), + onCloseButtonClick: fn(), + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + usage: '600MB', + limit: '1GB', + percentage: 60, + titleLabel: 'Get more space for your files', + descriptionLabelLine1: 'Unlock additional storage with an exclusive 85% discount on your upgrade', + descriptionLabelLine2: 'Access advanced features like file version history, Rclone, NAS support, premium support, and more', + upgradeLabel: 'Get offer', + isLoading: false, + }, +}; + +export const LowUsage: Story = { + args: { + ...Default.args, + usage: '800MB', + limit: '1GB', + percentage: 80, + titleLabel: 'Your storage is filling up', + descriptionLabelLine1: 'Upgrade today with an exclusive 85% discount and keep uploading without interruptions', + descriptionLabelLine2: 'Get more storage plus advanced features like file version history, NAS support, Rclone integration, and premium support', + }, +}; + +export const AlmostFull: Story = { + args: { + ...Default.args, + usage: '950MB', + limit: '1GB', + percentage: 95, + titleLabel: 'Your storage is almost full', + descriptionLabelLine1: 'You may soon be unable to upload new files', + descriptionLabelLine2: 'Upgrade now with an exclusive 85% discount to continue storing and syncing your files without limits' + }, +}; + +export const Loading: Story = { + args: { + ...Default.args, + isLoading: true, + }, +};