From 5a4e09bfdd230044db109163ace193456fad101b Mon Sep 17 00:00:00 2001 From: jaaaaavier Date: Tue, 9 Jun 2026 14:30:11 +0200 Subject: [PATCH 1/5] fix: use npm publish instead of yarn publish in GitHub registry workflow yarn publish prompts for version confirmation interactively, causing the CI job to hang and fail silently. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/publish-github.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml index c333cbb..d1a9258 100644 --- a/.github/workflows/publish-github.yml +++ b/.github/workflows/publish-github.yml @@ -30,6 +30,6 @@ jobs: run: yarn build - name: Publish to GitHub - run: yarn publish + run: npm publish --scope=@internxt --access public env: NODE_AUTH_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} From 9d40cbef1ee69151e3ddc04882322be4c2dc5600 Mon Sep 17 00:00:00 2001 From: jaaaaavier Date: Tue, 16 Jun 2026 12:57:08 +0200 Subject: [PATCH 2/5] feat: version update --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", From 0c5da67a2113c938585099f2a3a95b9ea5b635ee Mon Sep 17 00:00:00 2001 From: jaaaaavier Date: Tue, 16 Jun 2026 12:59:56 +0200 Subject: [PATCH 3/5] feat: usage warnning banner --- .../usageBanner/UsageWarningBanner.tsx | 101 ++++++++++++++++++ .../notifications/usageBanner/index.ts | 2 + src/components/index.ts | 1 + src/index.scss | 31 ++++++ .../components/usageBanner/Banner.stories.tsx | 71 ++++++++++++ 5 files changed, 206 insertions(+) create mode 100644 src/components/feedback/notifications/usageBanner/UsageWarningBanner.tsx create mode 100644 src/components/feedback/notifications/usageBanner/index.ts create mode 100644 src/stories/components/usageBanner/Banner.stories.tsx 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/index.scss b/src/index.scss index d244bc0..eb6b167 100644 --- a/src/index.scss +++ b/src/index.scss @@ -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)); @@ -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 { @@ -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; @@ -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 { @@ -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; @@ -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; } } @@ -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; @@ -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) { @@ -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; @@ -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; } } diff --git a/src/stories/components/usageBanner/Banner.stories.tsx b/src/stories/components/usageBanner/Banner.stories.tsx new file mode 100644 index 0000000..ac5946d --- /dev/null +++ b/src/stories/components/usageBanner/Banner.stories.tsx @@ -0,0 +1,71 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { fn } from 'storybook/test'; +import { UsageWarningBanner } from '@/components/feedback/notifications/usageBanner'; + +const meta: Meta = { + 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, + }, +}; From 1f4d123b85f32e5ad8ad02c6b7c22afa5255d280 Mon Sep 17 00:00:00 2001 From: jaaaaavier Date: Tue, 16 Jun 2026 13:00:57 +0200 Subject: [PATCH 4/5] feat: sidenav storage update --- .../navigation/sidenav/SidenavStorage.tsx | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) 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 = ({ )}
+
Date: Tue, 16 Jun 2026 13:04:51 +0200 Subject: [PATCH 5/5] Update publish-github.yml --- .github/workflows/publish-github.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml index d1a9258..c333cbb 100644 --- a/.github/workflows/publish-github.yml +++ b/.github/workflows/publish-github.yml @@ -30,6 +30,6 @@ jobs: run: yarn build - name: Publish to GitHub - run: npm publish --scope=@internxt --access public + run: yarn publish env: NODE_AUTH_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}