From 0abe792889b41e68eebcea41cd25f700d07e7a53 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Wed, 13 May 2026 14:14:58 +0300 Subject: [PATCH] chore: Migrate the main Tailwind JS config to CSS (#45686) This PR migrates the JS config for Tailwind into a CSS config. As such, all variables have been defined as CSS variables and they're using the specialized Tailwind syntax for generating utility classes. Beside the migration, these changes were also added: - Added `tailwind.config.css` to few packages to make the Tailwind Intellisense work. - Migrated away from Radix style color classes to our defined classes, the values will remain the same. - Most of the CSS is generated by scripts, they'll be removed in next PRs. * Removed redundant `border-light` classes from several components since it was undefined. * Removed redundant `text-strong` classes from several components since it was undefined. How to test: - Open all apps, compare the UI (mainly colors) to builds from #45417 and try to find a difference. ## Summary by CodeRabbit * **Style** * Harmonized color variable usages and updated UI color references (affects palettes, charts, gradients, hero illustrations, and scrollbars). * Tweaked border, tab, and selection visuals across components. * **New Features** * Added a suite of theme animations and refined typography presets used by site prose and docs. * **Refactor** * Overhauled Tailwind/theme configuration and color token generation for more consistent theming. --- .../components/color-palette.tsx | 6 +- .../content/docs/color-usage.mdx | 2 + .../Payment/PaymentMethods/CreditCard.tsx | 2 +- .../interfaces/Connect/ConnectionPanel.tsx | 2 +- .../Connect/ConnectionParameters.tsx | 2 +- .../Connect/DatabaseConnectionString.tsx | 2 +- .../components/layouts/Tabs/ActionCard.tsx | 2 +- apps/studio/components/ui/ActionCard.tsx | 2 +- .../components/ui/Charts/Charts.constants.ts | 17 +- apps/studio/styles/ui.css | 2 +- apps/www/components/Charts/PGCharts.tsx | 16 +- apps/www/components/OpenSource/OSSHero.tsx | 10 +- apps/www/components/OpenSource/Repos.tsx | 13 +- apps/www/components/Panel/Panel.tsx | 2 +- apps/www/pages/realtime.tsx | 6 +- apps/www/styles/globals.css | 5 +- packages/common/tailwind.config.css | 4 + packages/common/tailwind.config.js | 1 - packages/config/css/animations.css | 298 +++++++ packages/config/css/base.css | 17 + packages/config/css/brand-vars.css | 44 + packages/config/css/colors.css | 432 ++++++++++ packages/config/css/radix-vars.css | 806 ++++++++++++++++++ packages/config/css/theme.css | 563 ++++++++++++ packages/config/css/utilities.css | 71 ++ packages/config/css/variants.css | 30 + packages/config/default-colors.js | 90 -- packages/config/package.json | 8 +- packages/config/scripts/generate-colors.js | 251 ++++++ .../config/scripts/generate-radix-vars.js | 84 ++ .../scripts/tw-extend-color-snapshot.js} | 0 packages/config/tailwind.config.css | 37 +- packages/config/tailwind.config.js | 449 ---------- packages/config/typography.config.js | 232 +++++ packages/config/ui.config.js | 441 ---------- packages/dev-tools/tailwind.config.css | 4 + packages/marketing/tailwind.config.css | 4 + .../src/multi-select/multi-select.tsx | 2 +- packages/ui-patterns/tailwind.config.css | 4 + packages/ui-patterns/tailwind.config.js | 1 - .../ui/src/components/shadcn/ui/command.tsx | 2 +- .../components/shadcn/ui/dropdown-menu.tsx | 6 +- packages/ui/tailwind.config.css | 4 + packages/ui/tailwind.config.js | 1 - pnpm-lock.yaml | 36 +- 45 files changed, 2938 insertions(+), 1075 deletions(-) create mode 100644 packages/common/tailwind.config.css delete mode 100644 packages/common/tailwind.config.js create mode 100644 packages/config/css/animations.css create mode 100644 packages/config/css/base.css create mode 100644 packages/config/css/brand-vars.css create mode 100644 packages/config/css/colors.css create mode 100644 packages/config/css/radix-vars.css create mode 100644 packages/config/css/theme.css create mode 100644 packages/config/css/utilities.css create mode 100644 packages/config/css/variants.css delete mode 100644 packages/config/default-colors.js create mode 100644 packages/config/scripts/generate-colors.js create mode 100644 packages/config/scripts/generate-radix-vars.js rename packages/{ui/build/css/tw-extend/color.js => config/scripts/tw-extend-color-snapshot.js} (100%) delete mode 100644 packages/config/tailwind.config.js create mode 100644 packages/config/typography.config.js delete mode 100644 packages/config/ui.config.js create mode 100644 packages/dev-tools/tailwind.config.css create mode 100644 packages/marketing/tailwind.config.css create mode 100644 packages/ui-patterns/tailwind.config.css delete mode 100644 packages/ui-patterns/tailwind.config.js create mode 100644 packages/ui/tailwind.config.css delete mode 100644 packages/ui/tailwind.config.js diff --git a/apps/design-system/components/color-palette.tsx b/apps/design-system/components/color-palette.tsx index 2f8f3012bd7..024b7312f8a 100644 --- a/apps/design-system/components/color-palette.tsx +++ b/apps/design-system/components/color-palette.tsx @@ -56,11 +56,11 @@ const ColorPalette = () => {
{name}
{SCALE_STEPS.map((step) => { - const reference = `var(--colors-${slug}${step})` + const reference = `var(--color-${slug}-${step * 100})` const isCopied = copied === reference return ( ) diff --git a/apps/design-system/content/docs/color-usage.mdx b/apps/design-system/content/docs/color-usage.mdx index b8f5f16b555..b62441323eb 100644 --- a/apps/design-system/content/docs/color-usage.mdx +++ b/apps/design-system/content/docs/color-usage.mdx @@ -75,4 +75,6 @@ These can also be accessed with `foreground`. Like `text-foreground-light`. Every Radix scale exposed via `--colors-{name}{1..12}`. Click a swatch to copy the CSS variable reference. The colors are taken from `@radix-ui/colors` v0.1.9 except the `Brand` and `Scale` colors. +The `Brand` colors issue of not being uniform across the whole scale is known, they need to be refactored. The `brand-200` variable in the dark themes is also bad. + diff --git a/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CreditCard.tsx b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CreditCard.tsx index 13638070517..4c9b0429188 100644 --- a/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CreditCard.tsx +++ b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CreditCard.tsx @@ -125,7 +125,7 @@ const CreditCard = ({

{isSpt && spt && ( -
+

Managed via Stripe Projects ยท Token ending in{' '} {spt.last4} diff --git a/apps/studio/components/interfaces/Connect/ConnectionPanel.tsx b/apps/studio/components/interfaces/Connect/ConnectionPanel.tsx index 07e707155f4..82ca9c7a8da 100644 --- a/apps/studio/components/interfaces/Connect/ConnectionPanel.tsx +++ b/apps/studio/components/interfaces/Connect/ConnectionPanel.tsx @@ -175,7 +175,7 @@ export const ConnectionPanel = ({ onCopyCallback={onCopyCallback} /> {notice && ( -

+
{notice?.map((text: string) => (

{text} diff --git a/apps/studio/components/interfaces/Connect/ConnectionParameters.tsx b/apps/studio/components/interfaces/Connect/ConnectionParameters.tsx index 4b3348b5e41..7290919f6a0 100644 --- a/apps/studio/components/interfaces/Connect/ConnectionParameters.tsx +++ b/apps/studio/components/interfaces/Connect/ConnectionParameters.tsx @@ -27,7 +27,7 @@ export const ConnectionParameters = ({ parameters }: ConnectionParametersProps)

๐Ÿค”

"); +} diff --git a/packages/config/css/variants.css b/packages/config/css/variants.css new file mode 100644 index 00000000000..db88749e830 --- /dev/null +++ b/packages/config/css/variants.css @@ -0,0 +1,30 @@ +/* + * Custom Tailwind v4 variants migrated from: + * - packages/config/ui.config.js addVariant calls + * - packages/config/tailwind.config.js addVariant + darkMode setting + */ + +/* + * `darkMode: ['class', '[data-theme*="dark"]']` from the v3 config is replaced + * by this @custom-variant. The :where() wrapper keeps specificity at 0 so + * the dark utilities don't override unrelated rules. + */ +@custom-variant dark (&:where([data-theme*='dark'] *, [data-theme*='dark'])); + +@custom-variant data-open-parent ([data-state='open'] &); +@custom-variant data-closed-parent ([data-state='closed'] &); +@custom-variant data-open (&[data-state='open']); +@custom-variant data-closed (&[data-state='closed']); + +/* may be unused โ€” verify before pruning */ +@custom-variant data-show (&[data-state='show']); +/* may be unused โ€” verify before pruning */ +@custom-variant data-hide (&[data-state='hide']); + +@custom-variant data-checked (&[data-state='checked']); +/* may be unused โ€” verify before pruning */ +@custom-variant data-unchecked (&[data-state='unchecked']); + +@custom-variant aria-expanded (&[aria-expanded='true']); + +@custom-variant not-disabled (&:not(:disabled)); diff --git a/packages/config/default-colors.js b/packages/config/default-colors.js deleted file mode 100644 index 3373e649d3b..00000000000 --- a/packages/config/default-colors.js +++ /dev/null @@ -1,90 +0,0 @@ -module.exports = { - brand: { - brand1: 'hsla(153, 80%, 99%, 1)', - brand2: 'hsla(148, 88%, 97%, 1)', - brand3: 'hsla(148, 76%, 95%, 1)', - brand4: 'hsla(148, 68%, 92%, 1)', - brand5: 'hsla(148, 59%, 88%, 1)', - brand6: 'hsla(149, 43%, 82%, 1)', - brand7: 'hsla(149, 43%, 69%, 1)', - brand8: 'hsla(154, 55%, 45%, 1)', - brand9: 'hsla(153, 60%, 53%, 1)', - brand10: 'hsla(153, 50%, 50%, 1)', - brand11: 'hsla(153, 50%, 34%, 1)', - brand12: 'hsla(153, 40%, 12%, 1)', - }, - brandDark: { - brand1: 'hsla(153, 75%, 6%, 1)', - brand2: 'hsla(153, 73%, 7%, 1)', - brand3: 'hsla(154, 69%, 9%, 1)', - brand4: 'hsla(154, 67%, 11%, 1)', - brand5: 'hsla(154, 66%, 13%, 1)', - brand6: 'hsla(154, 64%, 17%, 1)', - brand7: 'hsla(154, 62%, 22%, 1)', - brand8: 'hsla(153, 60%, 28%, 1)', - brand9: 'hsla(153, 60%, 53%, 1)', - brand10: 'hsla(153, 60%, 70%, 1)', - brand11: 'hsla(153, 60%, 50%, 1)', - brand12: 'hsla(153, 60%, 95%, 1)', - }, - contrast: { - 'brand-hiContrast': 'hsl(var(--brand-default))', - 'brand-loContrast': 'hsl(var(--brand-300))', - }, - scale: { - scale1: 'var(--colors-slate1)', - scale2: 'var(--colors-slate2)', - scale3: 'var(--colors-slate3)', - scale4: 'var(--colors-slate4)', - scale5: 'var(--colors-slate5)', - scale6: 'var(--colors-slate6)', - scale7: 'var(--colors-slate7)', - scale8: 'var(--colors-slate8)', - scale9: 'var(--colors-slate9)', - scale10: 'var(--colors-slate10)', - scale11: 'var(--colors-slate11)', - scale12: 'var(--colors-slate12)', - }, - scaleA: { - scaleA1: 'var(--colors-slateA1)', - scaleA2: 'var(--colors-slateA2)', - scaleA3: 'var(--colors-slateA3)', - scaleA4: 'var(--colors-slateA4)', - scaleA5: 'var(--colors-slateA5)', - scaleA6: 'var(--colors-slateA6)', - scaleA7: 'var(--colors-slateA7)', - scaleA8: 'var(--colors-slateA8)', - scaleA9: 'var(--colors-slateA9)', - scaleA10: 'var(--colors-slateA10)', - scaleA11: 'var(--colors-slateA11)', - scaleA12: 'var(--colors-slateA12)', - }, - scaleDark: { - scale1: 'var(--colors-gray1)', - scale2: 'var(--colors-gray2)', - scale3: 'var(--colors-gray3)', - scale4: 'var(--colors-gray4)', - scale5: 'var(--colors-gray5)', - scale6: 'var(--colors-gray6)', - scale7: 'var(--colors-gray7)', - scale8: 'var(--colors-gray8)', - scale9: 'var(--colors-gray9)', - scale10: 'var(--colors-gray10)', - scale11: '#bbbbbb', - scale12: 'var(--colors-gray12)', - }, - scaleADark: { - scaleA1: 'var(--colors-grayA1)', - scaleA2: 'var(--colors-grayA2)', - scaleA3: 'var(--colors-grayA3)', - scaleA4: 'var(--colors-grayA4)', - scaleA5: 'var(--colors-grayA5)', - scaleA6: 'var(--colors-grayA6)', - scaleA7: 'var(--colors-grayA7)', - scaleA8: 'var(--colors-grayA8)', - scaleA9: 'var(--colors-grayA9)', - scaleA10: 'var(--colors-grayA10)', - scaleA11: 'var(--colors-grayA11)', - scaleA12: 'var(--colors-grayA12)', - }, -} diff --git a/packages/config/package.json b/packages/config/package.json index 5ce20f9026a..87b3bc63796 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -8,15 +8,11 @@ "clean": "rimraf node_modules" }, "dependencies": { - "@mertasan/tailwindcss-variables": "^2.2.3", - "@radix-ui/colors": "^0.1.8", "@tailwindcss/forms": "^0.5.11", - "@tailwindcss/typography": "^0.5.19", - "deepmerge": "^4.2.2", - "mini-svg-data-uri": "^1.4.3", - "tailwindcss-radix": "^2.0.0" + "@tailwindcss/typography": "^0.5.19" }, "devDependencies": { + "@radix-ui/colors": "^0.1.8", "@tailwindcss/postcss": "^4.2.4", "tailwindcss": "catalog:", "tailwindcss-animate": "^1.0.7", diff --git a/packages/config/scripts/generate-colors.js b/packages/config/scripts/generate-colors.js new file mode 100644 index 00000000000..f6f7ba43387 --- /dev/null +++ b/packages/config/scripts/generate-colors.js @@ -0,0 +1,251 @@ +#!/usr/bin/env node +/** + * Emits packages/config/css/colors.css and packages/config/css/theme.css + * from packages/config/scripts/tw-extend-color-snapshot.js. Run from + * packages/config/. + * + * - colors.css holds a :root block that declares the Radix/scale --color-* + * tokens, forwarding each one to its underlying --colors-{hue}{n} source + * var (which switches across light/dark themes via radix-vars.css). This + * is where the actual runtime value lives. Brand steps 200..600 forward + * to the semantic hsl(var(--brand-{step})) triplet instead of the raw + * --colors-brand{n} scale value. + * - theme.css holds the @theme inline block of --color-* design tokens + * that Tailwind v4 uses to generate bg-{name}, text-{name}, border-{name}, + * ring-{name}, plus directional border variants like border-r-{name}. + * Semantic tokens wrap their source HSL triplet in hsl(); Radix/scale + * tokens self-reference the matching var declared in :root (colors.css) + * so theme switching works at runtime without re-registering utilities. + * The file also contains hand-curated namespaced utility blocks + * (--background-color-*, --border-color-*, --text-color-*) that only + * generate utilities for a single Tailwind namespace, plus a deprecated + * --color-border-* block. + */ + +const fs = require('node:fs') +const path = require('node:path') + +// Snapshot of the v3 tw-extend/color.js (was deleted alongside the migration). +// Kept here as a generation source so the script remains runnable. +const colorEntries = require('./tw-extend-color-snapshot.js') + +const themeLines = [] +const rootLines = [] + +// Emit prefixed --color-* tokens for every semantic entry in the snapshot. +// `foo-DEFAULT` becomes `--color-foo`; everything else keeps its full key. +// `border-*` entries are intentionally skipped here โ€” they live in the +// deprecated --color-border-* block in `namespacedTokens` below, which is +// curated by hand (the snapshot has more border entries than we want to +// expose as the legacy `--color-border-*` namespace). +for (const [key, { cssVariable }] of Object.entries(colorEntries)) { + if (/var\(--core-/.test(cssVariable)) continue + if (key.startsWith('border-')) continue + const themeKey = key.replace(/-DEFAULT$/, '') + themeLines.push(` --color-${themeKey}: hsl(${cssVariable});`) +} + +// --- Hand-rolled additions that don't come from the snapshot --- + +// Radix hue palettes โ€” each --color-{hue}-{step} resolves to the raw hue var. +const radixHues = [ + 'amber', + 'blue', + 'crimson', + 'gold', + 'gray', + 'green', + 'indigo', + 'orange', + 'pink', + 'purple', + 'red', + 'slate', + 'tomato', + 'violet', + 'yellow', +] +for (const hue of radixHues) { + for (let i = 1; i <= 12; i++) { + const solid = `--color-${hue}-${i * 100}` + const alpha = `--color-${hue}A-${i * 100}` + rootLines.push(` ${solid}: var(--colors-${hue}${i});`) + rootLines.push(` ${alpha}: var(--colors-${hue}A${i});`) + themeLines.push(` ${solid}: var(${solid});`) + themeLines.push(` ${alpha}: var(${alpha});`) + } +} + +// blackA / whiteA โ€” neutral alpha scales. +for (let i = 1; i <= 12; i++) { + const black = `--color-blackA-${i * 100}` + const white = `--color-whiteA-${i * 100}` + rootLines.push(` ${black}: var(--colors-blackA${i});`) + rootLines.push(` ${white}: var(--colors-whiteA${i});`) + themeLines.push(` ${black}: var(${black});`) + themeLines.push(` ${white}: var(${white});`) +} + +// Supabase scale + scaleA. Underlying --colors-scale{n} / --colors-scaleA{n} +// are aliases (defined in radix-vars.css) that switch between slate/gray +// across light/dark themes. +for (let i = 1; i <= 12; i++) { + const solid = `--color-scale-${i * 100}` + const alpha = `--color-scaleA-${i * 100}` + rootLines.push(` ${solid}: var(--colors-scale${i});`) + rootLines.push(` ${alpha}: var(--colors-scaleA${i});`) + themeLines.push(` ${solid}: var(${solid});`) + themeLines.push(` ${alpha}: var(${alpha});`) +} + +// Supabase brand scale. Steps 200..600 are semantic: they forward to +// hsl(var(--brand-{step})) so light/dark themes can override the value. +// The other steps fall back to the raw --colors-brand{n} scale and get +// a self-referencing entry in theme.css; the semantic steps are already +// emitted to themeLines by the snapshot pass above (brand-200..600, +// brand-DEFAULT, brand-link), so we don't re-emit them here. +const SEMANTIC_BRAND_STEPS = new Set([2, 3, 4, 5, 6]) +for (let i = 1; i <= 12; i++) { + const solid = `--color-brand-${i * 100}` + if (SEMANTIC_BRAND_STEPS.has(i)) { + rootLines.push(` ${solid}: hsl(var(--brand-${i * 100}));`) + } else { + rootLines.push(` ${solid}: var(--colors-brand${i});`) + themeLines.push(` ${solid}: var(${solid});`) + } +} + +// Sidebar shadcn semantic tokens. +themeLines.push(` --color-sidebar: hsl(var(--sidebar-background));`) +themeLines.push(` --color-sidebar-foreground: hsl(var(--sidebar-foreground));`) +themeLines.push(` --color-sidebar-primary: hsl(var(--sidebar-primary));`) +themeLines.push(` --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));`) +themeLines.push(` --color-sidebar-accent: hsl(var(--sidebar-accent));`) +themeLines.push(` --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));`) +themeLines.push(` --color-sidebar-border: hsl(var(--sidebar-border));`) +themeLines.push(` --color-sidebar-ring: hsl(var(--sidebar-ring));`) + +// Misc semantic colors. +themeLines.push(` --color-hi-contrast: hsl(var(--foreground-default));`) +themeLines.push(` --color-lo-contrast: hsl(var(--background-alternative-default));`) + +// Namespaced utility blocks โ€” these intentionally use Tailwind v4's per- +// namespace --{property}-color-* keys so the values only generate utilities +// for a single namespace (bg-, border-, text-) rather than all of them. +const namespacedTokens = ` /* + * This will generate colors only for bg utilities. + */ + --background-color-200: hsl(var(--background-200)); + --background-color-default: hsl(var(--background-default)); + --background-color-muted: hsl(var(--background-muted)); + --background-color-alternative-200: hsl(var(--background-alternative-200)); + --background-color-alternative: hsl(var(--background-alternative-default)); + --background-color-selection: hsl(var(--background-selection)); + --background-color-control: hsl(var(--background-control)); + --background-color-surface-75: hsl(var(--background-surface-75)); + --background-color-surface-100: hsl(var(--background-surface-100)); + --background-color-surface-200: hsl(var(--background-surface-200)); + --background-color-surface-300: hsl(var(--background-surface-300)); + --background-color-surface-400: hsl(var(--background-surface-400)); + --background-color-overlay: hsl(var(--background-overlay-default)); + --background-color-overlay-hover: hsl(var(--background-overlay-hover)); + --background-color-button: hsl(var(--background-button-default)); + --background-color-dialog: hsl(var(--background-dialog-default)); + --background-color-dash-sidebar: hsl(var(--background-dash-sidebar)); + --background-color-dash-canvas: hsl(var(--background-dash-canvas)); + --background-color-studio: hsl(var(--background-200)); + + /* + * This will generate colors for all utilities, they'll have border in the name i.e. bg-border-overlay, text-border-muted + * This usage should be discouraged and deprecated. + */ + --color-border: hsl(var(--border-default)); + --color-border-overlay: hsl(var(--border-overlay)); + --color-border-muted: hsl(var(--border-muted)); + --color-border-control: hsl(var(--border-control)); + --color-border-strong: hsl(var(--border-strong)); + --color-border-stronger: hsl(var(--border-stronger)); + /* + * This will generate colors only for border utilities + * i.e. bg-muted and they'll only have border-color: var(--border-muted) rule + */ + --border-color-default: hsl(var(--border-default)); + --border-color-overlay: hsl(var(--border-overlay)); + --border-color-muted: hsl(var(--border-muted)); + --border-color-control: hsl(var(--border-control)); + --border-color-strong: hsl(var(--border-strong)); + --border-color-stronger: hsl(var(--border-stronger)); + --border-color-secondary: hsl(var(--border-secondary)); + --border-color-alternative: hsl(var(--border-alternative)); + --border-color-button: hsl(var(--border-button-default)); + --border-color-button-hover: hsl(var(--border-button-hover)); + + /* This will generate colors only for text utilities */ + --text-color-default: hsl(var(--foreground-default)); + --text-color-light: hsl(var(--foreground-light)); + --text-color-lighter: hsl(var(--foreground-lighter)); + --text-color-muted: hsl(var(--foreground-muted)); + --text-color-contrast: hsl(var(--foreground-contrast));` + +// Misc design tokens (font, breakpoint, sizing, radius, transform-origin) +// that lived in tailwind.config.js / ui.config.js theme blocks. +const miscTokens = ` /* misc theme tokens โ€” typography, breakpoints, sizing, transform-origin */ + --font-sans: var( + --font-custom, + Circular, + custom-font, + Helvetica Neue, + Helvetica, + Arial, + sans-serif + ); + --font-mono: var(--font-source-code-pro, Source Code Pro, Office Code Pro, Menlo, monospace); + --breakpoint-xs: 480px; + --width-listbox: 320px; + --radius-panel: 6px; + --spacing-content: 21px; + --spacing-card: var(--card-padding-x); + --transform-origin-dropdown: var(--radix-dropdown-menu-content-transform-origin); + --transform-origin-popover: var(--radix-popover-menu-content-transform-origin);` + +const colorsCss = `/* + * Auto-generated by scripts/generate-colors.js โ€” do not edit by hand. + * Run \`node scripts/generate-colors.js\` from packages/config/ to refresh. + * + * Declares the Radix/scale --color-* CSS variables in :root, each forwarding + * to its underlying --colors-{hue}{n} source (defined in radix-vars.css) + * which switches between light/dark themes. theme.css references these + * variables from its @theme inline block. + */ + +:root { +${rootLines.join('\n')} +} +` + +const themeCss = `/* + * Auto-generated by scripts/generate-colors.js โ€” do not edit by hand. + * Run \`node scripts/generate-colors.js\` from packages/config/ to refresh. + * + * Defines every Tailwind v4 --color-* design token. Tailwind generates + * bg-{name} / text-{name} / border-{name} / ring-{name} / border-{r,l,t,b}-{name} + * utilities for each entry. Semantic tokens wrap their HSL triplet in hsl(); + * Radix/scale tokens self-reference the var declared in colors.css :root. + * Source mappings: tw-extend-color-snapshot.js (semantic tokens) plus the + * Radix and scale aliases from radix-vars.css. + */ + +@theme inline { +${themeLines.join('\n')} + +${namespacedTokens} + +${miscTokens} +} +` + +const outDir = path.resolve(__dirname, '..', 'css') +fs.writeFileSync(path.join(outDir, 'colors.css'), colorsCss) +fs.writeFileSync(path.join(outDir, 'theme.css'), themeCss) +console.log(`wrote ${path.join(outDir, 'colors.css')} (${colorsCss.split('\n').length} lines)`) +console.log(`wrote ${path.join(outDir, 'theme.css')} (${themeCss.split('\n').length} lines)`) diff --git a/packages/config/scripts/generate-radix-vars.js b/packages/config/scripts/generate-radix-vars.js new file mode 100644 index 00000000000..69a4a1a677a --- /dev/null +++ b/packages/config/scripts/generate-radix-vars.js @@ -0,0 +1,84 @@ +#!/usr/bin/env node +/** + * Emits packages/config/css/radix-vars.css from @radix-ui/colors and the + * existing default-colors.js scale aliases. Run from packages/config/. + * + * One-shot generator โ€” committed alongside the CSS output so future updates + * (new Radix versions, new aliases) can be reproduced without searching for + * how the file was made. + */ + +const fs = require('node:fs') +const path = require('node:path') + +const radixUiColors = require('@radix-ui/colors') + +const excludedRadixColors = [ + 'bronze', + 'brown', + 'cyan', + 'grass', + 'olive', + 'mauve', + 'mint', + 'lime', + 'plum', + 'sage', + 'sand', + 'sky', + 'teal', +] + +const isExcluded = (key) => excludedRadixColors.some((ex) => key.startsWith(ex)) + +const lightEntries = [] +const darkEntries = [] + +for (const [key, palette] of Object.entries(radixUiColors)) { + if (isExcluded(key)) continue + const target = key.includes('Dark') ? darkEntries : lightEntries + for (const [varName, value] of Object.entries(palette)) { + target.push([varName, value]) + } +} + +const formatBlock = (selector, entries) => { + const lines = entries.map(([k, v]) => ` --colors-${k}: ${v};`).join('\n') + return `${selector} {\n${lines}\n}` +} + +const scaleAliasesLight = [] +const scaleAliasesDark = [] +for (let i = 1; i <= 12; i++) { + scaleAliasesLight.push([`scale${i}`, `var(--colors-slate${i})`]) + scaleAliasesLight.push([`scaleA${i}`, `var(--colors-slateA${i})`]) + if (i === 11) { + // Preserves the hard-coded value from default-colors.js scaleDark.scale11. + scaleAliasesDark.push([`scale${i}`, `#bbbbbb`]) + } else { + scaleAliasesDark.push([`scale${i}`, `var(--colors-gray${i})`]) + } + scaleAliasesDark.push([`scaleA${i}`, `var(--colors-grayA${i})`]) +} + +const header = `/* + * Auto-generated by scripts/generate-radix-vars.js โ€” do not edit by hand. + * Run \`node scripts/generate-radix-vars.js\` from packages/config/ to refresh. + * + * Mirrors the CSS variables that the legacy @mertasan/tailwindcss-variables + * plugin used to emit from ui.config.js's generateCssVariables() and + * default-colors.js's scale/scaleA aliases. + */ +` + +const output = [ + header, + formatBlock(':root', [...lightEntries, ...scaleAliasesLight]), + '', + formatBlock("[data-theme*='dark']", [...darkEntries, ...scaleAliasesDark]), + '', +].join('\n') + +const outPath = path.resolve(__dirname, '..', 'css', 'radix-vars.css') +fs.writeFileSync(outPath, output) +console.log(`wrote ${outPath} (${output.split('\n').length} lines)`) diff --git a/packages/ui/build/css/tw-extend/color.js b/packages/config/scripts/tw-extend-color-snapshot.js similarity index 100% rename from packages/ui/build/css/tw-extend/color.js rename to packages/config/scripts/tw-extend-color-snapshot.js diff --git a/packages/config/tailwind.config.css b/packages/config/tailwind.config.css index e773125bea8..2a6e4546c0d 100644 --- a/packages/config/tailwind.config.css +++ b/packages/config/tailwind.config.css @@ -1,6 +1,41 @@ @import 'tailwindcss'; + +/* JS plugins. Tailwind v4 supports `@plugin` for both v3-style JS plugins and + * v4-native packages. The two local plugins use theme('spacing') / + * theme('transitionDuration') which resolve from v4's @theme tokens. */ +@plugin "@tailwindcss/typography"; +@plugin "@tailwindcss/forms"; +@plugin "tailwindcss-animate"; +@plugin "./tailwind-plugins/hit-area"; +@plugin "./tailwind-plugins/motion-safe-transition"; + +/* Raw color values + per-theme overrides. Must come before colors.css so the + * --colors-* references resolve to a value. */ +@import './css/radix-vars.css'; +@import './css/brand-vars.css'; @import '../ui/build/css/source/global.css'; @import '../ui/build/css/themes/dark.css'; @import '../ui/build/css/themes/light.css'; + +/* Reset Tailwind's built-in color tokens before re-defining our own, so e.g. + * --color-amber-* doesn't ship a default Tailwind value alongside the Radix + * one. */ @import './unset-tw-colors.css'; -@config './tailwind.config.js'; + +/* Theme tokens + utilities + variants + base layer. colors.css declares the + * Radix/scale CSS variables in :root; theme.css consumes them from + * @theme inline so Tailwind generates the utilities. */ +@import './css/colors.css'; +@import './css/theme.css'; +@import './css/animations.css'; +@import './css/utilities.css'; +@import './css/variants.css'; +@import './css/base.css'; + +/* + * Typography prose / prose-toc / prose-docs overrides live in a focused JS + * config because @tailwindcss/typography reads its theme.typography options + * from the JS config โ€” not from CSS @theme tokens. See: + * https://github.com/tailwindlabs/tailwindcss-typography#customizing-the-css + */ +@config './typography.config.js'; diff --git a/packages/config/tailwind.config.js b/packages/config/tailwind.config.js deleted file mode 100644 index a32b36e179a..00000000000 --- a/packages/config/tailwind.config.js +++ /dev/null @@ -1,449 +0,0 @@ -const ui = require('./ui.config.js') -const deepMerge = require('deepmerge') -const plugin = require('tailwindcss/plugin') - -const color = require('./../ui/build/css/tw-extend/color') - -/** - * - */ -let colorExtend = {} -Object.values(color).map((x, i) => { - colorExtend[Object.keys(color)[i]] = `hsl(${x.cssVariable})` // x.cssVariable -}) - -// console.log('colorExtend', colorExtend) -// console.log('colorExtend kebabToNested', kebabToNested(colorExtend)) - -// console.log('colorExtend', kebabToNested(colorExtend).colors.gray) - -/** - * Generates Tailwind colors for the theme - */ -function generateTwColorClasses(globalKey, twAttributes) { - let classes = {} - Object.values(twAttributes).map((attr, i) => { - const attrKey = Object.keys(twAttributes)[i] - - if (attrKey.includes(globalKey)) { - const keySplit = attrKey.split('-').splice(1).join('-') - - let payload = { - [keySplit]: `hsl(${attr.cssVariable})`, - } - - if (keySplit == 'DEFAULT') { - // includes a 'default' duplicate - // this allows for classes like `border-default` which is the same as `border` - payload = { - ...payload, - default: `hsl(${attr.cssVariable})`, - } - } - - classes = { - ...classes, - ...payload, - } - } - }) - /** - * mutate object into nested object for tailwind theme structure - */ - const nestedClasses = kebabToNested(classes) - // return, but nest the keys if they are kebab case named - return nestedClasses -} - -/** - * Helper to convert kebab named keys in object to nested nodes - */ -function kebabToNested(obj) { - const result = {} - for (const [key, value] of Object.entries(obj)) { - const parts = key.split('-') - let currentObj = result - for (let i = 0; i < parts.length; i++) { - const part = parts[i] === 'DEFAULT' ? parts[i] : parts[i].toLowerCase() // convert key to lowercase - if (!currentObj[part]) { - currentObj[part] = {} - } - if (i === parts.length - 1) { - if (typeof value === 'object') { - currentObj[part] = kebabToNested(value) // recursively convert nested objects - } else { - currentObj[part] = value.toString().toLowerCase() // convert value to lowercase - } - } else { - currentObj = currentObj[part] - } - } - } - return result -} - -/** - * Main theme config - */ -const uiConfig = ui({ - darkMode: ['class', '[data-theme*="dark"]'], - theme: { - /** - * Spread all theme colors and custom generated colors into theme - */ - textColor: (theme) => ({ - ...theme('colors'), - ...generateTwColorClasses('foreground', color), - }), - backgroundColor: (theme) => ({ - ...theme('colors'), - ...generateTwColorClasses('background', color), - /* - * custom background re-maps - */ - studio: `hsl(var(--background-200))`, - }), - borderColor: (theme) => ({ - ...theme('colors'), - ...generateTwColorClasses('border', color), - }), - extend: { - colors: { - ...kebabToNested(colorExtend), - sidebar: { - DEFAULT: 'hsl(var(--sidebar-background))', - foreground: 'hsl(var(--sidebar-foreground))', - primary: 'hsl(var(--sidebar-primary))', - 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', - accent: 'hsl(var(--sidebar-accent))', - 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', - border: 'hsl(var(--sidebar-border))', - ring: 'hsl(var(--sidebar-ring))', - }, - }, - - typography: ({ theme }) => ({ - // Removal of backticks in code blocks for tailwind v3.0 - // https://github.com/tailwindlabs/tailwindcss-typography/issues/135 - DEFAULT: { - css: { - 'code::before': { - content: '""', - }, - 'code::after': { - content: '""', - }, - '--tw-prose-body': 'hsl(var(--foreground-light))', - '--tw-prose-headings': 'hsl(var(--foreground-default))', - '--tw-prose-lead': 'hsl(var(--foreground-light))', - '--tw-prose-links': 'hsl(var(--foreground-light))', - '--tw-prose-bold': 'hsl(var(--foreground-light))', - '--tw-prose-counters': 'hsl(var(--foreground-light))', - '--tw-prose-bullets': 'hsl(var(--foreground-muted))', - '--tw-prose-hr': 'hsl(var(--background-surface-300))', - '--tw-prose-quotes': 'hsl(var(--foreground-light))', - '--tw-prose-quote-borders': 'hsl(var(--background-surface-300))', - '--tw-prose-captions': 'hsl(var(--border-strong))', - '--tw-prose-code': 'hsl(var(--foreground-default))', - '--tw-prose-pre-code': 'hsl(var(--foreground-muted))', - '--tw-prose-pre-bg': 'hsl(var(--background-surface-200))', - '--tw-prose-th-borders': 'hsl(var(--background-surface-300))', - '--tw-prose-td-borders': 'hsl(var(--background-default))', - '--tw-prose-invert-body': 'hsl(var(--background-default))', - '--tw-prose-invert-headings': theme('colors.white'), - '--tw-prose-invert-lead': 'hsl(var(--background-surface-300))', - '--tw-prose-invert-links': theme('colors.white'), - '--tw-prose-invert-bold': theme('colors.white'), - '--tw-prose-invert-counters': 'hsl(var(--background-surface-200))', - '--tw-prose-invert-bullets': 'hsl(var(--background-selection))', - '--tw-prose-invert-hr': 'hsl(var(--border-strong))', - '--tw-prose-invert-quotes': 'hsl(var(--background-alternative-default))', - '--tw-prose-invert-quote-borders': 'hsl(var(--border-strong))', - '--tw-prose-invert-captions': 'hsl(var(--background-surface-200))', - // the following are typography overrides - // examples can be seen here โ€”> https://github.com/tailwindlabs/tailwindcss-typography/blob/master/src/styles.js - // reset all header font weights - h4: { - // override font size - fontSize: '1.15em', - }, - h5: { - // h5 not included in --tw-prose-headings - color: theme('colors.scale[1200]'), - }, - 'h1, h2, h3, h4, h5, h6': { - fontWeight: '400', - }, - 'article h2, article h3, article h4, article h5, article h6': { - marginTop: '2em', - marginBottom: '1em', - }, - p: { - fontWeight: '400', - }, - strong: { - fontWeight: '500', - }, - pre: { - background: 'none', - padding: 0, - marginBottom: '32px', - }, - ul: { - listStyleType: 'none', - paddingLeft: '1rem', - }, - 'ul li': { - position: 'relative', - }, - 'ul li::before': { - position: 'absolute', - top: '0.75rem', - left: '-1rem', - height: '0.125rem', - width: '0.5rem', - borderRadius: '0.25rem', - backgroundColor: 'hsl(var(--border-strong))', - content: '""', - }, - ol: { - paddingLeft: '1rem', - counterReset: 'item', - listStyleType: 'none', - marginBottom: '3rem', - }, - 'ol>li': { - display: 'block', - position: 'relative', - paddingLeft: '1rem', - }, - 'ol>li::before': { - position: 'absolute', - top: '0.25rem', - left: '-1rem', - height: '1.2rem', - width: '1.2rem', - borderRadius: '0.25rem', - backgroundColor: 'hsl(var(--background-surface-100))', - border: '1px solid hsl(var(--border-default))', - content: 'counter(item) " "', - counterIncrement: 'item', - fontSize: '12px', - color: 'hsl(var(--foreground-muted))', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }, - - 'p img': { - border: '1px solid hsl(var(--border-muted))', - borderRadius: '4px', - overflow: 'hidden', - }, - iframe: { - border: '1px solid ' + theme('borderColor.DEFAULT'), - borderRadius: theme('borderRadius.lg'), - }, - td: { - borderBottom: '1px solid ' + 'hsl(var(--background-surface-200))', - }, - code: { - fontWeight: '400', - padding: '0.2rem 0.4rem', - backgroundColor: 'hsl(var(--background-surface-200))', - border: '1px solid ' + 'hsl(var(--background-surface-300))', - borderRadius: theme('borderRadius.lg'), - }, - a: { - position: 'relative', - transition: 'all 0.18s ease', - paddingBottom: '2px', - fontWeight: '400', - opacity: 1, - color: 'hsl(var(--foreground-default))', - textDecorationLine: 'underline', - textDecorationColor: 'hsl(var(--foreground-muted))', - textDecorationThickness: '1px', - textUnderlineOffset: '2px', - }, - 'a:hover': { - textDecorationColor: 'hsl(var(--foreground-default))', - }, - figcaption: { - color: 'hsl(var(--foreground-muted))', - fontFamily: 'Office Code Pro, monospace', - }, - 'figure.quote-figure p:first-child': { - marginTop: '0 !important', - }, - 'figure.quote-figure p:last-child': { - marginBottom: '0 !important', - }, - figure: { - margin: '3rem 0', - }, - 'figure img': { - margin: '0 !important', - }, - }, - }, - - toc: { - css: { - ul: { - 'list-style-type': 'none', - 'padding-left': 0, - margin: 0, - li: { - 'padding-left': 0, - }, - a: { - display: 'block', - marginBottom: '0.4rem', - 'text-decoration': 'none', - fontSize: '0.8rem', - fontWeight: '200', - color: 'hsl(var(--foreground-light))', - '&:hover': { - color: 'hsl(var(--foreground-default))', - }, - 'font-weight': '400', - }, - // margin: 0, - ul: { - 'list-style-type': 'none', - li: { - marginTop: '0.2rem', - marginBottom: '0.2rem', - 'padding-left': '0 !important', - 'margin-left': '0.5rem', - }, - a: { - fontWeight: '200', - color: 'hsl(var(--foreground-lighter))', - '&:hover': { - color: 'hsl(var(--foreground-default))', - }, - }, - }, - }, - }, - }, - // used in auto docs - docs: { - css: { - '--tw-prose-body': 'hsl(var(--foreground-light))', - '--tw-prose-headings': 'hsl(var(--foreground-default))', - '--tw-prose-lead': 'hsl(var(--foreground-light))', - '--tw-prose-links': 'hsl(var(--brand-500))', - '--tw-prose-bold': 'hsl(var(--foreground-light))', - '--tw-prose-counters': 'hsl(var(--foreground-light))', - '--tw-prose-bullets': 'hsl(var(--foreground-muted))', - '--tw-prose-hr': 'hsl(var(--background-surface-300))', - '--tw-prose-quotes': 'hsl(var(--foreground-light))', - '--tw-prose-quote-borders': 'hsl(var(--background-surface-300))', - '--tw-prose-captions': 'hsl(var(--border-strong))', - '--tw-prose-code': 'hsl(var(--foreground-default))', - '--tw-prose-pre-code': 'hsl(var(--foreground-muted))', - '--tw-prose-pre-bg': 'hsl(var(--background-surface-200))', - '--tw-prose-th-borders': 'hsl(var(--background-surface-300))', - '--tw-prose-td-borders': 'hsl(var(--background-default))', - '--tw-prose-invert-body': 'hsl(var(--background-default))', - '--tw-prose-invert-headings': theme('colors.white'), - '--tw-prose-invert-lead': 'hsl(var(--background-surface-300))', - '--tw-prose-invert-links': theme('colors.white'), - '--tw-prose-invert-bold': theme('colors.white'), - '--tw-prose-invert-counters': 'hsl(var(--background-surface-200))', - '--tw-prose-invert-bullets': 'hsl(var(--background-selection))', - '--tw-prose-invert-hr': 'hsl(var(--border-strong))', - '--tw-prose-invert-quotes': 'hsl(var(--background-alternative-default))', - '--tw-prose-invert-quote-borders': 'hsl(var(--border-strong))', - '--tw-prose-invert-captions': 'hsl(var(--background-surface-200))', - // the following are typography overrides - // examples can be seen here โ€”> https://github.com/tailwindlabs/tailwindcss-typography/blob/master/src/styles.js - // reset all header font weights - 'h1, h2, h3, h4, h5': { - fontWeight: '400', - }, - }, - }, - }), - screens: { - xs: '480px', - }, - fontFamily: { - sans: 'var(--font-custom, Circular, custom-font, Helvetica Neue, Helvetica, Arial, sans-serif)', - mono: 'var(--font-source-code-pro, Source Code Pro, Office Code Pro, Menlo, monospace)', - }, - - // shadcn defaults START - keyframes: { - 'flash-code': { - '0%': { backgroundColor: 'rgba(63, 207, 142, 0.1)' }, - '100%': { backgroundColor: 'transparent' }, - }, - 'accordion-down': { - from: { height: 0 }, - to: { height: 'var(--radix-accordion-content-height)' }, - }, - 'accordion-up': { - from: { height: 'var(--radix-accordion-content-height)' }, - to: { height: 0 }, - }, - 'collapsible-down': { - from: { height: 0 }, - to: { height: 'var(--radix-collapsible-content-height)' }, - }, - 'collapsible-up': { - from: { height: 'var(--radix-collapsible-content-height)' }, - to: { height: 0 }, - }, - }, - animation: { - 'flash-code': 'flash-code 1s forwards', - 'flash-code-slow': 'flash-code 2s forwards', - 'accordion-down': 'accordion-down 0.15s ease-out', - 'accordion-up': 'accordion-up 0.15s ease-out', - 'collapsible-down': 'collapsible-down 0.10s ease-out', - 'collapsible-up': 'collapsible-up 0.10s ease-out', - }, - borderRadius: { - // lg: `var(--radius)`, - // md: `calc(var(--radius) - 2px)`, - // sm: 'calc(var(--radius) - 4px)', - panel: '6px', - }, - padding: { - content: '21px', - card: 'var(--card-padding-x)', - }, - // borderRadius: { - // lg: `var(--radius)`, - // md: `calc(var(--radius) - 2px)`, - // sm: 'calc(var(--radius) - 4px)', - // }, - // fontFamily: { - // sans: ['var(--font-sans)', ...fontFamily.sans], - // }, - // shadcn defaults END - }, - }, - plugins: [ - require('@tailwindcss/typography'), - require('tailwindcss-animate'), - require('./tailwind-plugins/hit-area'), - require('./tailwind-plugins/motion-safe-transition'), - function ({ addVariant }) { - addVariant('not-disabled', '&:not(:disabled)') - }, - function ({ addUtilities }) { - // The bg class worked in Tailwind 3, but stopped working in TW 4. This is a custom utility to add it back in, as - // it's used in a lot of places across the codebase. We should deprecate it and use `bg-default` instead. - addUtilities({ - '.bg': { backgroundColor: 'hsl(var(--background-default))' }, - }) - }, - ], -}) - -module.exports = uiConfig diff --git a/packages/config/typography.config.js b/packages/config/typography.config.js new file mode 100644 index 00000000000..69efdd74ae2 --- /dev/null +++ b/packages/config/typography.config.js @@ -0,0 +1,232 @@ +/** + * Typography customization for @tailwindcss/typography. + * + * Loaded via `@config './typography.config.js'` in tailwind.config.css. + * Only contains theme.extend.typography โ€” everything else (colors, animations, + * variants, utilities, base) lives in CSS under packages/config/css/. + * + * The typography plugin reads theme.typography to generate .prose, .prose-toc, + * and .prose-docs classes (and to make them @apply-able). See: + * https://github.com/tailwindlabs/tailwindcss-typography#customizing-the-css + */ + +module.exports = { + theme: { + extend: { + typography: { + DEFAULT: { + css: { + // Removal of backticks in code blocks. + // https://github.com/tailwindlabs/tailwindcss-typography/issues/135 + 'code::before': { content: '""' }, + 'code::after': { content: '""' }, + '--tw-prose-body': 'hsl(var(--foreground-light))', + '--tw-prose-headings': 'hsl(var(--foreground-default))', + '--tw-prose-lead': 'hsl(var(--foreground-light))', + '--tw-prose-links': 'hsl(var(--foreground-light))', + '--tw-prose-bold': 'hsl(var(--foreground-light))', + '--tw-prose-counters': 'hsl(var(--foreground-light))', + '--tw-prose-bullets': 'hsl(var(--foreground-muted))', + '--tw-prose-hr': 'hsl(var(--background-surface-300))', + '--tw-prose-quotes': 'hsl(var(--foreground-light))', + '--tw-prose-quote-borders': 'hsl(var(--background-surface-300))', + '--tw-prose-captions': 'hsl(var(--border-strong))', + '--tw-prose-code': 'hsl(var(--foreground-default))', + '--tw-prose-pre-code': 'hsl(var(--foreground-muted))', + '--tw-prose-pre-bg': 'hsl(var(--background-surface-200))', + '--tw-prose-th-borders': 'hsl(var(--background-surface-300))', + '--tw-prose-td-borders': 'hsl(var(--background-default))', + '--tw-prose-invert-body': 'hsl(var(--background-default))', + '--tw-prose-invert-headings': 'white', + '--tw-prose-invert-lead': 'hsl(var(--background-surface-300))', + '--tw-prose-invert-links': 'white', + '--tw-prose-invert-bold': 'white', + '--tw-prose-invert-counters': 'hsl(var(--background-surface-200))', + '--tw-prose-invert-bullets': 'hsl(var(--background-selection))', + '--tw-prose-invert-hr': 'hsl(var(--border-strong))', + '--tw-prose-invert-quotes': 'hsl(var(--background-alternative-default))', + '--tw-prose-invert-quote-borders': 'hsl(var(--border-strong))', + '--tw-prose-invert-captions': 'hsl(var(--background-surface-200))', + h4: { fontSize: '1.15em' }, + // h5 isn't included in --tw-prose-headings. + h5: { color: 'var(--color-scale-1200)' }, + 'h1, h2, h3, h4, h5, h6': { fontWeight: '400' }, + 'article h2, article h3, article h4, article h5, article h6': { + marginTop: '2em', + marginBottom: '1em', + }, + p: { fontWeight: '400' }, + strong: { fontWeight: '500' }, + pre: { + background: 'none', + padding: 0, + marginBottom: '32px', + }, + ul: { + listStyleType: 'none', + paddingLeft: '1rem', + }, + 'ul li': { position: 'relative' }, + 'ul li::before': { + position: 'absolute', + top: '0.75rem', + left: '-1rem', + height: '0.125rem', + width: '0.5rem', + borderRadius: '0.25rem', + backgroundColor: 'hsl(var(--border-strong))', + content: '""', + }, + ol: { + paddingLeft: '1rem', + counterReset: 'item', + listStyleType: 'none', + marginBottom: '3rem', + }, + 'ol>li': { + display: 'block', + position: 'relative', + paddingLeft: '1rem', + }, + 'ol>li::before': { + position: 'absolute', + top: '0.25rem', + left: '-1rem', + height: '1.2rem', + width: '1.2rem', + borderRadius: '0.25rem', + backgroundColor: 'hsl(var(--background-surface-100))', + border: '1px solid hsl(var(--border-default))', + content: 'counter(item) " "', + counterIncrement: 'item', + fontSize: '12px', + color: 'hsl(var(--foreground-muted))', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + 'p img': { + border: '1px solid hsl(var(--border-muted))', + borderRadius: '4px', + overflow: 'hidden', + }, + iframe: { + border: '1px solid hsl(var(--border-default))', + borderRadius: 'var(--radius-lg)', + }, + td: { + borderBottom: '1px solid hsl(var(--background-surface-200))', + }, + code: { + fontWeight: '400', + padding: '0.2rem 0.4rem', + backgroundColor: 'hsl(var(--background-surface-200))', + border: '1px solid hsl(var(--background-surface-300))', + borderRadius: 'var(--radius-lg)', + }, + a: { + position: 'relative', + transition: 'all 0.18s ease', + paddingBottom: '2px', + fontWeight: '400', + opacity: 1, + color: 'hsl(var(--foreground-default))', + textDecorationLine: 'underline', + textDecorationColor: 'hsl(var(--foreground-muted))', + textDecorationThickness: '1px', + textUnderlineOffset: '2px', + }, + 'a:hover': { + textDecorationColor: 'hsl(var(--foreground-default))', + }, + figcaption: { + color: 'hsl(var(--foreground-muted))', + fontFamily: 'Office Code Pro, monospace', + }, + 'figure.quote-figure p:first-child': { + marginTop: '0 !important', + }, + 'figure.quote-figure p:last-child': { + marginBottom: '0 !important', + }, + figure: { margin: '3rem 0' }, + 'figure img': { margin: '0 !important' }, + }, + }, + + toc: { + css: { + ul: { + 'list-style-type': 'none', + 'padding-left': 0, + margin: 0, + li: { 'padding-left': 0 }, + a: { + display: 'block', + marginBottom: '0.4rem', + 'text-decoration': 'none', + fontSize: '0.8rem', + fontWeight: '200', + color: 'hsl(var(--foreground-light))', + '&:hover': { + color: 'hsl(var(--foreground-default))', + }, + 'font-weight': '400', + }, + ul: { + 'list-style-type': 'none', + li: { + marginTop: '0.2rem', + marginBottom: '0.2rem', + 'padding-left': '0 !important', + 'margin-left': '0.5rem', + }, + a: { + fontWeight: '200', + color: 'hsl(var(--foreground-lighter))', + '&:hover': { + color: 'hsl(var(--foreground-default))', + }, + }, + }, + }, + }, + }, + + // Used in docs and changelog content. + docs: { + css: { + '--tw-prose-body': 'hsl(var(--foreground-light))', + '--tw-prose-headings': 'hsl(var(--foreground-default))', + '--tw-prose-lead': 'hsl(var(--foreground-light))', + '--tw-prose-links': 'hsl(var(--brand-500))', + '--tw-prose-bold': 'hsl(var(--foreground-light))', + '--tw-prose-counters': 'hsl(var(--foreground-light))', + '--tw-prose-bullets': 'hsl(var(--foreground-muted))', + '--tw-prose-hr': 'hsl(var(--background-surface-300))', + '--tw-prose-quotes': 'hsl(var(--foreground-light))', + '--tw-prose-quote-borders': 'hsl(var(--background-surface-300))', + '--tw-prose-captions': 'hsl(var(--border-strong))', + '--tw-prose-code': 'hsl(var(--foreground-default))', + '--tw-prose-pre-code': 'hsl(var(--foreground-muted))', + '--tw-prose-pre-bg': 'hsl(var(--background-surface-200))', + '--tw-prose-th-borders': 'hsl(var(--background-surface-300))', + '--tw-prose-td-borders': 'hsl(var(--background-default))', + '--tw-prose-invert-body': 'hsl(var(--background-default))', + '--tw-prose-invert-headings': 'white', + '--tw-prose-invert-lead': 'hsl(var(--background-surface-300))', + '--tw-prose-invert-links': 'white', + '--tw-prose-invert-bold': 'white', + '--tw-prose-invert-counters': 'hsl(var(--background-surface-200))', + '--tw-prose-invert-bullets': 'hsl(var(--background-selection))', + '--tw-prose-invert-hr': 'hsl(var(--border-strong))', + '--tw-prose-invert-quotes': 'hsl(var(--background-alternative-default))', + '--tw-prose-invert-quote-borders': 'hsl(var(--border-strong))', + '--tw-prose-invert-captions': 'hsl(var(--background-surface-200))', + 'h1, h2, h3, h4, h5': { fontWeight: '400' }, + }, + }, + }, + }, + }, +} diff --git a/packages/config/ui.config.js b/packages/config/ui.config.js deleted file mode 100644 index 2c84db05b6c..00000000000 --- a/packages/config/ui.config.js +++ /dev/null @@ -1,441 +0,0 @@ -const deepMerge = require('deepmerge') -const forms = require('@tailwindcss/forms') -const radixUiColors = require('@radix-ui/colors') -const brandColors = require('./default-colors') -const svgToDataUri = require('mini-svg-data-uri') - -function flattenColorPalette(colors) { - return Object.assign( - {}, - ...Object.entries(colors ?? {}).flatMap(([key, value]) => - typeof value === 'object' - ? Object.entries(flattenColorPalette(value)).map(([k, v]) => ({ - [key + (k === 'DEFAULT' ? '' : '-' + k)]: v, - })) - : [{ [key]: value }] - ) - ) -} - -// exclude these colors from the included set from Radix -const excludedRadixColors = [ - 'bronze', - 'brown', - 'cyan', - 'grass', - 'olive', - 'mauve', - 'mint', - 'lime', - 'plum', - 'sage', - 'sand', - 'sky', - 'teal', -] - -// generates fixed scales -// based on the root/light mode version -const fixedOptions = ['scale', 'scaleA'] - -function radixColorKeys() { - let keys = Object.keys(radixUiColors) - - /** - * Filter array items based on search criteria (query) - */ - function filterItems(arr, query) { - return arr.filter(function (el) { - return el.toLowerCase().indexOf(query.toLowerCase()) == -1 - }) - } - - keys = filterItems(keys, 'Dark') - - // remove excluded colors - keys = keys.filter( - (key) => !excludedRadixColors.some((excludeColor) => key.startsWith(excludeColor)) - ) - - return keys -} - -function generateColorClasses() { - const brandColors = ['scale', 'scaleA'] - const colors = [...radixColorKeys(), ...brandColors] - - let mappedColors = {} - - // generate the shape of the colors object - colors.map((x) => { - // create empty obj for each color - mappedColors[x] = {} - // create empty obj for each fixed color - if (fixedOptions.some((v) => x.indexOf(v) >= 0)) { - mappedColors[`${x}-fixed`] = {} - } - }) - - colors.map((x) => { - for (let index = 0; index < 12; index++) { - const step = index + 1 - mappedColors[x][step * 100] = `var(--colors-${x}${step})` - - if (fixedOptions.some((v) => x.indexOf(v) >= 0)) { - mappedColors[`${x}-fixed`][step * 100] = `var(--colors-fixed-${x}${step})` - } - } - }) - - return mappedColors -} - -const colorClasses = generateColorClasses() - -// generate the CSS variables for tailwind to use -function generateCssVariables() { - // potential options - // { fixedOptions, brandColors } - - let rootColors = {} - let darkColors = {} - - const radixArray = Object.entries(radixUiColors) - .filter( - ([key, value]) => !excludedRadixColors.some((excludeColor) => key.startsWith(excludeColor)) - ) - .map(([, value]) => value) - - const brandArray = Object.values(brandColors) - - function generateColors(colors, index, colorSet) { - const key = Object.keys(colorSet)[index] - - if (key.includes('Dark')) { - darkColors = { ...darkColors, ...colors } - } else { - rootColors = { ...rootColors, ...colors } - - // generate an optional 'fixed' scale of colors - if ( - fixedOptions.some(function (v) { - return key.indexOf(v) >= 0 - }) - ) { - rootColors.fixed = { ...rootColors?.fixed, ...colors } - } - } - } - - radixArray.map((x, i) => { - generateColors(x, i, radixUiColors) - }) - - brandArray.map((x, i) => { - generateColors(x, i, brandColors) - }) - - return { - root: { ...rootColors }, - dark: { ...darkColors }, - } -} - -const variables = generateCssVariables() - -const uiConfig = { - theme: { - variables: { - DEFAULT: { - width: { - listbox: '320px', - }, - colors: { ...variables.root }, - }, - "[data-theme*='dark']": { - colors: { ...variables.dark }, - }, - }, - extend: { - // dropdown extensions - transformOrigin: { - // tailwind class for this is `origin-dropdown` - dropdown: 'var(--radix-dropdown-menu-content-transform-origin)', - popover: 'var(--radix-popover-menu-content-transform-origin);', - }, - width: { - listbox: 'var(--width-listbox);', - }, - keyframes: { - fadeIn: { - '0%': { transform: 'scale(0.95)', opacity: 0 }, - '100%': { transform: 'scale(1)', opacity: 1 }, - }, - fadeOut: { - '0%': { transform: 'scale(1)', opacity: 1 }, - '100%': { transform: 'scale(0.95)', opacity: 0 }, - }, - overlayContentShow: { - '0%': { opacity: 0, transform: 'translate(0%, -2%) scale(1)' }, - '100%': { opacity: 1, transform: 'translate(0%, 0%) scale(1)' }, - }, - overlayContentHide: { - '0%': { opacity: 1, transform: 'translate(0%, 0%) scale(1)' }, - '100%': { opacity: 0, transform: 'translate(0%, -2%) scale(1)' }, - }, - dropdownFadeIn: { - '0%': { transform: 'scale(0.95)', opacity: 0 }, - '100%': { transform: 'scale(1)', opacity: 1 }, - }, - dropdownFadeOut: { - '0%': { transform: 'scale(1)', opacity: 1 }, - '100%': { transform: 'scale(0.95)', opacity: 0 }, - }, - fadeInOverlayBg: { - '0%': { opacity: 0 }, - '100%': { opacity: 0.75 }, - }, - fadeOutOverlayBg: { - '0%': { opacity: 0.75 }, - '100%': { opacity: 0 }, - }, - slideDown: { - '0%': { height: 0, opacity: 0 }, - '100%': { - height: 'var(--radix-accordion-content-height)', - opacity: 1, - }, - }, - slideUp: { - '0%': { height: 'var(--radix-accordion-content-height)', opacity: 1 }, - '100%': { height: 0, opacity: 0 }, - }, - - slideDownNormal: { - '0%': { height: 0, opacity: 0 }, - '100%': { - height: 'inherit', - opacity: 1, - }, - }, - slideUpNormal: { - '0%': { height: 'inherit', opacity: 1 }, - '100%': { height: 0, opacity: 0 }, - }, - - panelSlideLeftOut: { - '0%': { transform: 'translateX(-100%)', opacity: 0 }, - '100%': { - transform: 'translate-x-0', - opacity: 1, - }, - }, - panelSlideLeftIn: { - '0%': { transform: 'translate-x-0', opacity: 1 }, - '100%': { transform: 'translateX(-100%)', opacity: 0 }, - }, - panelSlideRightOut: { - '0%': { transform: 'translateX(100%)', opacity: 0 }, - '100%': { - transform: 'translate-x-0', - opacity: 1, - }, - }, - panelSlideRightIn: { - '0%': { transform: 'translate-x-0', opacity: 1 }, - '100%': { transform: 'translateX(100%)', opacity: 0 }, - }, - lineLoading: { - '0%': { - marginLeft: '-10%', - width: '80px', - }, - '25%': { - width: ' 240px', - }, - '50%': { - marginLeft: '100%', - width: '80px', - }, - '75%': { - width: '240px', - }, - '100%': { - marginLeft: '-10%', - width: '80px', - }, - }, - }, - animation: { - 'fade-in': 'fadeIn 300ms both', - 'fade-out': 'fadeOut 300ms both', - - 'dropdown-content-show': 'overlayContentShow 100ms cubic-bezier(0.16, 1, 0.3, 1)', - 'dropdown-content-hide': 'overlayContentHide 100ms cubic-bezier(0.16, 1, 0.3, 1)', - - 'overlay-show': 'overlayContentShow 300ms cubic-bezier(0.16, 1, 0.3, 1)', - 'overlay-hide': 'overlayContentHide 300ms cubic-bezier(0.16, 1, 0.3, 1)', - - 'fade-in-overlay-bg': 'fadeInOverlayBg 300ms', - 'fade-out-overlay-bg': 'fadeOutOverlayBg 300ms', - - 'slide-down': 'slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1)', - 'slide-up': 'slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1)', - - 'slide-down-normal': 'slideDownNormal 300ms cubic-bezier(0.87, 0, 0.13, 1)', - 'slide-up-normal': 'slideUpNormal 300ms cubic-bezier(0.87, 0, 0.13, 1)', - - 'panel-slide-left-out': 'panelSlideLeftOut 200ms cubic-bezier(0.87, 0, 0.13, 1)', - 'panel-slide-left-in': 'panelSlideLeftIn 250ms cubic-bezier(0.87, 0, 0.13, 1)', - 'panel-slide-right-out': 'panelSlideRightOut 200ms cubic-bezier(0.87, 0, 0.13, 1)', - 'panel-slide-right-in': 'panelSlideRightIn 250ms cubic-bezier(0.87, 0, 0.13, 1)', - - 'line-loading': 'lineLoading 1.8s infinite', - 'line-loading-slower': 'lineLoading 2.3s infinite', - - // tailwind class for this is `animate-dropdownFadeIn` - dropdownFadeIn: 'dropdownFadeIn 0.1s ease-out', - // tailwind class for this is `animate-dropdownFadeOut` - dropdownFadeOut: 'dropdownFadeOut 0.1s ease-out', - }, - colors: { - ...colorClasses, - 'hi-contrast': `hsl(var(--foreground-default))`, - 'lo-contrast': `hsl(var(--background-alternative-default))`, - warning: { - default: 'red', - 100: '#342355', - }, - }, - }, - }, - plugins: [ - require('@mertasan/tailwindcss-variables'), - function ({ addBase, addUtilities, addComponents, addVariant }) { - // hide the up/down arrows in number inputs for webkit browsers - addBase({ - 'input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button': - { - '-webkit-appearance': 'none', - margin: '0', - }, - }) - addBase({ - 'input[type=number]': { - '-moz-appearance': 'textfield', - }, - }) - // [Ivan] Not sure if it's used. - addComponents({ - ".dropdown-content[data-state='open']": { - animation: 'fadeIn 50ms ease-out', - }, - ".dropdown-content[data-state='closed']": { - animation: 'fadeOut 50ms ease-in', - }, - }) - addUtilities({ - '.line-loading-bg': { - background: 'rgb(0, 0, 0)', - background: - 'linear-gradient(90deg,rgba(0, 0, 0, 0) 0%,rgba(255, 255, 255, 0.65) 50%,rgba(0, 0, 0, 0) 100%)', - }, - '.line-loading-bg-light': { - background: 'rgb(0, 0, 0)', - background: - 'linear-gradient(90deg,rgba(0, 0, 0, 0) 0%,rgba(33, 33, 33, 0.65) 50%,rgba(0, 0, 0, 0) 100%)', - }, - '.no-scrollbar': { - /* Hide scrollbar for IE, Edge*/ - '-ms-overflow-style': 'none', - - /* Firefox */ - 'scrollbar-width': 'none' /* Firefox */, - - /* Hide scrollbar for Chrome, Safari and Opera */ - '&::-webkit-scrollbar': { - display: 'none', - }, - }, - /* Add fadeout effect */ - '.mask-fadeout-right': { - '-webkit-mask-image': 'linear-gradient(to right, white 98%, transparent 100%)', - 'mask-image': 'linear-gradient(to right, white 98%, transparent 100%)', - }, - '.mask-fadeout-left': { - '-webkit-mask-image': 'linear-gradient(to left, white 98%, transparent 100%)', - 'mask-image': 'linear-gradient(to left, white 98%, transparent 100%)', - }, - }) - addVariant('data-open-parent', '[data-state="open"] &') - addVariant('data-closed-parent', '[data-state="closed"] &') - addVariant('data-open', '&[data-state="open"]') - addVariant('data-closed', '&[data-state="closed"]') - addVariant('data-show', '&[data-state="show"]') - addVariant('data-hide', '&[data-state="hide"]') - addVariant('data-checked', '&[data-state="checked"]') - addVariant('data-unchecked', '&[data-state="unchecked"]') - addVariant('aria-expanded', '&[aria-expanded="true"]') - // addVariant('parent-data-open', '[data-state="open"]&') - }, - function ({ matchUtilities, theme }) { - matchUtilities( - { - highlight: (value) => ({ boxShadow: `inset 0 1px 0 0 ${value}` }), - }, - { values: flattenColorPalette(theme('backgroundColor')), type: 'color' } - ) - matchUtilities( - { - subhighlight: (value) => ({ - boxShadow: `inset 0 -1px 0 0 ${value}`, - }), - }, - { - values: flattenColorPalette(theme('backgroundColor')), - type: 'color', - } - ) - matchUtilities( - { - 'bg-grid': (value) => ({ - backgroundImage: `url("${svgToDataUri( - `` - )}")`, - }), - }, - { values: flattenColorPalette(theme('backgroundColor')), type: 'color' } - ) - }, - require('tailwindcss-radix')(), - forms, - ], -} - -function arrayMergeFn(destinationArray, sourceArray) { - return destinationArray.concat(sourceArray).reduce((acc, cur) => { - if (acc.includes(cur)) return acc - return [...acc, cur] - }, []) -} - -/** - * Merge Supabase UI and Tailwind CSS configurations - * @param {object} tailwindConfig - Tailwind config object - * @return {object} new config object - */ -function wrapper(tailwindConfig) { - let purge - if (Array.isArray(tailwindConfig.purge)) { - purge = { - content: tailwindConfig.purge, - } - } else { - purge = tailwindConfig.purge - } - return deepMerge({ ...tailwindConfig, purge }, uiConfig, { - arrayMerge: arrayMergeFn, - }) -} - -module.exports = wrapper diff --git a/packages/dev-tools/tailwind.config.css b/packages/dev-tools/tailwind.config.css new file mode 100644 index 00000000000..eeb08307b6f --- /dev/null +++ b/packages/dev-tools/tailwind.config.css @@ -0,0 +1,4 @@ +/* Placeholder so the VS Code Tailwind extension picks up IntelliSense in this + * package. Apps own their actual entry CSS file; this file just anchors the + * shared config for editor tooling. */ +@import 'config/tailwind.config.css'; diff --git a/packages/marketing/tailwind.config.css b/packages/marketing/tailwind.config.css new file mode 100644 index 00000000000..eeb08307b6f --- /dev/null +++ b/packages/marketing/tailwind.config.css @@ -0,0 +1,4 @@ +/* Placeholder so the VS Code Tailwind extension picks up IntelliSense in this + * package. Apps own their actual entry CSS file; this file just anchors the + * shared config for editor tooling. */ +@import 'config/tailwind.config.css'; diff --git a/packages/ui-patterns/src/multi-select/multi-select.tsx b/packages/ui-patterns/src/multi-select/multi-select.tsx index bc01b120aaf..fcdc6baff5a 100644 --- a/packages/ui-patterns/src/multi-select/multi-select.tsx +++ b/packages/ui-patterns/src/multi-select/multi-select.tsx @@ -46,7 +46,7 @@ const DROPDOWN_GAP = 8 const commandItemClass = cn( 'relative text-foreground-lighter text-left px-2 py-1.5 rounded-sm', 'hover:text-foreground hover:!bg-overlay-hover w-full flex items-center space-x-2', - 'peer-data-[value=true]:bg-overlay-hover peer-data-[value=true]:text-strong' + 'peer-data-[value=true]:bg-overlay-hover' ) function useMultiSelect() { diff --git a/packages/ui-patterns/tailwind.config.css b/packages/ui-patterns/tailwind.config.css new file mode 100644 index 00000000000..eeb08307b6f --- /dev/null +++ b/packages/ui-patterns/tailwind.config.css @@ -0,0 +1,4 @@ +/* Placeholder so the VS Code Tailwind extension picks up IntelliSense in this + * package. Apps own their actual entry CSS file; this file just anchors the + * shared config for editor tooling. */ +@import 'config/tailwind.config.css'; diff --git a/packages/ui-patterns/tailwind.config.js b/packages/ui-patterns/tailwind.config.js deleted file mode 100644 index fd65503e32a..00000000000 --- a/packages/ui-patterns/tailwind.config.js +++ /dev/null @@ -1 +0,0 @@ -// Placeholder so that VSCode tailwind extension enables intellisense in this project diff --git a/packages/ui/src/components/shadcn/ui/command.tsx b/packages/ui/src/components/shadcn/ui/command.tsx index 068bb834b7a..170c6b245ea 100644 --- a/packages/ui/src/components/shadcn/ui/command.tsx +++ b/packages/ui/src/components/shadcn/ui/command.tsx @@ -148,7 +148,7 @@ const CommandItem = React.forwardRef< =12.13.0'} - peerDependencies: - autoprefixer: ^10.0.2 - postcss: ^8.5.10 - '@mjackson/headers@0.11.1': resolution: {integrity: sha512-uXXhd4rtDdDwkqAuGef1nuafkCa1NlTmEc1Jzc0NL4YiA1yON1NFXuqJ3hOuKvNKQwkiDwdD+JJlKVyz4dunFA==} @@ -16462,9 +16443,6 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss-radix@2.8.0: - resolution: {integrity: sha512-1k1UfoIYgVyBl13FKwwoKavjnJ5VEaUClCTAsgz3VLquN4ay/lyaMPzkbqD71sACDs2fRGImytAUlMb4TzOt1A==} - tailwindcss@4.2.4: resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} @@ -20476,12 +20454,6 @@ snapshots: dependencies: langium: 3.3.1 - '@mertasan/tailwindcss-variables@2.7.0(autoprefixer@10.4.27(postcss@8.5.10))(postcss@8.5.10)': - dependencies: - autoprefixer: 10.4.27(postcss@8.5.10) - lodash: 4.18.1 - postcss: 8.5.10 - '@mjackson/headers@0.11.1': {} '@mjackson/multipart-parser@0.10.1': @@ -34811,8 +34783,6 @@ snapshots: dependencies: tailwindcss: 4.2.4 - tailwindcss-radix@2.8.0: {} - tailwindcss@4.2.4: {} tapable@2.3.0: {}