feat: upgrade tailwind to v4 (7) shadcnui admin components

This commit is contained in:
javayhu 2025-03-21 00:45:13 +08:00
parent 01ad90e905
commit 808699b238
5 changed files with 439 additions and 43 deletions

View File

@ -91,6 +91,7 @@
"swiper": "^11.2.5",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.2.4",
"unist-util-visit": "^5.0.0",
"use-intl": "^3.26.5",
"vaul": "^1.1.2",

8
pnpm-lock.yaml generated
View File

@ -251,6 +251,9 @@ importers:
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@4.0.14)
tw-animate-css:
specifier: ^1.2.4
version: 1.2.4
unist-util-visit:
specifier: ^5.0.0
version: 5.0.0
@ -5229,6 +5232,9 @@ packages:
engines: {node: '>=18.0.0'}
hasBin: true
tw-animate-css@1.2.4:
resolution: {integrity: sha512-yt+HkJB41NAvOffe4NweJU6fLqAlVx/mBX6XmHRp15kq0JxTtOKaIw8pVSWM1Z+n2nXtyi7cW6C9f0WG/F/QAQ==}
type-fest@4.35.0:
resolution: {integrity: sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==}
engines: {node: '>=16'}
@ -10940,6 +10946,8 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
tw-animate-css@1.2.4: {}
type-fest@4.35.0: {}
type-flag@3.0.0: {}

View File

@ -0,0 +1,68 @@
"use client";
import {
ReactNode,
createContext,
useContext,
useEffect,
useState,
} from "react";
const COOKIE_NAME = "active_theme";
const DEFAULT_THEME = "default";
function setThemeCookie(theme: string) {
if (typeof window === "undefined") return;
document.cookie = `${COOKIE_NAME}=${theme}; path=/; max-age=31536000; SameSite=Lax; ${window.location.protocol === "https:" ? "Secure;" : ""
}`;
}
type ThemeContextType = {
activeTheme: string;
setActiveTheme: (theme: string) => void;
};
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export function ActiveThemeProvider({
children,
initialTheme,
}: {
children: ReactNode;
initialTheme?: string;
}) {
const [activeTheme, setActiveTheme] = useState<string>(
() => initialTheme || DEFAULT_THEME
);
useEffect(() => {
setThemeCookie(activeTheme);
Array.from(document.body.classList)
.filter((className) => className.startsWith("theme-"))
.forEach((className) => {
document.body.classList.remove(className);
});
document.body.classList.add(`theme-${activeTheme}`);
if (activeTheme.endsWith("-scaled")) {
document.body.classList.add("theme-scaled");
}
}, [activeTheme]);
return (
<ThemeContext.Provider value={{ activeTheme, setActiveTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useThemeConfig() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error(
"useThemeConfig must be used within an ActiveThemeProvider"
);
}
return context;
}

View File

@ -0,0 +1,103 @@
"use client";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useThemeConfig } from "./active-theme";
const DEFAULT_THEMES = [
{
name: "Default",
value: "default",
},
{
name: "Blue",
value: "blue",
},
{
name: "Green",
value: "green",
},
{
name: "Amber",
value: "amber",
},
];
const SCALED_THEMES = [
{
name: "Default",
value: "default-scaled",
},
{
name: "Blue",
value: "blue-scaled",
},
];
const MONO_THEMES = [
{
name: "Mono",
value: "mono-scaled",
},
];
export function ThemeSelector() {
const { activeTheme, setActiveTheme } = useThemeConfig();
return (
<div className="flex items-center gap-2">
<Label htmlFor="theme-selector" className="sr-only">
Theme
</Label>
<Select value={activeTheme} onValueChange={setActiveTheme}>
<SelectTrigger
id="theme-selector"
size="sm"
className="justify-start *:data-[slot=select-value]:w-12"
>
<span className="text-muted-foreground hidden sm:block">
Select a theme:
</span>
<span className="text-muted-foreground block sm:hidden">Theme</span>
<SelectValue placeholder="Select a theme" />
</SelectTrigger>
<SelectContent align="end">
<SelectGroup>
<SelectLabel>Default</SelectLabel>
{DEFAULT_THEMES.map((theme) => (
<SelectItem key={theme.name} value={theme.value}>
{theme.name}
</SelectItem>
))}
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Scaled</SelectLabel>
{SCALED_THEMES.map((theme) => (
<SelectItem key={theme.name} value={theme.value}>
{theme.name}
</SelectItem>
))}
</SelectGroup>
<SelectGroup>
<SelectLabel>Monospaced</SelectLabel>
{MONO_THEMES.map((theme) => (
<SelectItem key={theme.name} value={theme.value}>
{theme.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
);
}

View File

@ -1,16 +1,165 @@
@import 'tailwindcss';
@plugin 'tailwindcss-animate';
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@theme {
@theme inline {
--font-sans: var(--font-dm-sans);
--font-mono: var(--font-dm-mono);
--font-serif: var(--font-dm-serif-text);
--font-bricolage: var(--font-bricolage-grotesque);
--font-heading: var(--font-dm-sans);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
/* animate-css for magic ui components */
--animate-shiny-text: shiny-text 8s infinite;
--animate-rainbow: rainbow var(--speed, 2s) infinite linear;
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
@keyframes shiny-text {
0%,
90%,
100% {
background-position: calc(-100% - var(--shiny-width)) 0;
}
30%,
60% {
background-position: calc(100% + var(--shiny-width)) 0;
}
}
@keyframes rainbow {
0% {
background-position: 0%;
}
100% {
background-position: 200%;
}
}
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.147 0.004 49.25);
--card: oklch(1 0 0);
--card-foreground: oklch(0.147 0.004 49.25);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.147 0.004 49.25);
--primary: oklch(0.216 0.006 56.043);
--primary-foreground: oklch(0.985 0.001 106.423);
--secondary: oklch(0.97 0.001 106.424);
--secondary-foreground: oklch(0.216 0.006 56.043);
--muted: oklch(0.97 0.001 106.424);
--muted-foreground: oklch(0.553 0.013 58.071);
--accent: oklch(0.97 0.001 106.424);
--accent-foreground: oklch(0.216 0.006 56.043);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.923 0.003 48.717);
--input: oklch(0.923 0.003 48.717);
--ring: oklch(0.709 0.01 56.259);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0.001 106.423);
--sidebar-foreground: oklch(0.147 0.004 49.25);
--sidebar-primary: oklch(0.216 0.006 56.043);
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
--sidebar-accent: oklch(0.97 0.001 106.424);
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
--sidebar-border: oklch(0.923 0.003 48.717);
--sidebar-ring: oklch(0.709 0.01 56.259);
}
.dark {
--background: oklch(0.147 0.004 49.25);
--foreground: oklch(0.985 0.001 106.423);
--card: oklch(0.216 0.006 56.043);
--card-foreground: oklch(0.985 0.001 106.423);
--popover: oklch(0.216 0.006 56.043);
--popover-foreground: oklch(0.985 0.001 106.423);
--primary: oklch(0.923 0.003 48.717);
--primary-foreground: oklch(0.216 0.006 56.043);
--secondary: oklch(0.268 0.007 34.298);
--secondary-foreground: oklch(0.985 0.001 106.423);
--muted: oklch(0.268 0.007 34.298);
--muted-foreground: oklch(0.709 0.01 56.259);
--accent: oklch(0.268 0.007 34.298);
--accent-foreground: oklch(0.985 0.001 106.423);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.553 0.013 58.071);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.216 0.006 56.043);
--sidebar-foreground: oklch(0.985 0.001 106.423);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
--sidebar-accent: oklch(0.268 0.007 34.298);
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.553 0.013 58.071);
}
/* @theme {
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
@ -104,7 +253,7 @@
height: 0;
}
}
}
} */
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
@ -124,51 +273,118 @@
}
}
@layer base {
/* Import theme variables from theme-xxx.css as you need */
/* all the themes are from https://ui.shadcn.com/themes */
/* @import "./theme-blue.css"; */
/* @import "./theme-violet.css"; */
/* @import "./theme-zinc.css"; */
@import "./theme.css";
/* please notice that sidebar theme need to be set manually */
/* https://ui.shadcn.com/docs/components/sidebar#theming */
:root {
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%}
.dark {
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%}
}
@layer base {
* {
@apply border-border;
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
html {
/* prevent the layout from shifting when the scrollbar appears or disappears. */
/* after upgrading to tailwindcss@4, no need to set this anymore, :-) */
/* scrollbar-gutter: stable; */
/* Force the scrollbar to always be visible to prevent layout shifts */
/* can not set this, otherwise the theme switcher will disappear when click it after page scrolled */
/* overflow-y: scroll; */
}
body {
@apply overscroll-none bg-transparent;
}
:root {
/* --font-sans: var(--font-inter); */
--header-height: calc(var(--spacing) * 12 + 1px);
}
.theme-scaled {
@media (min-width: 1024px) {
--radius: 0.6rem;
--text-lg: 1.05rem;
--text-base: 0.85rem;
--text-sm: 0.8rem;
--spacing: 0.222222rem;
}
[data-slot="card"] {
--spacing: 0.16rem;
}
[data-slot="select-trigger"],
[data-slot="toggle-group-item"] {
--spacing: 0.222222rem;
}
}
.theme-default,
.theme-default-scaled {
--primary: var(--color-neutral-600);
--primary-foreground: var(--color-neutral-50);
@variant dark {
--primary: var(--color-neutral-500);
--primary-foreground: var(--color-neutral-50);
}
}
.theme-blue,
.theme-blue-scaled {
--primary: var(--color-blue-600);
--primary-foreground: var(--color-blue-50);
@variant dark {
--primary: var(--color-blue-500);
--primary-foreground: var(--color-blue-50);
}
}
.theme-green,
.theme-green-scaled {
--primary: var(--color-lime-600);
--primary-foreground: var(--color-lime-50);
@variant dark {
--primary: var(--color-lime-600);
--primary-foreground: var(--color-lime-50);
}
}
.theme-amber,
.theme-amber-scaled {
--primary: var(--color-amber-600);
--primary-foreground: var(--color-amber-50);
@variant dark {
--primary: var(--color-amber-500);
--primary-foreground: var(--color-amber-50);
}
}
.theme-mono,
.theme-mono-scaled {
--font-sans: var(--font-mono);
--primary: var(--color-neutral-600);
--primary-foreground: var(--color-neutral-50);
@variant dark {
--primary: var(--color-neutral-500);
--primary-foreground: var(--color-neutral-50);
}
.rounded-xs,
.rounded-sm,
.rounded-md,
.rounded-lg,
.rounded-xl {
@apply !rounded-none;
border-radius: 0;
}
.shadow-xs,
.shadow-sm,
.shadow-md,
.shadow-lg,
.shadow-xl {
@apply !shadow-none;
}
[data-slot="toggle-group"],
[data-slot="toggle-group-item"] {
@apply !rounded-none !shadow-none;
}
}