Files
supabase/apps/studio/tailwind.config.js
kemal.earth 908da85225 feat(studio): nano exhaustion indicators (#44395)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

This PR introduces some visual indicators when a Nano project is
exhausting resources on Pro plus plans. The indicator tells the user a
free upgrade to Micro is available via a flashing Nano badge that guides
them to Upgrade and a global banner that notifies them resources are
near exhaustion, please upgrade for free.

| Project List | Project View |
|--------|--------|
| <img width="412" height="223" alt="Screenshot 2026-03-31 at 16 11 19"
src="https://github.com/user-attachments/assets/0d0e7727-e4eb-4ade-9024-528a2501596c"
/> | <img width="1256" height="618" alt="Screenshot 2026-03-31 at 16 11
10"
src="https://github.com/user-attachments/assets/b664e020-2b18-4842-8e64-4e49fac69eef"
/> |

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Free Micro Upgrade banner appears for nano-tier projects near compute
exhaustion with animated visuals and an “Upgrade for free” CTA.
* Compute badge highlights near-exhaustion with glow/shimmer and an
animated upgrade indicator; optional icon support added.
* Per-project banner dismissal is saved locally; dismissed banners
remain hidden.
* **Tests**
  * Added tests covering banner display and dismissal scenarios.
* **Chores**
* Added local-storage keys and telemetry events for banner and upgrade
interactions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-01 11:48:43 +01:00

124 lines
3.8 KiB
JavaScript

const config = require('config/tailwind.config')
module.exports = config({
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
// purge styles from grid library
'./../../packages/ui/src/**/*.{tsx,ts,js}',
'./../../packages/ui-patterns/src/**/*.{tsx,ts,js}',
],
plugins: [require('@tailwindcss/container-queries')],
theme: {
extend: {
fontSize: {
grid: '13px',
},
colors: {
/* typography */
'typography-body': {
light: 'hsl(var(--foreground-light))',
dark: 'hsl(var(--foreground-light))',
},
'typography-body-secondary': {
light: 'hsl(var(--foreground-lighter))',
dark: 'hsl(var(--foreground-lighter))',
},
'typography-body-strong': {
light: 'hsl(var(--foreground-default))',
dark: 'hsl(var(--foreground-default))',
},
'typography-body-faded': {
light: 'hsl(var(--foreground-muted))',
dark: 'hsl(var(--foreground-muted))',
},
/* Tables */
'table-body': {
light: 'hsl(var(--background-default))',
dark: 'hsl(var(--background-default))',
},
'table-header': {
light: 'hsl(var(--background-surface-100))',
dark: 'hsl(var(--background-surface-100))',
},
'table-footer': {
light: 'hsl(var(--background-surface-100))',
dark: 'hsl(var(--background-surface-100))',
},
'table-border': {
light: 'hsl(var(--border-default))',
dark: 'hsl(var(--border-default))',
},
/* Panels */
'panel-body': {
light: 'hsl(var(--background-surface-100))',
dark: 'hsl(var(--background-surface-100))',
},
'panel-header': {
light: 'hsl(var(--background-surface-100))',
dark: 'hsl(var(--background-surface-100))',
},
'panel-footer': {
light: 'hsl(var(--background-surface-100))',
dark: 'hsl(var(--background-surface-100))',
},
'panel-border': {
light: 'hsl(var(--border-default))',
dark: 'hsl(var(--border-default))',
},
'panel-border-interior': {
light: 'hsl(var(--border-muted))',
dark: 'hsl(var(--border-muted))',
},
'panel-border-hover': {
light: 'hsl(var(--border-muted))',
dark: 'hsl(var(--border-muted))',
},
},
animation: {
shimmer: 'shimmer 2s infinite linear',
'badge-shimmer': 'badge-shimmer 3s ease-in-out infinite',
'badge-pulse': 'badge-pulse 3s ease-in-out infinite',
'chevron-up': 'chevron-up 2s ease-in-out infinite',
sway: 'sway 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
},
keyframes: {
shimmer: {
'0%': {
'background-position': '-1000px 0',
},
'100%': {
'background-position': '1000px 0',
},
},
'badge-shimmer': {
'0%': { transform: 'rotate(-45deg) translateX(-100%)', opacity: '0' },
'10%': { opacity: '1' },
'40%': { opacity: '1' },
'50%': { transform: 'rotate(-45deg) translateX(100%)', opacity: '0' },
'100%': { transform: 'rotate(-45deg) translateX(100%)', opacity: '0' },
},
'badge-pulse': {
'0%, 100%': { opacity: '1' },
'50%': { opacity: '0.8' },
},
'chevron-up': {
'0%, 100%': { opacity: '0.2' },
'50%': { opacity: '1' },
},
sway: {
'0%, 100%': {
transform: 'rotate(-10deg) scale(1.5) translateY(4rem)',
},
'50%': {
transform: 'rotate(10deg) scale(1.5) translateY(2rem)',
},
},
},
},
},
})