Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@internxt/ui",
"version": "0.1.20",
"version": "0.1.21",
"description": "Library of Internxt components",
"repository": {
"type": "git",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<StorageLevel, { bar: string }> = {
'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 <strong key={index}>{segment.slice(2, -2)}</strong>;
}
return <React.Fragment key={index}>{segment}</React.Fragment>;
});

const UsageWarningBanner: React.FC<UsageWarningBannerProps> = ({
usage,
limit,
percentage,
titleLabel,
descriptionLabelLine1,
descriptionLabelLine2,
upgradeLabel,
onUpgradeClick,
onCloseButtonClick,
isLoading = true,
}) => {
const level = getStorageLevel(percentage);
const styles = STORAGE_LEVEL_STYLES[level];

return (
<div className="flex flex-col w-full px-4 py-3 rounded-xl bg-alert border-alert-1 border h-min">
<div className="flex flex-col w-full gap-1">
<div className="flex flex-row justify-between items-center">
<span className="flex flex-row items-center gap-2">
<CloudWarningIcon weight="fill" className="size-5 text-yellow-60" />
<p className="text-sm font-semibold">{titleLabel}</p>
</span>
<XIcon className="size-5 text-gray-54 cursor-pointer" onClick={onCloseButtonClick} />
</div>
<span>
<p className="text-xs font-medium text-gray-54 whitespace-nowrap">{renderWithBold(descriptionLabelLine1)}</p>
<p className="text-xs font-medium text-gray-54 whitespace-nowrap">{renderWithBold(descriptionLabelLine2)}</p>
</span>
</div>
<div className="flex flex-row items-center gap-6 h-10">
<div className="flex flex-1 flex-col gap-1">
<div className="flex w-full h-1.5 bg-gray-10 rounded-full">
<div className={`${styles.bar} h-full rounded-full`} style={{ width: `${percentage}%` }} />
</div>
{isLoading ? (
<div className="flex flex-row items-center gap-2">
<div className="h-3 w-8 rounded-lg bg-gray-5 animate-pulse" />
<div className="h-3 w-2 rounded-lg bg-gray-5 animate-pulse" />
<div className="h-3 w-8 rounded-lg bg-gray-5 animate-pulse" />
</div>
) : (
<span className="flex flex-row gap-1">
<p className="text-gray-60 text-xs">{usage}</p>
<p className="text-gray-60 text-xs">/</p>
<p className="text-gray-60 text-xs">{limit}</p>
</span>
)}
</div>
<Button variant="secondary" size="medium" onClick={onUpgradeClick}>
{upgradeLabel}
</Button>
</div>
</div>
);
};

export default UsageWarningBanner;
2 changes: 2 additions & 0 deletions src/components/feedback/notifications/usageBanner/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as UsageWarningBanner } from './UsageWarningBanner';
export type { UsageWarningBannerProps } from './UsageWarningBanner';
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
39 changes: 37 additions & 2 deletions src/components/navigation/sidenav/SidenavStorage.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,56 @@
import { CloudWarningIcon } from '@phosphor-icons/react';

interface SidenavStorageProps {
usage: string;
limit: string;
percentage: number;
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<StorageLevel, { bar: string; container: string }> = {
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,
percentage,
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 (
<div className="flex flex-col w-full gap-2.5 px-2 pb-5">
<div className={`flex flex-col w-full gap-2.5 px-2 pb-5 ${styles.container}`}>
{showWarning && advertisementMessage && (
<div className="flex flex-row gap-0.5 items-center">
<CloudWarningIcon
className="size-5 inline-block text-yellow-60 mr-1"
weight={level === 'high warning' ? 'fill' : 'regular'}
/>
<p className="text-sm font-semibold">{advertisementMessage}</p>
</div>
)}
<div className="flex flex-row w-full justify-between">
<div className="flex flex-row items-center gap-2">
{isLoading ? (
Expand All @@ -39,9 +73,10 @@ const SidenavStorage = ({
</button>
)}
</div>

<div className="flex w-full h-1.5 bg-gray-10 rounded-full">
<div
className="bg-gray-60 rounded-full"
className={`${styles.bar} rounded-full`}
style={{
width: `${percentage}%`,
}}
Expand Down
31 changes: 31 additions & 0 deletions src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
--color-red-dark: rgb(var(--color-red-dark-rgb));
--color-orange: rgb(var(--color-orange-rgb));
--color-orange-dark: rgb(var(--color-orange-dark-rgb));
--color-orange-60: rgb(var(--color-orange-60-rgb));
--color-yellow: rgb(var(--color-yellow-rgb));
--color-yellow-dark: rgb(var(--color-yellow-dark-rgb));
--color-yellow-60: rgb(var(--color-yellow-60-rgb));
--color-green: rgb(var(--color-green-rgb));
--color-green-dark: rgb(var(--color-green-dark-rgb));
--color-pink: rgb(var(--color-pink-rgb));
Expand All @@ -24,12 +26,17 @@
--color-gray-30: rgb(var(--color-gray-30-rgb));
--color-gray-40: rgb(var(--color-gray-40-rgb));
--color-gray-50: rgb(var(--color-gray-50-rgb));
--color-gray-54: rgb(var(--color-gray-54-rgb));
--color-gray-55: rgb(var(--color-gray-55-rgb));
--color-gray-60: rgb(var(--color-gray-60-rgb));
--color-gray-70: rgb(var(--color-gray-70-rgb));
--color-gray-80: rgb(var(--color-gray-80-rgb));
--color-gray-90: rgb(var(--color-gray-90-rgb));
--color-gray-100: rgb(var(--color-gray-100-rgb));
--color-danger: rgb(var(--color-danger-rgb));
--color-alert: rgb(var(--color-alert-rgb));
--color-alert-1: rgb(var(--color-alert-1-rgb));

}

button {
Expand All @@ -46,8 +53,10 @@ button {
--color-red-dark-rgb: 230 11 0;
--color-orange-rgb: 255 149 0;
--color-orange-dark-rgb: 230 134 0;
--color-orange-60-rgb: 229 134 0;
--color-yellow-rgb: 255 204 0;
--color-yellow-dark-rgb: 230 184 0;
--color-yellow-60-rgb: 229 183 0;
--color-green-rgb: 50 195 86;
--color-green-dark-rgb: 45 174 77;
--color-pink-rgb: 255 36 76;
Expand All @@ -63,12 +72,16 @@ button {
--color-gray-30-rgb: 199 199 205;
--color-gray-40-rgb: 174 174 179;
--color-gray-50-rgb: 142 142 148;
--color-gray-54-rgb: 124 124 126;
--color-gray-55-rgb: 115 115 115;
--color-gray-60-rgb: 99 99 103;
--color-gray-70-rgb: 72 72 75;
--color-gray-80-rgb: 58 58 59;
--color-gray-90-rgb: 44 44 48;
--color-gray-100-rgb: 24 24 27;
--color-danger-rgb:229 11 0;
--color-alert-rgb: 255 249 229;
--color-alert-1-rgb: 255 244 204;
}

:root.dark {
Expand All @@ -79,9 +92,11 @@ button {
--color-red-rgb: 255 61 51;
--color-red-dark-rgb: 255 36 26;
--color-orange-rgb: 255 164 36;
--color-orange-60-rgb: 229 134 0;
--color-orange-dark-rgb: 255 153 10;
--color-yellow-rgb: 255 214 51;
--color-yellow-dark-rgb: 255 209 26;
--color-yellow-60-rgb: 229 183 0;
--color-green-rgb: 72 208 106;
--color-green-dark-rgb: 52 203 90;
--color-pink-rgb: 255 71 105;
Expand All @@ -97,12 +112,16 @@ button {
--color-gray-30-rgb: 99 99 103;
--color-gray-40-rgb: 142 142 148;
--color-gray-50-rgb: 174 174 179;
--color-gray-54-rgb: 124 124 126;
--color-gray-55-rgb: 115 115 115;
--color-gray-60-rgb: 199 199 205;
--color-gray-70-rgb: 209 209 215;
--color-gray-80-rgb: 229 229 235;
--color-gray-90-rgb: 243 243 248;
--color-gray-100-rgb: 249 249 252;
--color-danger-rgb:229 11 0;
--color-alert-rgb: 255 249 229;
--color-alert-1-rgb: 255 244 204;
}
}

Expand All @@ -116,8 +135,10 @@ button {
--color-red-dark-rgb: 255 36 26;
--color-orange-rgb: 255 164 36;
--color-orange-dark-rgb: 255 153 10;
--color-orange-60-rgb: 229 134 0;
--color-yellow-rgb: 255 214 51;
--color-yellow-dark-rgb: 255 209 26;
--color-yellow-60-rgb: 229 183 0;
--color-green-rgb: 72 208 106;
--color-green-dark-rgb: 52 203 90;
--color-pink-rgb: 255 71 105;
Expand All @@ -133,12 +154,16 @@ button {
--color-gray-30-rgb: 99 99 103;
--color-gray-40-rgb: 142 142 148;
--color-gray-50-rgb: 174 174 179;
--color-gray-54-rgb: 124 124 126;
--color-gray-55-rgb: 115 115 115;
--color-gray-60-rgb: 199 199 205;
--color-gray-70-rgb: 209 209 215;
--color-gray-80-rgb: 229 229 235;
--color-gray-90-rgb: 243 243 248;
--color-gray-100-rgb: 249 249 252;
--color-danger-rgb:229 11 0;
--color-alert-rgb: 255 249 229;
--color-alert-1-rgb: 255 244 204;
}

:root:not(.dark) {
Expand All @@ -150,8 +175,10 @@ button {
--color-red-dark-rgb: 230 11 0;
--color-orange-rgb: 255 149 0;
--color-orange-dark-rgb: 230 134 0;
--color-orange-60-rgb: 229 134 0;
--color-yellow-rgb: 255 204 0;
--color-yellow-dark-rgb: 230 184 0;
--color-yellow-60-rgb: 229 183 0;
--color-green-rgb: 50 195 86;
--color-green-dark-rgb: 45 174 77;
--color-pink-rgb: 255 36 76;
Expand All @@ -167,11 +194,15 @@ button {
--color-gray-30-rgb: 199 199 205;
--color-gray-40-rgb: 174 174 179;
--color-gray-50-rgb: 142 142 148;
--color-gray-54-rgb: 124 124 126;
--color-gray-55-rgb: 115 115 115;
--color-gray-60-rgb: 99 99 103;
--color-gray-70-rgb: 72 72 75;
--color-gray-80-rgb: 58 58 59;
--color-gray-90-rgb: 44 44 48;
--color-gray-100-rgb: 24 24 27;
--color-danger-rgb:229 11 0;
--color-alert-rgb: 255 249 229;
--color-alert-1-rgb: 255 244 204;
}
}
Loading
Loading