) {
);
}
-function TableHead({ className, ...props }: React.ComponentProps<"th">) {
+function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
return (
| [role=checkbox]]:translate-y-[2px]",
+ 'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}
@@ -77,12 +78,12 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
);
}
-function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
return (
| [role=checkbox]]:translate-y-[2px]",
+ 'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className,
)}
{...props}
@@ -93,11 +94,11 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
function TableCaption({
className,
...props
-}: React.ComponentProps<"caption">) {
+}: React.ComponentProps<'caption'>) {
return (
);
diff --git a/packages/ui/src/tabs.tsx b/packages/ui/src/tabs.tsx
index c794ab2..193383e 100644
--- a/packages/ui/src/tabs.tsx
+++ b/packages/ui/src/tabs.tsx
@@ -1,8 +1,9 @@
-"use client";
+'use client';
-import * as React from "react";
-import { cn } from "@/lib/utils";
-import * as TabsPrimitive from "@radix-ui/react-tabs";
+import * as React from 'react';
+import * as TabsPrimitive from '@radix-ui/react-tabs';
+
+import { cn } from '@gib/ui';
function Tabs({
className,
@@ -11,7 +12,7 @@ function Tabs({
return (
);
@@ -25,7 +26,7 @@ function TabsList({
);
diff --git a/packages/ui/src/theme.tsx b/packages/ui/src/theme.tsx
index 8cc6f38..e02825d 100644
--- a/packages/ui/src/theme.tsx
+++ b/packages/ui/src/theme.tsx
@@ -1,184 +1,70 @@
-"use client";
+'use client';
-import * as React from "react";
-import { DesktopIcon, MoonIcon, SunIcon } from "@radix-ui/react-icons";
-import * as z from "zod/v4";
+import type { ComponentProps } from 'react';
+import { useEffect, useState } from 'react';
+import { Moon, Sun } from 'lucide-react';
+import { ThemeProvider as NextThemesProvider, useTheme } from 'next-themes';
-import { Button } from "./button";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "./dropdown-menu";
+import { Button, cn } from '@gib/ui';
-const ThemeModeSchema = z.enum(["light", "dark", "auto"]);
+const ThemeProvider = ({
+ children,
+ ...props
+}: ComponentProps) => {
+ const [mounted, setMounted] = useState(false);
-const themeKey = "theme-mode";
+ useEffect(() => {
+ setMounted(true);
+ }, []);
-export type ThemeMode = z.output;
-export type ResolvedTheme = Exclude;
+ if (!mounted) return null;
+ return {children};
+};
-const getStoredThemeMode = (): ThemeMode => {
- if (typeof window === "undefined") return "auto";
- try {
- const storedTheme = localStorage.getItem(themeKey);
- return ThemeModeSchema.parse(storedTheme);
- } catch {
- return "auto";
+type ThemeToggleProps = {
+ size?: number;
+ buttonProps?: Omit, 'onClick'>;
+};
+
+const ThemeToggle = ({ size = 1, buttonProps }: ThemeToggleProps) => {
+ const { setTheme, resolvedTheme } = useTheme();
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ if (!mounted) {
+ return (
+
+ );
}
-};
-const setStoredThemeMode = (theme: ThemeMode) => {
- try {
- const parsedTheme = ThemeModeSchema.parse(theme);
- localStorage.setItem(themeKey, parsedTheme);
- } catch {
- // Silently fail if localStorage is unavailable
- }
-};
-
-const getSystemTheme = () => {
- if (typeof window === "undefined") return "light";
- return window.matchMedia("(prefers-color-scheme: dark)").matches
- ? "dark"
- : "light";
-};
-
-const updateThemeClass = (themeMode: ThemeMode) => {
- const root = document.documentElement;
- root.classList.remove("light", "dark", "auto");
- const newTheme = themeMode === "auto" ? getSystemTheme() : themeMode;
- root.classList.add(newTheme);
-
- if (themeMode === "auto") {
- root.classList.add("auto");
- }
-};
-
-const setupPreferredListener = () => {
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
- const handler = () => updateThemeClass("auto");
- mediaQuery.addEventListener("change", handler);
- return () => mediaQuery.removeEventListener("change", handler);
-};
-
-const getNextTheme = (current: ThemeMode): ThemeMode => {
- const themes: ThemeMode[] =
- getSystemTheme() === "dark"
- ? ["auto", "light", "dark"]
- : ["auto", "dark", "light"];
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- return themes[(themes.indexOf(current) + 1) % themes.length]!;
-};
-
-export const themeDetectorScript = (function () {
- function themeFn() {
- const isValidTheme = (theme: string): theme is ThemeMode => {
- const validThemes = ["light", "dark", "auto"] as const;
- return validThemes.includes(theme as ThemeMode);
- };
-
- const storedTheme = localStorage.getItem("theme-mode") ?? "auto";
- const validTheme = isValidTheme(storedTheme) ? storedTheme : "auto";
-
- if (validTheme === "auto") {
- const autoTheme = window.matchMedia("(prefers-color-scheme: dark)")
- .matches
- ? "dark"
- : "light";
- document.documentElement.classList.add(autoTheme, "auto");
- } else {
- document.documentElement.classList.add(validTheme);
- }
- }
- return `(${themeFn.toString()})();`;
-})();
-
-interface ThemeContextProps {
- themeMode: ThemeMode;
- resolvedTheme: ResolvedTheme;
- setTheme: (theme: ThemeMode) => void;
- toggleMode: () => void;
-}
-const ThemeContext = React.createContext(
- undefined,
-);
-
-export function ThemeProvider({ children }: React.PropsWithChildren) {
- const [themeMode, setThemeMode] = React.useState(getStoredThemeMode);
-
- React.useEffect(() => {
- if (themeMode !== "auto") return;
- return setupPreferredListener();
- }, [themeMode]);
-
- const resolvedTheme = themeMode === "auto" ? getSystemTheme() : themeMode;
-
- const setTheme = (newTheme: ThemeMode) => {
- setThemeMode(newTheme);
- setStoredThemeMode(newTheme);
- updateThemeClass(newTheme);
- };
-
- const toggleMode = () => {
- setTheme(getNextTheme(themeMode));
+ const toggleTheme = () => {
+ if (resolvedTheme === 'dark') setTheme('light');
+ else setTheme('dark');
};
return (
-
-
- {children}
-
+
+
);
-}
+};
-export function useTheme() {
- const context = React.use(ThemeContext);
- if (!context) {
- throw new Error("useTheme must be used within a ThemeProvider");
- }
- return context;
-}
-
-export function ThemeToggle() {
- const { setTheme } = useTheme();
-
- return (
-
-
-
-
-
- setTheme("light")}>
- Light
-
- setTheme("dark")}>
- Dark
-
- setTheme("auto")}>
- System
-
-
-
- );
-}
+export { ThemeProvider, ThemeToggle, type ThemeToggleProps };
diff --git a/packages/ui/src/toast.tsx b/packages/ui/src/toast.tsx
deleted file mode 100644
index 1ab424c..0000000
--- a/packages/ui/src/toast.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-"use client";
-
-import type { ToasterProps } from "sonner";
-import { Toaster as Sonner, toast } from "sonner";
-
-import { useTheme } from "./theme";
-
-export const Toaster = ({ ...props }: ToasterProps) => {
- const { themeMode } = useTheme();
-
- return (
-
- );
-};
-
-export { toast };
diff --git a/tools/eslint/.cache/.prettiercache b/tools/eslint/.cache/.prettiercache
new file mode 100644
index 0000000..49560de
--- /dev/null
+++ b/tools/eslint/.cache/.prettiercache
@@ -0,0 +1 @@
+[["1","2","3","4","5"],{"key":"6","value":"7"},{"key":"8","value":"9"},{"key":"10","value":"11"},{"key":"12","value":"13"},{"key":"14","value":"15"},"/home/gib/Documents/Code/studybuddy/tools/eslint/base.ts",{"size":2511,"mtime":1768143609481,"data":"16"},"/home/gib/Documents/Code/studybuddy/tools/eslint/nextjs.ts",{"size":440,"mtime":1768143609505,"data":"17"},"/home/gib/Documents/Code/studybuddy/tools/eslint/package.json",{"size":979,"mtime":1767666484841,"data":"18"},"/home/gib/Documents/Code/studybuddy/tools/eslint/react.ts",{"size":592,"mtime":1768143609549,"data":"19"},"/home/gib/Documents/Code/studybuddy/tools/eslint/tsconfig.json",{"size":94,"mtime":1767666484841,"data":"20"},{"hashOfOptions":"21"},{"hashOfOptions":"22"},{"hashOfOptions":"23"},{"hashOfOptions":"24"},{"hashOfOptions":"25"},"1431902546","3979710663","3047921244","3279956730","3404194763"]
\ No newline at end of file
diff --git a/tools/eslint/base.ts b/tools/eslint/base.ts
index 30354b1..35278d6 100644
--- a/tools/eslint/base.ts
+++ b/tools/eslint/base.ts
@@ -1,33 +1,33 @@
-import * as path from "node:path";
-import { includeIgnoreFile } from "@eslint/compat";
-import eslint from "@eslint/js";
-import importPlugin from "eslint-plugin-import";
-import turboPlugin from "eslint-plugin-turbo";
-import { defineConfig } from "eslint/config";
-import tseslint from "typescript-eslint";
+import * as path from 'node:path';
+import { includeIgnoreFile } from '@eslint/compat';
+import eslint from '@eslint/js';
+import importPlugin from 'eslint-plugin-import';
+import turboPlugin from 'eslint-plugin-turbo';
+import { defineConfig } from 'eslint/config';
+import tseslint from 'typescript-eslint';
/**
* All packages that leverage t3-env should use this rule
*/
export const restrictEnvAccess = defineConfig(
- { ignores: ["**/env.ts"] },
+ { ignores: ['**/env.ts'] },
{
- files: ["**/*.js", "**/*.ts", "**/*.tsx"],
+ files: ['**/*.js', '**/*.ts', '**/*.tsx'],
rules: {
- "no-restricted-properties": [
- "error",
+ 'no-restricted-properties': [
+ 'error',
{
- object: "process",
- property: "env",
+ object: 'process',
+ property: 'env',
message:
"Use `import { env } from '@/env'` instead to ensure validated types.",
},
],
- "no-restricted-imports": [
- "error",
+ 'no-restricted-imports': [
+ 'error',
{
- name: "process",
- importNames: ["env"],
+ name: 'process',
+ importNames: ['env'],
message:
"Use `import { env } from '@/env'` instead to ensure validated types.",
},
@@ -38,10 +38,10 @@ export const restrictEnvAccess = defineConfig(
export const baseConfig = defineConfig(
// Ignore files not tracked by VCS and any config files
- includeIgnoreFile(path.join(import.meta.dirname, "../../.gitignore")),
- { ignores: ["**/*.config.*"] },
+ includeIgnoreFile(path.join(import.meta.dirname, '../../.gitignore')),
+ { ignores: ['**/*.config.*'] },
{
- files: ["**/*.js", "**/*.ts", "**/*.tsx"],
+ files: ['**/*.js', '**/*.ts', '**/*.tsx'],
plugins: {
import: importPlugin,
turbo: turboPlugin,
@@ -54,26 +54,26 @@ export const baseConfig = defineConfig(
],
rules: {
...turboPlugin.configs.recommended.rules,
- "@typescript-eslint/no-unused-vars": [
- "error",
- { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
- "@typescript-eslint/consistent-type-imports": [
- "warn",
- { prefer: "type-imports", fixStyle: "separate-type-imports" },
+ '@typescript-eslint/consistent-type-imports': [
+ 'warn',
+ { prefer: 'type-imports', fixStyle: 'separate-type-imports' },
],
- "@typescript-eslint/no-misused-promises": [
+ '@typescript-eslint/no-misused-promises': [
2,
{ checksVoidReturn: { attributes: false } },
],
- "@typescript-eslint/no-unnecessary-condition": [
- "error",
+ '@typescript-eslint/no-unnecessary-condition': [
+ 'error',
{
allowConstantLoopConditions: true,
},
],
- "@typescript-eslint/no-non-null-assertion": "error",
- "import/consistent-type-specifier-style": ["error", "prefer-top-level"],
+ '@typescript-eslint/no-non-null-assertion': 'error',
+ 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
},
},
{
diff --git a/tools/eslint/nextjs.ts b/tools/eslint/nextjs.ts
index 6858c53..efc4902 100644
--- a/tools/eslint/nextjs.ts
+++ b/tools/eslint/nextjs.ts
@@ -1,15 +1,15 @@
-import nextPlugin from "@next/eslint-plugin-next";
-import { defineConfig } from "eslint/config";
+import nextPlugin from '@next/eslint-plugin-next';
+import { defineConfig } from 'eslint/config';
export const nextjsConfig = defineConfig({
- files: ["**/*.ts", "**/*.tsx"],
+ files: ['**/*.ts', '**/*.tsx'],
plugins: {
- "@next/next": nextPlugin,
+ '@next/next': nextPlugin,
},
rules: {
...nextPlugin.configs.recommended.rules,
- ...nextPlugin.configs["core-web-vitals"].rules,
+ ...nextPlugin.configs['core-web-vitals'].rules,
// TypeError: context.getAncestors is not a function
- "@next/next/no-duplicate-head": "off",
+ '@next/next/no-duplicate-head': 'off',
},
});
diff --git a/tools/eslint/react.ts b/tools/eslint/react.ts
index 6163287..4b1d321 100644
--- a/tools/eslint/react.ts
+++ b/tools/eslint/react.ts
@@ -1,19 +1,19 @@
-import reactPlugin from "eslint-plugin-react";
-import reactHooks from "eslint-plugin-react-hooks";
-import { defineConfig } from "eslint/config";
+import reactPlugin from 'eslint-plugin-react';
+import reactHooks from 'eslint-plugin-react-hooks';
+import { defineConfig } from 'eslint/config';
export const reactConfig = defineConfig(
{
- files: ["**/*.ts", "**/*.tsx"],
+ files: ['**/*.ts', '**/*.tsx'],
...reactPlugin.configs.flat.recommended,
- ...reactPlugin.configs.flat["jsx-runtime"],
+ ...reactPlugin.configs.flat['jsx-runtime'],
languageOptions: {
...reactPlugin.configs.flat.recommended?.languageOptions,
- ...reactPlugin.configs.flat["jsx-runtime"]?.languageOptions,
+ ...reactPlugin.configs.flat['jsx-runtime']?.languageOptions,
globals: {
- React: "writable",
+ React: 'writable',
},
},
},
- reactHooks.configs.flat["recommended-latest"]!,
+ reactHooks.configs.flat['recommended-latest']!,
);
diff --git a/tools/prettier/.cache/.prettiercache b/tools/prettier/.cache/.prettiercache
new file mode 100644
index 0000000..dd42f7c
--- /dev/null
+++ b/tools/prettier/.cache/.prettiercache
@@ -0,0 +1 @@
+[["1","2","3"],{"key":"4","value":"5"},{"key":"6","value":"7"},{"key":"8","value":"9"},"/home/gib/Documents/Code/studybuddy/tools/prettier/index.js",{"size":1170,"mtime":1768143608718,"data":"10"},"/home/gib/Documents/Code/studybuddy/tools/prettier/package.json",{"size":607,"mtime":1767666484841,"data":"11"},"/home/gib/Documents/Code/studybuddy/tools/prettier/tsconfig.json",{"size":94,"mtime":1767666484841,"data":"12"},{"hashOfOptions":"13"},{"hashOfOptions":"14"},{"hashOfOptions":"15"},"1313973583","3244410010","3673900301"]
\ No newline at end of file
diff --git a/tools/prettier/index.js b/tools/prettier/index.js
index 6fedac4..2fa0590 100644
--- a/tools/prettier/index.js
+++ b/tools/prettier/index.js
@@ -5,42 +5,42 @@
/** @type { PrettierConfig | SortImportsConfig | TailwindConfig } */
const config = {
singleQuote: true,
- trailingComma: "all",
+ trailingComma: 'all',
tabWidth: 2,
printWidth: 80,
plugins: [
- "@ianvs/prettier-plugin-sort-imports",
- "prettier-plugin-tailwindcss",
+ '@ianvs/prettier-plugin-sort-imports',
+ 'prettier-plugin-tailwindcss',
],
- tailwindFunctions: ["cn", "cva"],
+ tailwindFunctions: ['cn', 'cva'],
importOrder: [
- "",
- "^(react/(.*)$)|^(react$)|^(react-native(.*)$)",
- "^(next/(.*)$)|^(next$)",
- "^(expo(.*)$)|^(expo$)",
- "",
- "",
- "^@gib",
- "^@gib/(.*)$",
- "",
- "^[.|..|~]",
- "^~/",
- "^[../]",
- "^[./]",
+ '',
+ '^(react/(.*)$)|^(react$)|^(react-native(.*)$)',
+ '^(next/(.*)$)|^(next$)',
+ '^(expo(.*)$)|^(expo$)',
+ '',
+ '',
+ '^@gib',
+ '^@gib/(.*)$',
+ '',
+ '^[.|..|~]',
+ '^~/',
+ '^[../]',
+ '^[./]',
],
- importOrderParserPlugins: ["typescript", "jsx", "decorators-legacy"],
- importOrderTypeScriptVersion: "5.0.0",
+ importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'],
+ importOrderTypeScriptVersion: '5.0.0',
overrides: [
{
- files: "*.json.hbs",
+ files: '*.json.hbs',
options: {
- parser: "json",
+ parser: 'json',
},
},
{
- files: "*.ts.hbs",
+ files: '*.ts.hbs',
options: {
- parser: "babel",
+ parser: 'babel',
},
},
],
diff --git a/tools/tailwind/.cache/.prettiercache b/tools/tailwind/.cache/.prettiercache
new file mode 100644
index 0000000..f843874
--- /dev/null
+++ b/tools/tailwind/.cache/.prettiercache
@@ -0,0 +1 @@
+[["1","2","3","4","5"],{"key":"6","value":"7"},{"key":"8","value":"9"},{"key":"10","value":"11"},{"key":"12","value":"13"},{"key":"14","value":"15"},"/home/gib/Documents/Code/studybuddy/tools/tailwind/postcss-config.js",{"size":70,"mtime":1768143608435,"data":"16"},"/home/gib/Documents/Code/studybuddy/tools/tailwind/package.json",{"size":851,"mtime":1767666484842,"data":"17"},"/home/gib/Documents/Code/studybuddy/tools/tailwind/tsconfig.json",{"size":94,"mtime":1767666484842,"data":"18"},"/home/gib/Documents/Code/studybuddy/tools/tailwind/eslint.config.ts",{"size":143,"mtime":1768143608345,"data":"19"},"/home/gib/Documents/Code/studybuddy/tools/tailwind/theme.css",{"size":6741,"mtime":1767666484842,"data":"20"},{"hashOfOptions":"21"},{"hashOfOptions":"22"},{"hashOfOptions":"23"},{"hashOfOptions":"24"},{"hashOfOptions":"25"},"261655752","130635797","3253964882","957385747","2634406715"]
\ No newline at end of file
diff --git a/tools/tailwind/eslint.config.ts b/tools/tailwind/eslint.config.ts
index 2c8ae6f..524c3fd 100644
--- a/tools/tailwind/eslint.config.ts
+++ b/tools/tailwind/eslint.config.ts
@@ -1,5 +1,5 @@
-import { defineConfig } from "eslint/config";
+import { defineConfig } from 'eslint/config';
-import { baseConfig } from "@gib/eslint-config/base";
+import { baseConfig } from '@gib/eslint-config/base';
export default defineConfig(baseConfig);
diff --git a/tools/tailwind/postcss-config.js b/tools/tailwind/postcss-config.js
index c2ddf74..a34a3d5 100644
--- a/tools/tailwind/postcss-config.js
+++ b/tools/tailwind/postcss-config.js
@@ -1,5 +1,5 @@
export default {
plugins: {
- "@tailwindcss/postcss": {},
+ '@tailwindcss/postcss': {},
},
};
|