Replace Tailwind with modular Sass system using mixins and utilities

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-19 19:13:21 +00:00
parent 95b712db37
commit de0a282086
15 changed files with 995 additions and 1126 deletions

1085
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -45,7 +45,6 @@
"@radix-ui/react-toggle-group": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@reduxjs/toolkit": "^2.11.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/react-query": "^5.83.1",
"@types/sql.js": "^1.4.9",
"class-variance-authority": "^0.7.1",
@@ -71,25 +70,21 @@
"react-resizable-panels": "^2.1.7",
"react-router-dom": "^7.12.0",
"recharts": "^2.15.1",
"sass": "^1.97.2",
"sonner": "^2.0.1",
"tailwind-merge": "^3.0.2",
"three": "^0.175.0",
"tw-animate-css": "^1.2.4",
"uuid": "^11.1.0",
"vaul": "^1.1.2",
"zod": "^3.25.76"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@tailwindcss/postcss": "^4.1.18",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"autoprefixer": "^10.4.23",
"eslint": "^9.28.0",
"eslint-config-next": "^16.1.3",
"eslint-plugin-react-hooks": "^5.2.0",
"globals": "^16.0.0",
"tailwindcss": "^4.1.11",
"typescript": "~5.7.2",
"typescript-eslint": "^8.38.0"
},

View File

@@ -1,5 +0,0 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}

View File

@@ -1,67 +0,0 @@
@import 'tailwindcss';
:root {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 263 70% 50%;
--primary-foreground: 210 40% 98%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 195 100% 70%;
--accent-foreground: 222.2 84% 4.9%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 195 100% 70%;
--radius: 0.5rem;
}
@theme {
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
}
* {
border-color: hsl(var(--border));
}
body {
background: hsl(var(--background));
color: hsl(var(--foreground));
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-inter), 'Inter', sans-serif;
}
body {
font-family: var(--font-inter), 'Inter', sans-serif;
}
code, pre {
font-family: var(--font-jetbrains-mono), 'JetBrains Mono', monospace;
}

49
src/app/globals.scss Normal file
View File

@@ -0,0 +1,49 @@
@import '../styles/abstracts';
@import '../styles/utilities/utilities';
@import './theme.scss';
* {
margin: 0;
padding: 0;
box-sizing: border-box;
border-color: $border;
}
body {
background: $background;
color: $foreground;
font-family: var(--font-inter), 'Inter', sans-serif;
line-height: 1.5;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-inter), 'Inter', sans-serif;
font-weight: 600;
}
code, pre {
font-family: var(--font-jetbrains-mono), 'JetBrains Mono', monospace;
}
:root {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 263 70% 50%;
--primary-foreground: 210 40% 98%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 195 100% 70%;
--accent-foreground: 222.2 84% 4.9%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 195 100% 70%;
--radius: 0.5rem;
}

View File

@@ -1,5 +1,5 @@
import type { Metadata } from 'next';
import './globals.css';
import './globals.scss';
import { Providers } from './providers';
export const metadata: Metadata = {

View File

@@ -1,262 +0,0 @@
@import "tailwindcss";
@import '@radix-ui/colors/sage-dark.css' layer(base);
@import '@radix-ui/colors/olive.css' layer(base);
@import '@radix-ui/colors/olive-dark.css' layer(base);
@import '@radix-ui/colors/sand.css' layer(base);
@import '@radix-ui/colors/sand-dark.css' layer(base);
@import '@radix-ui/colors/red.css' layer(base);
@import '@radix-ui/colors/red-dark.css' layer(base);
@import '@radix-ui/colors/ruby.css' layer(base);
@import '@radix-ui/colors/ruby-dark.css' layer(base);
@import '@radix-ui/colors/crimson.css' layer(base);
@import '@radix-ui/colors/crimson-dark.css' layer(base);
@import '@radix-ui/colors/pink.css' layer(base);
@import '@radix-ui/colors/pink-dark.css' layer(base);
@import '@radix-ui/colors/plum.css' layer(base);
@import '@radix-ui/colors/plum-dark.css' layer(base);
@import '@radix-ui/colors/purple.css' layer(base);
@import '@radix-ui/colors/purple-dark.css' layer(base);
@import '@radix-ui/colors/violet.css' layer(base);
@import '@radix-ui/colors/violet-dark.css' layer(base);
@import '@radix-ui/colors/iris.css' layer(base);
@import '@radix-ui/colors/iris-dark.css' layer(base);
@import '@radix-ui/colors/indigo.css' layer(base);
@import '@radix-ui/colors/indigo-dark.css' layer(base);
@import '@radix-ui/colors/blue.css' layer(base);
@import '@radix-ui/colors/blue-dark.css' layer(base);
@import '@radix-ui/colors/cyan.css' layer(base);
@import '@radix-ui/colors/cyan-dark.css' layer(base);
@import '@radix-ui/colors/teal.css' layer(base);
@import '@radix-ui/colors/teal-dark.css' layer(base);
@import '@radix-ui/colors/jade.css' layer(base);
@import '@radix-ui/colors/jade-dark.css' layer(base);
@import '@radix-ui/colors/green.css' layer(base);
@import '@radix-ui/colors/green-dark.css' layer(base);
@import '@radix-ui/colors/grass.css' layer(base);
@import '@radix-ui/colors/grass-dark.css' layer(base);
@import '@radix-ui/colors/bronze.css' layer(base);
@import '@radix-ui/colors/bronze-dark.css' layer(base);
@import '@radix-ui/colors/gold.css' layer(base);
@import '@radix-ui/colors/gold-dark.css' layer(base);
@import '@radix-ui/colors/brown.css' layer(base);
@import '@radix-ui/colors/brown-dark.css' layer(base);
@import '@radix-ui/colors/orange.css' layer(base);
@import '@radix-ui/colors/orange-dark.css' layer(base);
@import '@radix-ui/colors/amber.css' layer(base);
@import '@radix-ui/colors/amber-dark.css' layer(base);
@import '@radix-ui/colors/yellow.css' layer(base);
@import '@radix-ui/colors/yellow-dark.css' layer(base);
@import '@radix-ui/colors/lime.css' layer(base);
@import '@radix-ui/colors/lime-dark.css' layer(base);
@import '@radix-ui/colors/mint.css' layer(base);
@import '@radix-ui/colors/mint-dark.css' layer(base);
@import '@radix-ui/colors/sky.css' layer(base);
@import '@radix-ui/colors/sky-dark.css' layer(base);
@import '@radix-ui/colors/tomato.css' layer(base);
@import '@radix-ui/colors/tomato-dark.css' layer(base);
@import '@radix-ui/colors/gray.css' layer(base);
@import '@radix-ui/colors/gray-dark.css' layer(base);
@import '@radix-ui/colors/mauve.css' layer(base);
@import '@radix-ui/colors/mauve-dark.css' layer(base);
@import '@radix-ui/colors/slate.css' layer(base);
@import '@radix-ui/colors/slate-dark.css' layer(base);
@import '@radix-ui/colors/slate-alpha.css' layer(base);
@import '@radix-ui/colors/slate-dark-alpha.css' layer(base);
@import 'tailwindcss/theme' layer(theme);
@import 'tailwindcss/preflight' layer(base);
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
@layer base {
#spark-app {
--tomato-contrast: #fff;
--red-contrast: #fff;
--ruby-contrast: #fff;
--crimson-contrast: #fff;
--pink-contrast: #fff;
--plum-contrast: #fff;
--purple-contrast: #fff;
--violet-contrast: #fff;
--iris-contrast: #fff;
--indigo-contrast: #fff;
--blue-contrast: #fff;
--cyan-contrast: #fff;
--teal-contrast: #fff;
--jade-contrast: #fff;
--green-contrast: #fff;
--grass-contrast: #fff;
--bronze-contrast: #fff;
--gold-contrast: #fff;
--brown-contrast: #fff;
--orange-contrast: #fff;
--amber-contrast: #000;
--yellow-contrast: #000;
--lime-contrast: #000;
--mint-contrast: #000;
--sky-contrast: #000;
--gray-contrast: #fff;
--mauve-contrast: #fff;
--slate-contrast: #fff;
--sage-contrast: #fff;
--olive-contrast: #fff;
--sand-contrast: #fff;
/**
* Spacing scale
*
* These variables define a spacing scale based on Tailwind's default.
* We've introduced a --size-scale variable as a multiplier.
* By adjusting this single value, we can proportionally
* scale all spacing throughout the entire application.
*
* https://tailwindcss.com/docs/customizing-spacing#default-spacing-scale
*/
--size-scale: 1;
--size-0: 0px;
--size-px: 1px;
--size-0-5: calc(0.125rem * var(--size-scale));
--size-1: calc(0.25rem * var(--size-scale));
--size-1-5: calc(0.375rem * var(--size-scale));
--size-2: calc(0.5rem * var(--size-scale));
--size-2-5: calc(0.625rem * var(--size-scale));
--size-3: calc(0.75rem * var(--size-scale));
--size-3-5: calc(0.875rem * var(--size-scale));
--size-4: calc(1rem * var(--size-scale));
--size-5: calc(1.25rem * var(--size-scale));
--size-6: calc(1.5rem * var(--size-scale));
--size-7: calc(1.75rem * var(--size-scale));
--size-8: calc(2rem * var(--size-scale));
--size-9: calc(2.25rem * var(--size-scale));
--size-10: calc(2.5rem * var(--size-scale));
--size-11: calc(2.75rem * var(--size-scale));
--size-12: calc(3rem * var(--size-scale));
--size-14: calc(3.5rem * var(--size-scale));
--size-16: calc(4rem * var(--size-scale));
--size-20: calc(5rem * var(--size-scale));
--size-24: calc(6rem * var(--size-scale));
--size-28: calc(7rem * var(--size-scale));
--size-32: calc(8rem * var(--size-scale));
--size-36: calc(9rem * var(--size-scale));
--size-40: calc(10rem * var(--size-scale));
--size-44: calc(11rem * var(--size-scale));
--size-48: calc(12rem * var(--size-scale));
--size-52: calc(13rem * var(--size-scale));
--size-56: calc(14rem * var(--size-scale));
--size-60: calc(15rem * var(--size-scale));
--size-64: calc(16rem * var(--size-scale));
--size-72: calc(18rem * var(--size-scale));
--size-80: calc(20rem * var(--size-scale));
--size-96: calc(24rem * var(--size-scale));
/* Border radii */
--radius-factor: 1;
--radius-sm: calc(2px * var(--radius-factor) * var(--size-scale));
--radius-md: calc(6px * var(--radius-factor) * var(--size-scale));
--radius-lg: calc(8px * var(--radius-factor) * var(--size-scale));
--radius-xl: calc(12px * var(--radius-factor) * var(--size-scale));
--radius-2xl: calc(16px * var(--radius-factor) * var(--size-scale));
--radius-full: 9999px;
/* Neutral colors */
--color-neutral-1: var(--slate-1);
--color-neutral-2: var(--slate-2);
--color-neutral-3: var(--slate-3);
--color-neutral-4: var(--slate-4);
--color-neutral-5: var(--slate-5);
--color-neutral-6: var(--slate-6);
--color-neutral-7: var(--slate-7);
--color-neutral-8: var(--slate-8);
--color-neutral-9: var(--slate-9);
--color-neutral-10: var(--slate-10);
--color-neutral-11: var(--slate-11);
--color-neutral-12: var(--slate-12);
--color-neutral-a1: var(--slate-a1);
--color-neutral-a2: var(--slate-a2);
--color-neutral-a3: var(--slate-a3);
--color-neutral-a4: var(--slate-a4);
--color-neutral-a5: var(--slate-a5);
--color-neutral-a6: var(--slate-a6);
--color-neutral-a7: var(--slate-a7);
--color-neutral-a8: var(--slate-a8);
--color-neutral-a9: var(--slate-a9);
--color-neutral-a10: var(--slate-a10);
--color-neutral-a11: var(--slate-a11);
--color-neutral-a12: var(--slate-a12);
--color-neutral-contrast: var(--slate-contrast);
/* Accent colors */
--color-accent-1: var(--blue-1);
--color-accent-2: var(--blue-2);
--color-accent-3: var(--blue-3);
--color-accent-4: var(--blue-4);
--color-accent-5: var(--blue-5);
--color-accent-6: var(--blue-6);
--color-accent-7: var(--blue-7);
--color-accent-8: var(--blue-8);
--color-accent-9: var(--blue-9);
--color-accent-10: var(--blue-10);
--color-accent-11: var(--blue-11);
--color-accent-12: var(--blue-12);
--color-accent-contrast: var(--blue-contrast);
/* Secondary accent colors */
--color-accent-secondary-1: var(--violet-1);
--color-accent-secondary-2: var(--violet-2);
--color-accent-secondary-3: var(--violet-3);
--color-accent-secondary-4: var(--violet-4);
--color-accent-secondary-5: var(--violet-5);
--color-accent-secondary-6: var(--violet-6);
--color-accent-secondary-7: var(--violet-7);
--color-accent-secondary-8: var(--violet-8);
--color-accent-secondary-9: var(--violet-9);
--color-accent-secondary-10: var(--violet-10);
--color-accent-secondary-11: var(--violet-11);
--color-accent-secondary-12: var(--violet-12);
--color-accent-secondary-contrast: var(--violet-contrast);
/* Foreground colors */
--color-fg: var(--color-neutral-12);
--color-fg-secondary: var(--color-neutral-a11);
/* Background colors */
--color-bg: #ffffff;
--color-bg-inset: var(--color-neutral-2);
--color-bg-overlay: #ffffff;
/* Focus ring */
--color-focus-ring: var(--color-accent-9);
/* Fonts */
--font-sans-serif: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-monospace: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
--font-family: var(--font-sans-serif);
}
#spark-app.dark-theme {
--color-bg: var(--color-neutral-1);
--color-bg-inset: #000000;
--color-bg-overlay: var(--color-neutral-3);
}
}

75
src/app/theme.scss Normal file
View File

@@ -0,0 +1,75 @@
// Radix UI Colors imports (keeping the color system)
@import '@radix-ui/colors/sage-dark.css';
@import '@radix-ui/colors/slate.css';
@import '@radix-ui/colors/slate-dark.css';
@import '@radix-ui/colors/blue.css';
@import '@radix-ui/colors/blue-dark.css';
@import '@radix-ui/colors/violet.css';
@import '@radix-ui/colors/violet-dark.css';
// Spacing scale variables
$size-scale: 1;
$size-0: 0px;
$size-px: 1px;
$size-0-5: calc(0.125rem * $size-scale);
$size-1: calc(0.25rem * $size-scale);
$size-2: calc(0.5rem * $size-scale);
$size-3: calc(0.75rem * $size-scale);
$size-4: calc(1rem * $size-scale);
$size-5: calc(1.25rem * $size-scale);
$size-6: calc(1.5rem * $size-scale);
$size-8: calc(2rem * $size-scale);
$size-10: calc(2.5rem * $size-scale);
$size-12: calc(3rem * $size-scale);
$size-16: calc(4rem * $size-scale);
$size-20: calc(5rem * $size-scale);
$size-24: calc(6rem * $size-scale);
// Border radii
$radius-factor: 1;
$radius-sm: calc(2px * $radius-factor * $size-scale);
$radius-md: calc(6px * $radius-factor * $size-scale);
$radius-lg: calc(8px * $radius-factor * $size-scale);
$radius-xl: calc(12px * $radius-factor * $size-scale);
$radius-full: 9999px;
// App-level CSS variables
#spark-app {
--size-scale: #{$size-scale};
--radius-factor: #{$radius-factor};
// Neutral colors (using slate from Radix)
--color-neutral-1: var(--slate-1);
--color-neutral-2: var(--slate-2);
--color-neutral-3: var(--slate-3);
--color-neutral-11: var(--slate-11);
--color-neutral-12: var(--slate-12);
// Accent colors (using blue from Radix)
--color-accent-9: var(--blue-9);
--color-accent-11: var(--blue-11);
// Foreground colors
--color-fg: var(--color-neutral-12);
--color-fg-secondary: var(--color-neutral-11);
// Background colors
--color-bg: #ffffff;
--color-bg-inset: var(--color-neutral-2);
--color-bg-overlay: #ffffff;
// Focus ring
--color-focus-ring: var(--color-accent-9);
// Fonts
--font-sans-serif: ui-sans-serif, system-ui, sans-serif;
--font-serif: ui-serif, Georgia, serif;
--font-monospace: ui-monospace, 'SF Mono', Monaco, Consolas, monospace;
--font-family: var(--font-sans-serif);
}
#spark-app.dark-theme {
--color-bg: var(--color-neutral-1);
--color-bg-inset: #000000;
--color-bg-overlay: var(--color-neutral-3);
}

View File

@@ -1,6 +1,5 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
return clsx(inputs)
}

View File

@@ -0,0 +1,24 @@
// Spacing function
@function spacing($key) {
@return map-get($spacing, $key);
}
// Border radius function
@function radius($key) {
@return map-get($radius, $key);
}
// Font size function
@function font-size($key) {
@return map-get($font-sizes, $key);
}
// Font weight function
@function font-weight($key) {
@return map-get($font-weights, $key);
}
// Breakpoint function
@function breakpoint($key) {
@return map-get($breakpoints, $key);
}

View File

@@ -0,0 +1,3 @@
@import './variables';
@import './functions';
@import './mixins';

View File

@@ -0,0 +1,212 @@
// Flexbox mixins
@mixin flex($direction: row, $justify: flex-start, $align: stretch, $wrap: nowrap) {
display: flex;
flex-direction: $direction;
justify-content: $justify;
align-items: $align;
flex-wrap: $wrap;
}
@mixin flex-center {
@include flex(row, center, center);
}
@mixin flex-between {
@include flex(row, space-between, center);
}
@mixin flex-col {
@include flex(column, flex-start, stretch);
}
// Grid mixin
@mixin grid($columns: 1, $gap: spacing(4)) {
display: grid;
grid-template-columns: repeat($columns, 1fr);
gap: $gap;
}
// Spacing mixins
@mixin p($value) {
padding: spacing($value);
}
@mixin px($value) {
padding-left: spacing($value);
padding-right: spacing($value);
}
@mixin py($value) {
padding-top: spacing($value);
padding-bottom: spacing($value);
}
@mixin pt($value) {
padding-top: spacing($value);
}
@mixin pr($value) {
padding-right: spacing($value);
}
@mixin pb($value) {
padding-bottom: spacing($value);
}
@mixin pl($value) {
padding-left: spacing($value);
}
@mixin m($value) {
margin: spacing($value);
}
@mixin mx($value) {
margin-left: spacing($value);
margin-right: spacing($value);
}
@mixin my($value) {
margin-top: spacing($value);
margin-bottom: spacing($value);
}
@mixin mt($value) {
margin-top: spacing($value);
}
@mixin mr($value) {
margin-right: spacing($value);
}
@mixin mb($value) {
margin-bottom: spacing($value);
}
@mixin ml($value) {
margin-left: spacing($value);
}
// Gap mixin
@mixin gap($value) {
gap: spacing($value);
}
// Border radius mixin
@mixin rounded($value: base) {
border-radius: radius($value);
}
// Typography mixins
@mixin text($size) {
font-size: font-size($size);
}
@mixin font($weight) {
font-weight: font-weight($weight);
}
// Responsive breakpoint mixin
@mixin respond-to($breakpoint) {
$size: breakpoint($breakpoint);
@media (min-width: $size) {
@content;
}
}
// Button base styles mixin
@mixin button-base {
@include px(4);
@include py(2);
@include rounded(md);
@include font(medium);
@include text(sm);
display: inline-flex;
align-items: center;
justify-content: center;
gap: spacing(2);
cursor: pointer;
transition: all 0.2s ease;
border: none;
outline: none;
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
// Card mixin
@mixin card {
background: $card;
color: $card-foreground;
border: 1px solid $border;
@include rounded(lg);
@include p(6);
}
// Shadow mixins
@mixin shadow-sm {
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}
@mixin shadow {
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
}
@mixin shadow-md {
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
}
@mixin shadow-lg {
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
}
// Transition mixin
@mixin transition($properties: all, $duration: 0.2s, $timing: ease) {
transition: $properties $duration $timing;
}
// Truncate text
@mixin truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Visually hidden (for accessibility)
@mixin sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
// Absolute positioning shortcuts
@mixin absolute-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@mixin absolute-fill {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
// Focus ring
@mixin focus-ring {
&:focus-visible {
outline: 2px solid $ring;
outline-offset: 2px;
}
}

View File

@@ -0,0 +1,119 @@
// Color Variables
$background: hsl(222.2, 84%, 4.9%);
$foreground: hsl(210, 40%, 98%);
$card: hsl(222.2, 84%, 4.9%);
$card-foreground: hsl(210, 40%, 98%);
$popover: hsl(222.2, 84%, 4.9%);
$popover-foreground: hsl(210, 40%, 98%);
$primary: hsl(263, 70%, 50%);
$primary-foreground: hsl(210, 40%, 98%);
$secondary: hsl(217.2, 32.6%, 17.5%);
$secondary-foreground: hsl(210, 40%, 98%);
$muted: hsl(217.2, 32.6%, 17.5%);
$muted-foreground: hsl(215, 20.2%, 65.1%);
$accent: hsl(195, 100%, 70%);
$accent-foreground: hsl(222.2, 84%, 4.9%);
$destructive: hsl(0, 62.8%, 30.6%);
$destructive-foreground: hsl(210, 40%, 98%);
$border: hsl(217.2, 32.6%, 17.5%);
$input: hsl(217.2, 32.6%, 17.5%);
$ring: hsl(195, 100%, 70%);
// Spacing Scale
$spacing: (
0: 0,
px: 1px,
0-5: 0.125rem,
1: 0.25rem,
1-5: 0.375rem,
2: 0.5rem,
2-5: 0.625rem,
3: 0.75rem,
3-5: 0.875rem,
4: 1rem,
5: 1.25rem,
6: 1.5rem,
7: 1.75rem,
8: 2rem,
9: 2.25rem,
10: 2.5rem,
11: 2.75rem,
12: 3rem,
14: 3.5rem,
16: 4rem,
20: 5rem,
24: 6rem,
28: 7rem,
32: 8rem,
36: 9rem,
40: 10rem,
48: 12rem,
56: 14rem,
64: 16rem,
72: 18rem,
80: 20rem,
96: 24rem,
);
// Border Radius
$radius: (
none: 0,
sm: 0.125rem,
base: 0.25rem,
md: 0.375rem,
lg: 0.5rem,
xl: 0.75rem,
2xl: 1rem,
3xl: 1.5rem,
full: 9999px,
);
// Font Sizes
$font-sizes: (
xs: 0.75rem,
sm: 0.875rem,
base: 1rem,
lg: 1.125rem,
xl: 1.25rem,
2xl: 1.5rem,
3xl: 1.875rem,
4xl: 2.25rem,
5xl: 3rem,
6xl: 3.75rem,
7xl: 4.5rem,
8xl: 6rem,
9xl: 8rem,
);
// Font Weights
$font-weights: (
thin: 100,
extralight: 200,
light: 300,
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
extrabold: 800,
black: 900,
);
// Breakpoints
$breakpoints: (
sm: 640px,
md: 768px,
lg: 1024px,
xl: 1280px,
2xl: 1536px,
);
// Z-index scale
$z-index: (
0: 0,
10: 10,
20: 20,
30: 30,
40: 40,
50: 50,
auto: auto,
);

View File

@@ -0,0 +1,146 @@
@import '../abstracts';
// Utility classes generator
@each $name, $value in $spacing {
.p-#{$name} { padding: $value !important; }
.px-#{$name} { padding-left: $value !important; padding-right: $value !important; }
.py-#{$name} { padding-top: $value !important; padding-bottom: $value !important; }
.pt-#{$name} { padding-top: $value !important; }
.pr-#{$name} { padding-right: $value !important; }
.pb-#{$name} { padding-bottom: $value !important; }
.pl-#{$name} { padding-left: $value !important; }
.m-#{$name} { margin: $value !important; }
.mx-#{$name} { margin-left: $value !important; margin-right: $value !important; }
.my-#{$name} { margin-top: $value !important; margin-bottom: $value !important; }
.mt-#{$name} { margin-top: $value !important; }
.mr-#{$name} { margin-right: $value !important; }
.mb-#{$name} { margin-bottom: $value !important; }
.ml-#{$name} { margin-left: $value !important; }
.gap-#{$name} { gap: $value !important; }
}
// Flexbox utilities
.flex { display: flex !important; }
.inline-flex { display: inline-flex !important; }
.flex-row { flex-direction: row !important; }
.flex-col { flex-direction: column !important; }
.flex-wrap { flex-wrap: wrap !important; }
.flex-nowrap { flex-wrap: nowrap !important; }
.flex-1 { flex: 1 1 0% !important; }
// Justify content
.justify-start { justify-content: flex-start !important; }
.justify-end { justify-content: flex-end !important; }
.justify-center { justify-content: center !important; }
.justify-between { justify-content: space-between !important; }
.justify-around { justify-content: space-around !important; }
// Align items
.items-start { align-items: flex-start !important; }
.items-end { align-items: flex-end !important; }
.items-center { align-items: center !important; }
.items-stretch { align-items: stretch !important; }
// Grid
.grid { display: grid !important; }
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)) !important; }
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; }
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)) !important; }
// Border radius
@each $name, $value in $radius {
.rounded-#{$name} { border-radius: $value !important; }
}
// Text sizes
@each $name, $value in $font-sizes {
.text-#{$name} { font-size: $value !important; }
}
// Font weights
@each $name, $value in $font-weights {
.font-#{$name} { font-weight: $value !important; }
}
// Text align
.text-left { text-align: left !important; }
.text-center { text-align: center !important; }
.text-right { text-align: right !important; }
// Colors
.text-foreground { color: $foreground !important; }
.text-muted-foreground { color: $muted-foreground !important; }
.text-primary { color: $primary !important; }
.text-destructive { color: $destructive !important; }
.text-accent { color: $accent !important; }
.bg-background { background-color: $background !important; }
.bg-card { background-color: $card !important; }
.bg-primary { background-color: $primary !important; }
.bg-secondary { background-color: $secondary !important; }
.bg-muted { background-color: $muted !important; }
.bg-accent { background-color: $accent !important; }
.bg-destructive { background-color: $destructive !important; }
// Border
.border { border: 1px solid $border !important; }
.border-b { border-bottom: 1px solid $border !important; }
.border-t { border-top: 1px solid $border !important; }
.border-l { border-left: 1px solid $border !important; }
.border-r { border-right: 1px solid $border !important; }
// Width & Height
.w-full { width: 100% !important; }
.h-full { height: 100% !important; }
.min-h-screen { min-height: 100vh !important; }
.max-w-3xl { max-width: 48rem !important; }
.max-w-7xl { max-width: 80rem !important; }
// Position
.relative { position: relative !important; }
.absolute { position: absolute !important; }
.fixed { position: fixed !important; }
.sticky { position: sticky !important; }
// Display
.hidden { display: none !important; }
.block { display: block !important; }
.inline-block { display: inline-block !important; }
// Overflow
.overflow-hidden { overflow: hidden !important; }
.overflow-auto { overflow: auto !important; }
.overflow-scroll { overflow: scroll !important; }
// Cursor
.cursor-pointer { cursor: pointer !important; }
.cursor-not-allowed { cursor: not-allowed !important; }
// Opacity
.opacity-50 { opacity: 0.5 !important; }
.opacity-75 { opacity: 0.75 !important; }
.opacity-100 { opacity: 1 !important; }
// Transitions
.transition { transition: all 0.2s ease !important; }
.transition-colors { transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease !important; }
// Shadow
.shadow-sm { @include shadow-sm; }
.shadow { @include shadow; }
.shadow-md { @include shadow-md; }
.shadow-lg { @include shadow-lg; }
// Space between children
.space-y-2 > * + * { margin-top: spacing(2) !important; }
.space-y-3 > * + * { margin-top: spacing(3) !important; }
.space-y-4 > * + * { margin-top: spacing(4) !important; }
.space-y-6 > * + * { margin-top: spacing(6) !important; }
.space-y-8 > * + * { margin-top: spacing(8) !important; }
.space-x-2 > * + * { margin-left: spacing(2) !important; }
.space-x-3 > * + * { margin-left: spacing(3) !important; }
.space-x-4 > * + * { margin-left: spacing(4) !important; }

View File

@@ -1,62 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: ["class"],
content: [
'./src/pages/**/*.{ts,tsx}',
'./src/components/**/*.{ts,tsx}',
'./src/app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
plugins: [],
}