Compare commits

..

1 Commits

Author SHA1 Message Date
shadcn
84eb464ae1 ci: add request workflow 2025-10-31 11:05:18 +04:00
81 changed files with 1827 additions and 3098 deletions

131
.github/workflows/request.yml vendored Normal file
View File

@@ -0,0 +1,131 @@
name: "Convert requests to discussions"
on:
workflow_dispatch:
schedule:
# This runs every hour: https://crontab.guru/#0_*_*_*_*
- cron: "0 * * * *"
jobs:
convert:
runs-on: ubuntu-latest
if: github.repository_owner == 'shadcn-ui'
steps:
- name: "Convert issues to discussions"
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// Fetch issues with "area: request" label (limit 20).
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'area: request',
state: 'open',
per_page: 20,
sort: 'created',
direction: 'asc',
});
console.log(`Found ${issues.length} issues with "area: request" label`);
if (issues.length === 0) {
console.log('No issues to convert');
return;
}
// Get the repository node ID for GraphQL queries.
const repoQuery = `
query getRepo($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
id
discussionCategories(first: 20) {
nodes {
id
name
slug
}
}
}
}
`;
const repoResult = await github.graphql(repoQuery, {
owner: context.repo.owner,
repo: context.repo.repo,
});
const requestsCategory = repoResult.repository.discussionCategories.nodes.find(
cat => cat.name.toLowerCase() === 'requests' || cat.slug.toLowerCase() === 'requests'
);
if (!requestsCategory) {
throw new Error('Requests category not found in discussions. Available categories: ' +
repoResult.repository.discussionCategories.nodes.map(c => c.name).join(', '));
}
// Convert each issue to a discussion.
for (const issue of issues) {
try {
// Create discussion from issue using GraphQL.
const discussionTitle = issue.title;
const discussionBody = `**Converted from issue #${issue.number}**
${issue.body || ''}
---
_This discussion was automatically created from [issue #${issue.number}](${issue.html_url})_`;
const createDiscussionMutation = `
mutation createDiscussion($repositoryId: ID!, $categoryId: ID!, $title: String!, $body: String!) {
createDiscussion(input: {
repositoryId: $repositoryId
categoryId: $categoryId
title: $title
body: $body
}) {
discussion {
id
number
url
}
}
}
`;
const discussionResult = await github.graphql(createDiscussionMutation, {
repositoryId: repoResult.repository.id,
categoryId: requestsCategory.id,
title: discussionTitle,
body: discussionBody,
});
const discussion = discussionResult.createDiscussion.discussion;
console.log(`Created discussion #${discussion.number} from issue #${issue.number}`);
// Add a comment to the original issue linking to the discussion.
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `This issue has been converted to a discussion: [#${discussion.number}](${discussion.url})`,
});
// Close the original issue.
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
});
console.log(`Closed issue #${issue.number}`);
} catch (error) {
console.error(`Failed to convert issue #${issue.number}:`, error);
// Continue with next issue instead of failing the entire workflow.
}
}
console.log(`Completed processing ${issues.length} issues`);

View File

@@ -1,5 +1,4 @@
import type { Metadata } from "next"
import { NuqsAdapter } from "nuqs/adapters/next/app"
import { META_THEME_COLORS, siteConfig } from "@/lib/config"
import { fontVariables } from "@/lib/fonts"
@@ -85,20 +84,18 @@ export default function RootLayout({
</head>
<body
className={cn(
"group/body overscroll-none antialiased [--footer-height:calc(var(--spacing)*14)] [--header-height:calc(var(--spacing)*14)] xl:[--footer-height:calc(var(--spacing)*24)]",
"group/body theme-blue overscroll-none antialiased [--footer-height:calc(var(--spacing)*14)] [--header-height:calc(var(--spacing)*14)] xl:[--footer-height:calc(var(--spacing)*24)]",
fontVariables
)}
>
<ThemeProvider>
<LayoutProvider>
<NuqsAdapter>
<ActiveThemeProvider>
{children}
<TailwindIndicator />
<Toaster position="top-center" />
<Analytics />
</ActiveThemeProvider>
</NuqsAdapter>
<ActiveThemeProvider initialTheme="blue">
{children}
<TailwindIndicator />
<Toaster position="top-center" />
<Analytics />
</ActiveThemeProvider>
</LayoutProvider>
</ThemeProvider>
</body>

View File

@@ -8,7 +8,7 @@ import {
useState,
} from "react"
const DEFAULT_THEME = "default"
const DEFAULT_THEME = "blue"
type ThemeContextType = {
activeTheme: string

View File

@@ -1,11 +1,8 @@
"use client"
import * as React from "react"
import { IconArrowUpRight } from "@tabler/icons-react"
import { useSearchRegistry } from "@/hooks/use-search-registry"
import { DirectoryAddButton } from "@/components/directory-add-button"
import globalRegistries from "@/registry/directory.json"
import registries from "@/registry/directory.json"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Item,
@@ -19,72 +16,55 @@ import {
ItemTitle,
} from "@/registry/new-york-v4/ui/item"
import { SearchDirectory } from "./search-directory"
function getHomepageUrl(homepage: string) {
const url = new URL(homepage)
url.searchParams.set("utm_source", "ui.shadcn.com")
url.searchParams.set("utm_medium", "referral")
url.searchParams.set("utm_campaign", "directory")
return url.toString()
}
export function DirectoryList() {
const { registries } = useSearchRegistry()
return (
<div className="mt-6">
<SearchDirectory />
<ItemGroup className="my-8">
{registries.map((registry, index) => (
<React.Fragment key={index}>
<Item className="group/item relative gap-6 px-0">
<ItemMedia
variant="image"
dangerouslySetInnerHTML={{ __html: registry.logo }}
className="*:[svg]:fill-foreground grayscale *:[svg]:size-8"
/>
<ItemContent>
<ItemTitle>
<a
href={getHomepageUrl(registry.homepage)}
target="_blank"
rel="noopener noreferrer external"
>
{registry.name}
</a>
</ItemTitle>
{registry.description && (
<ItemDescription className="text-pretty">
{registry.description}
</ItemDescription>
)}
</ItemContent>
<ItemActions className="relative z-10 hidden self-start sm:flex">
<Button size="sm" variant="outline" asChild>
<a
href={getHomepageUrl(registry.homepage)}
target="_blank"
rel="noopener noreferrer external"
>
View <IconArrowUpRight />
</a>
</Button>
<DirectoryAddButton registry={registry} />
</ItemActions>
<ItemFooter className="justify-start pl-16 sm:hidden">
<Button size="sm" variant="outline">
<ItemGroup className="my-8">
{registries.map((registry, index) => (
<React.Fragment key={index}>
<Item className="group/item relative gap-6 px-0 sm:px-4">
<ItemMedia
variant="image"
dangerouslySetInnerHTML={{ __html: registry.logo }}
className="*:[svg]:fill-foreground grayscale *:[svg]:size-8"
/>
<ItemContent>
<ItemTitle>
<a
href={registry.homepage}
target="_blank"
rel="noopener noreferrer"
>
{registry.name}
</a>
</ItemTitle>
{registry.description && (
<ItemDescription className="text-pretty">
{registry.description}
</ItemDescription>
)}
</ItemContent>
<ItemActions className="relative z-10 hidden self-start sm:flex">
<Button size="sm" variant="outline" asChild>
<a
href={registry.homepage}
target="_blank"
rel="noopener noreferrer"
>
View <IconArrowUpRight />
</Button>
<DirectoryAddButton registry={registry} />
</ItemFooter>
</Item>
{index < globalRegistries.length - 1 && (
<ItemSeparator className="my-1" />
)}
</React.Fragment>
))}
</ItemGroup>
</div>
</a>
</Button>
<DirectoryAddButton registry={registry} />
</ItemActions>
<ItemFooter className="justify-start pl-16 sm:hidden">
<Button size="sm" variant="outline">
View <IconArrowUpRight />
</Button>
<DirectoryAddButton registry={registry} />
</ItemFooter>
</Item>
{index < registries.length - 1 && <ItemSeparator className="my-1" />}
</React.Fragment>
))}
</ItemGroup>
)
}

View File

@@ -11,7 +11,7 @@ export function GitHubLink() {
<Button asChild size="sm" variant="ghost" className="h-8 shadow-none">
<Link href={siteConfig.links.github} target="_blank" rel="noreferrer">
<Icons.gitHub />
<React.Suspense fallback={<Skeleton className="h-4 w-[42px]" />}>
<React.Suspense fallback={<Skeleton className="h-4 w-8" />}>
<StarsCount />
</React.Suspense>
</Link>
@@ -21,20 +21,15 @@ export function GitHubLink() {
export async function StarsCount() {
const data = await fetch("https://api.github.com/repos/shadcn-ui/ui", {
next: { revalidate: 86400 },
next: { revalidate: 86400 }, // Cache for 1 day (86400 seconds)
})
const json = await data.json()
const formattedCount =
json.stargazers_count >= 1000
? json.stargazers_count % 1000 === 0
? `${Math.floor(json.stargazers_count / 1000)}k`
: `${(json.stargazers_count / 1000).toFixed(1)}k`
: json.stargazers_count.toLocaleString()
return (
<span className="text-muted-foreground w-fit text-xs tabular-nums">
{formattedCount.replace(".0k", "k")}
<span className="text-muted-foreground w-8 text-xs tabular-nums">
{json.stargazers_count >= 1000
? `${(json.stargazers_count / 1000).toFixed(1)}k`
: json.stargazers_count.toLocaleString()}
</span>
)
}

View File

@@ -1,49 +0,0 @@
import * as React from "react"
import { Search, X } from "lucide-react"
import { useSearchRegistry } from "@/hooks/use-search-registry"
import { Field } from "@/registry/new-york-v4/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/new-york-v4/ui/input-group"
export const SearchDirectory = () => {
const { query, setQuery } = useSearchRegistry()
const onQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setQuery(value)
}
return (
<Field>
<InputGroup>
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupInput
placeholder="Search"
value={query}
onChange={onQueryChange}
/>
<InputGroupAddon
align="inline-end"
data-disabled={!query.length}
className="data-[disabled=true]:hidden"
>
<InputGroupButton
aria-label="Clear"
title="Clear"
size="icon-xs"
onClick={() => setQuery(null)}
>
<X />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
)
}

View File

@@ -124,7 +124,9 @@ export function CopyCodeButton({
</DrawerTrigger>
<DrawerContent className="h-auto">
<DrawerHeader>
<DrawerTitle className="capitalize">{activeThemeName}</DrawerTitle>
<DrawerTitle className="capitalize">
{activeThemeName === "neutral" ? "Default" : activeThemeName}
</DrawerTitle>
<DrawerDescription>
Copy and paste the following code into your CSS file.
</DrawerDescription>
@@ -147,7 +149,9 @@ export function CopyCodeButton({
</DialogTrigger>
<DialogContent className="rounded-xl border-none bg-clip-padding shadow-2xl ring-4 ring-neutral-200/80 outline-none md:max-w-2xl dark:bg-neutral-800 dark:ring-neutral-900">
<DialogHeader>
<DialogTitle className="capitalize">{activeThemeName}</DialogTitle>
<DialogTitle className="capitalize">
{activeThemeName === "neutral" ? "Default" : activeThemeName}
</DialogTitle>
<DialogDescription>
Copy and paste the following code into your CSS file.
</DialogDescription>
@@ -161,7 +165,7 @@ export function CopyCodeButton({
function CustomizerCode({ themeName }: { themeName: string }) {
const [hasCopied, setHasCopied] = React.useState(false)
const [tailwindVersion, setTailwindVersion] = React.useState("v4-oklch")
const [tailwindVersion, setTailwindVersion] = React.useState("v4")
const activeTheme = React.useMemo(
() => baseColors.find((theme) => theme.name === themeName),
[themeName]
@@ -187,11 +191,10 @@ function CustomizerCode({ themeName }: { themeName: string }) {
className="min-w-0 px-4 pb-4 md:p-0"
>
<TabsList>
<TabsTrigger value="v4-oklch">OKLCH</TabsTrigger>
<TabsTrigger value="v4-hsl">HSL</TabsTrigger>
<TabsTrigger value="v4">Tailwind v4</TabsTrigger>
<TabsTrigger value="v3">Tailwind v3</TabsTrigger>
</TabsList>
<TabsContent value="v4-oklch">
<TabsContent value="v4">
<figure
data-rehype-pretty-code-figure
className="!mx-0 mt-0 rounded-lg"
@@ -213,12 +216,14 @@ function CustomizerCode({ themeName }: { themeName: string }) {
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
onClick={() => {
copyToClipboardWithMeta(
getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
tailwindVersion === "v3"
? getThemeCode(activeTheme, 0.65)
: getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
{
name: "copy_theme_code",
properties: {
theme: themeName,
radius: 0.65,
radius: 0.5,
},
}
)
@@ -241,8 +246,7 @@ function CustomizerCode({ themeName }: { themeName: string }) {
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}: <ColorIndicator color={value} />{" "}
{value};
&nbsp;&nbsp;&nbsp;--{key}: {value};
</span>
))}
<span data-line className="line text-code-foreground">
@@ -260,8 +264,7 @@ function CustomizerCode({ themeName }: { themeName: string }) {
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}: <ColorIndicator color={value} />{" "}
{value};
&nbsp;&nbsp;&nbsp;--{key}: {value};
</span>
))}
<span data-line className="line text-code-foreground">
@@ -271,90 +274,6 @@ function CustomizerCode({ themeName }: { themeName: string }) {
</pre>
</figure>
</TabsContent>
<TabsContent value="v4-hsl">
<figure
data-rehype-pretty-code-figure
className="!mx-0 mt-0 rounded-lg"
>
<figcaption
className="text-code-foreground [&_svg]:text-code-foreground flex items-center gap-2 [&_svg]:size-4 [&_svg]:opacity-70"
data-rehype-pretty-code-title=""
data-language="css"
data-theme="github-dark github-light-default"
>
<Icons.css className="fill-foreground" />
app/globals.css
</figcaption>
<pre className="no-scrollbar max-h-[300px] min-w-0 overflow-x-auto px-4 py-3.5 outline-none has-[[data-highlighted-line]]:px-0 has-[[data-line-numbers]]:px-0 has-[[data-slot=tabs]]:p-0 md:max-h-[450px]">
<Button
data-slot="copy-button"
size="icon"
variant="ghost"
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
onClick={() => {
copyToClipboardWithMeta(
getThemeCodeHSLV4(activeTheme, 0.65),
{
name: "copy_theme_code",
properties: {
theme: themeName,
radius: 0.65,
},
}
)
setHasCopied(true)
}}
>
<span className="sr-only">Copy</span>
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
<code data-line-numbers data-language="css">
<span data-line className="line text-code-foreground">
&nbsp;:root &#123;
</span>
<span data-line className="line text-code-foreground">
&nbsp;&nbsp;&nbsp;--radius: 0.65rem;
</span>
{Object.entries(activeTheme?.cssVars.light || {}).map(
([key, value]) => (
<span
data-line
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}:{" "}
<ColorIndicator color={`hsl(${value})`} /> hsl({value});
</span>
)
)}
<span data-line className="line text-code-foreground">
&nbsp;&#125;
</span>
<span data-line className="line text-code-foreground">
&nbsp;
</span>
<span data-line className="line text-code-foreground">
&nbsp;.dark &#123;
</span>
{Object.entries(activeTheme?.cssVars.dark || {}).map(
([key, value]) => (
<span
data-line
className="line text-code-foreground"
key={key}
>
&nbsp;&nbsp;&nbsp;--{key}:{" "}
<ColorIndicator color={`hsl(${value})`} /> hsl({value});
</span>
)
)}
<span data-line className="line text-code-foreground">
&nbsp;&#125;
</span>
</code>
</pre>
</figure>
</TabsContent>
<TabsContent value="v3">
<figure
data-rehype-pretty-code-figure
@@ -376,13 +295,18 @@ function CustomizerCode({ themeName }: { themeName: string }) {
variant="ghost"
className="bg-code text-code-foreground absolute top-3 right-2 z-10 size-7 shadow-none hover:opacity-100 focus-visible:opacity-100"
onClick={() => {
copyToClipboardWithMeta(getThemeCode(activeTheme, 0.5), {
name: "copy_theme_code",
properties: {
theme: themeName,
radius: 0.5,
},
})
copyToClipboardWithMeta(
tailwindVersion === "v3"
? getThemeCode(activeTheme, 0.65)
: getThemeCodeOKLCH(activeThemeOKLCH, 0.65),
{
name: "copy_theme_code",
properties: {
theme: themeName,
radius: 0.5,
},
}
)
setHasCopied(true)
}}
>
@@ -398,16 +322,10 @@ function CustomizerCode({ themeName }: { themeName: string }) {
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.light["background"]})`}
/>{" "}
{activeTheme?.cssVars.light["background"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.light["foreground"]})`}
/>{" "}
{activeTheme?.cssVars.light["foreground"]};
</span>
{[
@@ -422,13 +340,6 @@ function CustomizerCode({ themeName }: { themeName: string }) {
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
})`}
/>{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
@@ -438,13 +349,6 @@ function CustomizerCode({ themeName }: { themeName: string }) {
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.light[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
]
})`}
/>{" "}
{
activeTheme?.cssVars.light[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.light
@@ -456,23 +360,14 @@ function CustomizerCode({ themeName }: { themeName: string }) {
))}
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.light["border"]})`}
/>{" "}
{activeTheme?.cssVars.light["border"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.light["input"]})`}
/>{" "}
{activeTheme?.cssVars.light["input"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.light["ring"]})`}
/>{" "}
{activeTheme?.cssVars.light["ring"]};
</span>
<span data-line className="line">
@@ -483,13 +378,6 @@ function CustomizerCode({ themeName }: { themeName: string }) {
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
]
})`}
/>{" "}
{
activeTheme?.cssVars.light[
prefix as keyof typeof activeTheme.cssVars.light
@@ -511,16 +399,10 @@ function CustomizerCode({ themeName }: { themeName: string }) {
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--background:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.dark["background"]})`}
/>{" "}
{activeTheme?.cssVars.dark["background"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--foreground:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.dark["foreground"]})`}
/>{" "}
{activeTheme?.cssVars.dark["foreground"]};
</span>
{[
@@ -535,13 +417,6 @@ function CustomizerCode({ themeName }: { themeName: string }) {
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
})`}
/>{" "}
{
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
@@ -551,13 +426,6 @@ function CustomizerCode({ themeName }: { themeName: string }) {
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}-foreground:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.dark[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
]
})`}
/>{" "}
{
activeTheme?.cssVars.dark[
`${prefix}-foreground` as keyof typeof activeTheme.cssVars.dark
@@ -569,23 +437,14 @@ function CustomizerCode({ themeName }: { themeName: string }) {
))}
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--border:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.dark["border"]})`}
/>{" "}
{activeTheme?.cssVars.dark["border"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--input:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.dark["input"]})`}
/>{" "}
{activeTheme?.cssVars.dark["input"]};
</span>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--ring:{" "}
<ColorIndicator
color={`hsl(${activeTheme?.cssVars.dark["ring"]})`}
/>{" "}
{activeTheme?.cssVars.dark["ring"]};
</span>
{["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"].map(
@@ -593,13 +452,6 @@ function CustomizerCode({ themeName }: { themeName: string }) {
<React.Fragment key={prefix}>
<span data-line className="line">
&nbsp;&nbsp;&nbsp;&nbsp;--{prefix}:{" "}
<ColorIndicator
color={`hsl(${
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
]
})`}
/>{" "}
{
activeTheme?.cssVars.dark[
prefix as keyof typeof activeTheme.cssVars.dark
@@ -625,15 +477,6 @@ function CustomizerCode({ themeName }: { themeName: string }) {
)
}
function ColorIndicator({ color }: { color: string }) {
return (
<span
className="border-border/50 inline-block size-3 border"
style={{ backgroundColor: color }}
/>
)
}
function getThemeCodeOKLCH(theme: BaseColorOKLCH | undefined, radius: number) {
if (!theme) {
return ""
@@ -666,27 +509,6 @@ function getThemeCode(theme: BaseColor | undefined, radius: number) {
})
}
function getThemeCodeHSLV4(theme: BaseColor | undefined, radius: number) {
if (!theme) {
return ""
}
const rootSection =
":root {\n --radius: " +
radius +
"rem;\n" +
Object.entries(theme.cssVars.light)
.map((entry) => " --" + entry[0] + ": hsl(" + entry[1] + ");")
.join("\n") +
"\n}\n\n.dark {\n" +
Object.entries(theme.cssVars.dark)
.map((entry) => " --" + entry[0] + ": hsl(" + entry[1] + ");")
.join("\n") +
"\n}\n"
return rootSection
}
const BASE_STYLES_WITH_VARIABLES = `
@layer base {
:root {

View File

@@ -17,14 +17,12 @@ import { CopyCodeButton } from "./theme-customizer"
export function ThemeSelector({ className }: React.ComponentProps<"div">) {
const { activeTheme, setActiveTheme } = useThemeConfig()
const value = activeTheme === "default" ? "neutral" : activeTheme
return (
<div className={cn("flex items-center gap-2", className)}>
<Label htmlFor="theme-selector" className="sr-only">
Theme
</Label>
<Select value={value} onValueChange={setActiveTheme}>
<Select value={activeTheme} onValueChange={setActiveTheme}>
<SelectTrigger
id="theme-selector"
size="sm"
@@ -40,7 +38,7 @@ export function ThemeSelector({ className }: React.ComponentProps<"div">) {
value={theme.name}
className="data-[state=checked]:opacity-50"
>
{theme.label}
{theme.label === "Neutral" ? "Default" : theme.label}
</SelectItem>
))}
</SelectContent>

View File

@@ -1014,7 +1014,7 @@ It has support for infinite looping, autoplay, vertical orientation, and more.
### Drawer
Oh the drawer component 😍. Built on top of [Vaul](https://github.com/emilkowalski/vaul) by [emilkowalski](https://twitter.com/emilkowalski).
Oh the drawer component 😍. Built on top of [Vaul](https://github.com/emilkowalski/vaul) by [emilkowalski\_](https://twitter.com/emilkowalski_).
Try opening the following drawer on mobile. It looks amazing!
@@ -1036,7 +1036,7 @@ Build resizable panel groups and layouts with this `<Resizable />` component.
### Sonner
Another one by [emilkowalski](https://twitter.com/emilkowalski). The last toast component you'll ever need. Sonner is now availabe in shadcn/ui.
Another one by [emilkowalski\_](https://twitter.com/emilkowalski_). The last toast component you'll ever need. Sonner is now availabe in shadcn/ui.
<ComponentPreview name="sonner-demo" />

View File

@@ -8,13 +8,12 @@ description: Every component recreated in Figma. With customizable props, typogr
questions or feedback, please reach out to the Figma file maintainers.
</Callout>
## Free
- [Obra shadcn/ui](https://www.figma.com/community/file/1514746685758799870/obra-shadcn-ui) by [Obra Studio](https://obra.studio/) - Carefully crafted kit designed in the philosophy of shadcn, tracks v4, MIT licensed
- [shadcn/ui design system](https://www.figma.com/community/file/1203061493325953101) by [Pietro Schirano](https://twitter.com/skirano) - A design companion for shadcn/ui. Each component was painstakingly crafted to perfectly match the code implementation.
## Paid
- [shadcn/ui kit](https://shadcndesign.com) by [ Matt Wierzbicki](https://x.com/matsugfx) - A premium, always up-to-date UI kit for Figma - shadcn/ui compatible and optimized for smooth design-to-dev handoff.
- [Shadcraft UI Kit](https://shadcraft.com) - The most advanced shadcn-compatible kit with instant theming via [tweakcn](https://tweakcn.com), a pro library of components and templates, and complete coverage of shadcn components and blocks.
- [shadcn/studio UI Kit](https://shadcnstudio.com/figma) - Accelerate design & development with a shadcn/ui compatible Figma kit with updated components, 550+ blocks, 10+ templates, 20+ themes, and an AI tool that converts designs into shadcn/ui code.
## Free
- [shadcn/ui design system](https://www.figma.com/community/file/1203061493325953101) by [Pietro Schirano](https://twitter.com/skirano) - A design companion for shadcn/ui. Each component was painstakingly crafted to perfectly match the code implementation.
- [Obra shadcn/ui](https://www.figma.com/community/file/1514746685758799870/obra-shadcn-ui) by [Obra Studio](https://obra.studio/) - Carefully crafted kit designed in the philosophy of shadcn, tracks v4, MIT licensed

View File

@@ -182,7 +182,7 @@ To configure MCP in VS Code with GitHub Copilot, add the shadcn server to your p
```json title=".vscode/mcp.json" showLineNumbers
{
"servers": {
"mcpServers": {
"shadcn": {
"command": "npx",
"args": ["shadcn@latest", "mcp"]

View File

@@ -24,7 +24,7 @@ To create a new monorepo project, run the `init` command. You will be prompted
to select the type of project you are creating.
```bash
npx shadcn@latest init
npx shadcn@canary init
```
Select the `Next.js (Monorepo)` option.
@@ -51,15 +51,15 @@ cd apps/web
```
```bash
npx shadcn@latest add [COMPONENT]
npx shadcn@canary add [COMPONENT]
```
The CLI will figure out what type of component you are adding and install the
correct files to the correct path.
For example, if you run `npx shadcn@latest add button`, the CLI will install the button component under `packages/ui` and update the import path for components in `apps/web`.
For example, if you run `npx shadcn@canary add button`, the CLI will install the button component under `packages/ui` and update the import path for components in `apps/web`.
If you run `npx shadcn@latest add login-01`, the CLI will install the `button`, `label`, `input` and `card` components under `packages/ui` and the `login-form` component under `apps/web/components`.
If you run `npx shadcn@canary add login-01`, the CLI will install the `button`, `label`, `input` and `card` components under `packages/ui` and the `login-form` component under `apps/web/components`.
### Importing components

View File

@@ -31,7 +31,7 @@ We designed the `chart` component with composition in mind. **You build your cha
```tsx showLineNumbers /ChartContainer/ /ChartTooltipContent/
import { Bar, BarChart } from "recharts"
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
import { ChartContainer, ChartTooltipContent } from "@/components/ui/charts"
export function MyChart() {
return (
@@ -193,7 +193,7 @@ You can now build your chart using Recharts components.
<Callout className="mt-4 bg-amber-50 border-amber-200 dark:bg-amber-950/50 dark:border-amber-950">
**Important:** Remember to set a `min-h-[VALUE]` on the `ChartContainer` component. This is required for the chart to be responsive.
**Important:** Remember to set a `min-h-[VALUE]` on the `ChartContainer` component. This is required for the chart be responsive.
</Callout>
@@ -370,7 +370,7 @@ The chart config is where you define the labels, icons and colors for a chart.
It is intentionally decoupled from chart data.
This allows you to share config and color tokens between charts. It can also work independently for cases where your data or color tokens live remotely or in a different format.
This allows you to share config and color tokens between charts. It can also works independently for cases where your data or color tokens live remotely or in a different format.
```tsx showLineNumbers /ChartConfig/
import { Monitor } from "lucide-react"
@@ -394,7 +394,7 @@ const chartConfig = {
## Theming
Charts have built-in support for theming. You can use css variables (recommended) or color values in any color format, such as hex, hsl or oklch.
Charts has built-in support for theming. You can use css variables (recommended) or color values in any color format, such as hex, hsl or oklch.
### CSS Variables

View File

@@ -10,7 +10,7 @@ links:
## About
Drawer is built on top of [Vaul](https://github.com/emilkowalski/vaul) by [emilkowalski](https://twitter.com/emilkowalski).
Drawer is built on top of [Vaul](https://github.com/emilkowalski/vaul) by [emilkowalski\_](https://twitter.com/emilkowalski_).
## Installation

View File

@@ -1,6 +1,6 @@
---
title: Empty
description: Use the Empty component to display an empty state.
description: Use the Empty component to display a empty state.
component: true
---
@@ -70,7 +70,7 @@ import {
### Outline
Use the `border` utility class to create an outline empty state.
Use the `border` utility class to create a outline empty state.
<ComponentPreview
name="empty-outline"

View File

@@ -10,7 +10,7 @@ links:
## About
Sonner is built and maintained by [emilkowalski](https://twitter.com/emilkowalski).
Sonner is built and maintained by [emilkowalski\_](https://twitter.com/emilkowalski_).
## Installation

View File

@@ -18,7 +18,7 @@ npx create-tsrouter-app@latest my-app --template file-router --tailwind --add-on
You can now start adding components to your project.
```bash
npx shadcn@latest add button
npx shadcn@canary add button
```
The command above will add the `Button` component to your project. You can then import it like this:

View File

@@ -7,18 +7,109 @@ description: Install and configure shadcn/ui for TanStack Start.
### Create project
Run the following command to create a new TanStack Start project with shadcn/ui:
Start by creating a new TanStack Start project by following the [Build a Project from Scratch](https://tanstack.com/start/latest/docs/framework/react/build-from-scratch) guide on the TanStack Start website.
**Do not add Tailwind yet. We'll install Tailwind v4 in the next step.**
### Add Tailwind
Install `tailwindcss` and its dependencies.
```bash
npm create @tanstack/start@latest --tailwind --add-ons shadcn
npm install tailwindcss @tailwindcss/postcss postcss
```
### Add Components
### Create postcss.config.ts
Create a `postcss.config.ts` file at the root of your project.
```ts title="postcss.config.ts" showLineNumbers
export default {
plugins: {
"@tailwindcss/postcss": {},
},
}
```
### Create `app/styles/app.css`
Create an `app.css` file in the `app/styles` directory and import `tailwindcss`
```css title="app/styles/app.css"
@import "tailwindcss" source("../");
```
### Import `app.css`
```tsx title="app/routes/__root.tsx" showLineNumbers {5,21-26} showLineNumbers
import type { ReactNode } from "react"
import { createRootRoute, Outlet } from "@tanstack/react-router"
import { Meta, Scripts } from "@tanstack/start"
import appCss from "@/styles/app.css?url"
export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
{
title: "TanStack Start Starter",
},
],
links: [
{
rel: "stylesheet",
href: appCss,
},
],
}),
component: RootComponent,
})
```
### Edit tsconfig.json file
Add the following code to the `tsconfig.json` file to resolve paths.
```ts title="tsconfig.json" showLineNumbers {9-12}
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ES2022",
"skipLibCheck": true,
"strictNullChecks": true,
"baseUrl": ".",
"paths": {
"@/*": ["./app/*"]
}
}
}
```
### Run the CLI
Run the `shadcn` init command to setup your project:
```bash
npx shadcn@canary init
```
This will create a `components.json` file in the root of your project and configure CSS variables inside `app/styles/app.css`.
### That's it
You can now start adding components to your project.
```bash
npx shadcn@latest add button
npx shadcn@canary add button
```
The command above will add the `Button` component to your project. You can then import it like this:
@@ -26,7 +117,10 @@ The command above will add the `Button` component to your project. You can then
```tsx title="app/routes/index.tsx" showLineNumbers {1,6}
import { Button } from "@/components/ui/button"
function App() {
function Home() {
const router = useRouter()
const state = Route.useLoaderData()
return (
<div>
<Button>Click me</Button>
@@ -36,9 +130,3 @@ function App() {
```
</Steps>
If you want to add all `shadcn/ui` components, you can run the following command:
```bash
npx shadcn@latest add --all
```

View File

@@ -103,7 +103,7 @@ You can read more about the registry item schema and file types in the [registry
### Install the shadcn CLI
```bash
npm install shadcn@latest
npm install shadcn@canary
```
### Add a build script

View File

@@ -1,39 +0,0 @@
import { debounce, useQueryState } from "nuqs"
import globalRegistries from "@/registry/directory.json"
const normalizeQuery = (query: string) =>
query.toLowerCase().replaceAll(" ", "").replaceAll("@", "")
function finderFn<T extends (typeof globalRegistries)[0]>(
registry: T,
query: string
) {
const normalizedName = normalizeQuery(registry.name)
const normalizedDecription = normalizeQuery(registry.description)
const normalizedQuery = normalizeQuery(query)
return (
normalizedName.includes(normalizedQuery) ||
normalizedDecription.includes(normalizedQuery)
)
}
const searchDirectory = (query: string | null) => {
if (!query) return globalRegistries
return globalRegistries.filter((registry) => finderFn(registry, query))
}
export const useSearchRegistry = () => {
const [query, setQuery] = useQueryState("q", {
defaultValue: "",
limitUrlUpdates: debounce(250),
})
return {
query,
registries: searchDirectory(query),
setQuery,
}
}

View File

@@ -1,5 +1,5 @@
import { baseColors } from "@/registry/base-colors"
export const THEMES = baseColors
.filter((theme) => !["slate", "stone", "gray", "zinc"].includes(theme.name))
.sort((a, b) => a.name.localeCompare(b.name))
export const THEMES = baseColors.filter(
(theme) => !["slate", "stone", "gray", "zinc"].includes(theme.name)
)

View File

@@ -22,10 +22,10 @@
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@faker-js/faker": "^10.1.0",
"@faker-js/faker": "^8.2.0",
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accessible-icon": "^1.1.1",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.5",
"@radix-ui/react-aspect-ratio": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.2",
@@ -38,7 +38,7 @@
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-menubar": "^1.1.5",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-navigation-menu": "^1.2.4",
"@radix-ui/react-popover": "^1.1.5",
"@radix-ui/react-portal": "^1.1.3",
"@radix-ui/react-progress": "^1.1.1",
@@ -51,11 +51,11 @@
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-toast": "^1.2.5",
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.2.8",
"@radix-ui/react-tooltip": "^1.1.7",
"@tabler/icons-react": "^3.31.0",
"@tailwindcss/postcss": "^4.1.17",
"@tailwindcss/postcss": "^4.1.11",
"@tanstack/react-form": "^1.20.0",
"@tanstack/react-table": "^8.9.1",
"@vercel/analytics": "^1.4.1",
@@ -73,14 +73,13 @@
"fumadocs-mdx": "13.0.2",
"fumadocs-ui": "16.0.5",
"input-otp": "^1.4.2",
"jotai": "^2.15.0",
"jotai": "^2.1.0",
"little-date": "^1.0.0",
"lodash": "^4.17.21",
"lucide-react": "0.474.0",
"motion": "^12.12.1",
"next": "16.0.7",
"next": "16.0.0",
"next-themes": "0.4.6",
"nuqs": "^2.7.2",
"postcss": "^8.5.1",
"react": "19.2.0",
"react-day-picker": "^9.7.0",
@@ -91,7 +90,7 @@
"recharts": "2.15.1",
"rehype-pretty-code": "^0.14.1",
"rimraf": "^6.0.1",
"shadcn": "3.5.2",
"shadcn": "3.5.0",
"shiki": "^1.10.1",
"sonner": "^2.0.0",
"tailwind-merge": "^3.0.1",

View File

@@ -1,16 +1,11 @@
{
"@8bitcn": "https://8bitcn.com/r/{name}.json",
"@97cn": "https://97cn.itzik.co/r/{name}.json",
"@abstract": "https://build.abs.xyz/r/{name}/json",
"@abui": "https://abui.io/r/{name}.json",
"@aceternity": "https://ui.aceternity.com/registry/{name}.json",
"@aevr": "https://ui.aevr.space/r/{name}.json",
"@ai-blocks": "https://webllm.org/r/{name}.json",
"@ai-elements": "https://registry.ai-sdk.dev/{name}.json",
"@alexcarpenter": "https://ui.alexcarpenter.me/r/{name}.json",
"@algolia": "https://sitesearch.algolia.com/r/{name}.json",
"@alpine": "https://alpine-registry.vercel.app/r/{name}.json",
"@aliimam": "https://aliimam.in/r/{name}.json",
"@animate-ui": "https://animate-ui.com/r/{name}.json",
"@assistant-ui": "https://r.assistant-ui.com/{name}.json",
"@austin-ui": "https://austin-ui.netlify.app/r/{name}.json",
@@ -21,44 +16,32 @@
"@bucharitesh": "https://bucharitesh.in/r/{name}.json",
"@clerk": "https://clerk.com/r/{name}.json",
"@coss": "https://coss.com/ui/r/{name}.json",
"@commercn": "https://commercn.com/r/{name}.json",
"@chisom-ui": "https://chisom-ui.netlify.app/r/{name}.json",
"@creative-tim": "https://www.creative-tim.com/ui/r/{name}.json",
"@cult-ui": "https://cult-ui.com/r/{name}.json",
"@diceui": "https://diceui.com/r/{name}.json",
"@doras-ui": "https://ui.doras.to/r/{name}.json",
"@efferd": "https://efferd.com/r/{name}.json",
"@eldoraui": "https://eldoraui.site/r/{name}.json",
"@elements": "https://tryelements.dev/r/{name}.json",
"@elevenlabs-ui": "https://ui.elevenlabs.io/r/{name}.json",
"@fancy": "https://fancycomponents.dev/r/{name}.json",
"@formcn": "https://formcn.dev/r/{name}.json",
"@gaia": "https://ui.heygaia.io/r/{name}.json",
"@glass-ui": "https://glass-ui.crenspire.com/r/{name}.json",
"@heseui": "https://www.heseui.com/r/{name}.json",
"@hooks": "https://shadcn-hooks.vercel.app/r/{name}.json",
"@intentui": "https://intentui.com/r/{name}",
"@kibo-ui": "https://www.kibo-ui.com/r/{name}.json",
"@kanpeki": "https://kanpeki.vercel.app/r/{name}.json",
"@kokonutui": "https://kokonutui.com/r/{name}.json",
"@lens-blocks": "https://lensblocks.com/r/{name}.json",
"@limeplay": "https://limeplay.winoffrg.dev/r/{name}.json",
"@lucide-animated": "https://lucide-animated.com/r/{name}.json",
"@lytenyte": "https://www.1771technologies.com/r/{name}.json",
"@magicui": "https://magicui.design/r/{name}.json",
"@magicui-pro": "https://pro.magicui.design/registry/{name}",
"@motion-primitives": "https://motion-primitives.com/c/{name}.json",
"@mui-treasury": "https://mui-treasury.com/r/{name}.json",
"@nativeui": "https://nativeui.io/registry/{name}.json",
"@nexus-elements": "https://elements.nexus.availproject.org/r/{name}.json",
"@ncdai": "https://chanhdai.com/r/{name}.json",
"@nuqs": "https://nuqs.dev/r/{name}.json",
"@oui": "https://oui.mw10013.workers.dev/r/{name}.json",
"@paceui": "https://ui.paceui.com/r/{name}.json",
"@plate": "https://platejs.org/r/{name}.json",
"@prompt-kit": "https://prompt-kit.com/c/{name}.json",
"@prosekit": "https://prosekit.dev/r/{name}.json",
"@phucbm": "https://ui.phucbm.com/r/{name}.json",
"@react-bits": "https://reactbits.dev/r/{name}.json",
"@react-market": "https://www.react-market.com/get/{name}.json",
"@retroui": "https://retroui.dev/r/{name}.json",
@@ -67,13 +50,10 @@
"@roiui": "https://roiui.com/r/{name}.json",
"@solaceui": "https://www.solaceui.com/r/{name}.json",
"@scrollxui": "https://www.scrollxui.dev/registry/{name}.json",
"@systaliko-ui": "https://systaliko-ui.vercel.app/r/{name}.json",
"@shadcn-editor": "https://shadcn-editor.vercel.app/r/{name}.json",
"@shadcn-map": "http://shadcn-map.vercel.app/r/{name}.json",
"@shadcn-studio": "https://shadcnstudio.com/r/{name}.json",
"@shadcnblocks": "https://shadcnblocks.com/r/{name}.json",
"@shadcnui-blocks": "https://shadcnui-blocks.com/r/{name}.json",
"@shadcraft": "https://shadcraft-free.vercel.app/r/{name}.json",
"@simple-ai": "https://simple-ai.dev/r/{name}.json",
"@skiper-ui": "https://skiper-ui.com/registry/{name}.json",
"@skyr": "https://ui-play.skyroc.me/r/{name}.json",
@@ -82,27 +62,14 @@
"@supabase": "https://supabase.com/ui/r/{name}.json",
"@svgl": "https://svgl.app/r/{name}.json",
"@tailark": "https://tailark.com/r/{name}.json",
"@tailwind-admin": "https://tailwind-admin.com/r/{name}.json",
"@tailwind-builder": "https://tailwindbuilder.ai/r/{name}.json",
"@tweakcn": "https://tweakcn.com/r/themes/{name}.json",
"@wds": "https://wds-shadcn-registry.netlify.app/r/{name}.json",
"@wandry-ui": "https://ui.wandry.com.ua/r/{name}.json",
"@wigggle-ui": "https://wigggle-ui.vercel.app/r/{name}.json",
"@paykit-sdk": "https://www.usepaykit.dev/r/{name}.json",
"@pixelact-ui": "https://www.pixelactui.com/r/{name}.json",
"@zippystarter": "https://zippystarter.com/r/{name}.json",
"@shadcndesign": "https://shadcndesign-free.vercel.app/r/{name}.json",
"@ha-components": "https://hacomponents.keshuac.com/r/{name}.json",
"@shadix-ui": "https://shadix-ui.vercel.app/r/{name}.json",
"@utilcn": "https://utilcn.dev/r/{name}.json",
"@hextaui": "https://hextaui.com/r/{name}.json",
"@taki": "https://taki-ui.com/r/{name}.json",
"@square-ui": "https://square.lndev.me/registry/{name}.json",
"@moleculeui": "https://moleculeui.design/r/{name}.json",
"@uicapsule": "https://uicapsule.com/r/{name}.json",
"@uitripled": "https://ui.tripled.work/r/{name}.json",
"@ui-layouts": "https://ui-layouts.com/r/{name}.json",
"@tour": "https://onboarding-tour.vercel.app/r/{name}.json",
"@tb-blocks": "https://tailwindbuilder.ai/r/blocks/{name}.json",
"@ui-layouts": "https://ui-layouts.com/r/{name}.json"
"@utilcn": "https://utilcn.dev/r/{name}.json"
}

File diff suppressed because one or more lines are too long

View File

@@ -150,7 +150,7 @@
@apply bg-selection text-selection-foreground;
}
html {
@apply overscroll-y-none scroll-smooth;
@apply overscroll-none scroll-smooth;
}
body {
font-synthesis-weight: none;
@@ -158,7 +158,7 @@
}
[data-slot="layout"] {
@apply overscroll-none;
@apply 3xl:p-2 3xl:fixed:p-0 overscroll-none;
}
@supports (font: -apple-system-body) and (-webkit-appearance: none) {

View File

@@ -44,7 +44,7 @@
"@babel/core": "^7.22.1",
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.1",
"@commitlint/cli": "^20.1.0",
"@commitlint/cli": "^17.6.3",
"@commitlint/config-conventional": "^17.6.3",
"@ianvs/prettier-plugin-sort-imports": "^3.7.2",
"@manypkg/cli": "^0.20.0",

View File

@@ -1,23 +1,5 @@
# @shadcn/ui
## 3.5.2
### Patch Changes
- [#8997](https://github.com/shadcn-ui/ui/pull/8997) [`6699158a22e376a6abc91693843a64cdb0b496da`](https://github.com/shadcn-ui/ui/commit/6699158a22e376a6abc91693843a64cdb0b496da) Thanks [@shadcn](https://github.com/shadcn)! - fix handling of base styles for add command
- [#8993](https://github.com/shadcn-ui/ui/pull/8993) [`142cd8ef13a0ff691a94bbd73dba9d7a62428ffa`](https://github.com/shadcn-ui/ui/commit/142cd8ef13a0ff691a94bbd73dba9d7a62428ffa) Thanks [@pasqualevitiello](https://github.com/pasqualevitiello)! - Prevent duplicate keyframes when adding components
## 3.5.1
### Patch Changes
- [#8900](https://github.com/shadcn-ui/ui/pull/8900) [`d0fb73ac0e4e7f6d02768586c5232bbc6b33a3c3`](https://github.com/shadcn-ui/ui/commit/d0fb73ac0e4e7f6d02768586c5232bbc6b33a3c3) Thanks [@shadcn](https://github.com/shadcn)! - do not install base style when adding themes
- [#7557](https://github.com/shadcn-ui/ui/pull/7557) [`ad6a3c63678bb31dbfb94536ee1d4aa4f06a8b8d`](https://github.com/shadcn-ui/ui/commit/ad6a3c63678bb31dbfb94536ee1d4aa4f06a8b8d) Thanks [@remorses](https://github.com/remorses)! - Fix utils import transform when workspace alias does not start with @
- [#8901](https://github.com/shadcn-ui/ui/pull/8901) [`62218c1c0c79195bda49a36817a13392cae7b4f2`](https://github.com/shadcn-ui/ui/commit/62218c1c0c79195bda49a36817a13392cae7b4f2) Thanks [@shadcn](https://github.com/shadcn)! - update color value detection for cssVars
## 3.5.0
### Minor Changes

View File

@@ -1,6 +1,6 @@
{
"name": "shadcn",
"version": "3.5.2",
"version": "3.5.0",
"description": "Add components to your apps.",
"publishConfig": {
"access": "public"

View File

@@ -4,7 +4,6 @@ import { preFlightAdd } from "@/src/preflights/preflight-add"
import { getRegistryItems, getShadcnRegistryIndex } from "@/src/registry/api"
import { DEPRECATED_COMPONENTS } from "@/src/registry/constants"
import { clearRegistryContext } from "@/src/registry/context"
import { registryItemTypeSchema } from "@/src/registry/schema"
import { isUniversalRegistryItem } from "@/src/registry/utils"
import { addComponents } from "@/src/utils/add-components"
import { createProject } from "@/src/utils/create-project"
@@ -89,21 +88,14 @@ export const add = new Command()
hasNewRegistries = newRegistries.length > 0
}
let itemType: z.infer<typeof registryItemTypeSchema> | undefined
let shouldInstallBaseStyle = true
if (components.length > 0) {
const [registryItem] = await getRegistryItems([components[0]], {
config: initialConfig,
})
itemType = registryItem?.type
shouldInstallBaseStyle =
itemType !== "registry:theme" && itemType !== "registry:style"
const itemType = registryItem?.type
if (isUniversalRegistryItem(registryItem)) {
await addComponents(components, initialConfig, {
...options,
baseStyle: shouldInstallBaseStyle,
})
await addComponents(components, initialConfig, options)
return
}
@@ -176,12 +168,11 @@ export const add = new Command()
force: true,
defaults: false,
skipPreflight: false,
silent: options.silent && !hasNewRegistries,
silent: options.silent || !hasNewRegistries,
isNewProject: false,
srcDir: options.srcDir,
cssVariables: options.cssVariables,
baseStyle: shouldInstallBaseStyle,
baseColor: shouldInstallBaseStyle ? undefined : "neutral",
baseStyle: true,
components: options.components,
})
initHasRun = true
@@ -216,8 +207,7 @@ export const add = new Command()
isNewProject: true,
srcDir: options.srcDir,
cssVariables: options.cssVariables,
baseStyle: shouldInstallBaseStyle,
baseColor: shouldInstallBaseStyle ? undefined : "neutral",
baseStyle: true,
components: options.components,
})
initHasRun = true
@@ -244,10 +234,7 @@ export const add = new Command()
config = updatedConfig
if (!initHasRun) {
await addComponents(options.components, config, {
...options,
baseStyle: shouldInstallBaseStyle,
})
await addComponents(options.components, config, options)
}
// If we're adding a single component and it's from the v0 registry,

View File

@@ -26,7 +26,6 @@ import {
DEFAULT_TAILWIND_CONFIG,
DEFAULT_TAILWIND_CSS,
DEFAULT_UTILS,
createConfig,
getConfig,
resolveConfigPaths,
type Config,
@@ -162,13 +161,7 @@ export const init = new Command()
if (components.length > 0) {
// We don't know the full config at this point.
// So we'll use a shadow config to fetch the first item.
let shadowConfig = configWithDefaults(
createConfig({
resolvedPaths: {
cwd: options.cwd,
},
})
)
let shadowConfig = configWithDefaults({})
// Check if there's a components.json file.
// If so, we'll merge with our shadow config.
@@ -176,18 +169,7 @@ export const init = new Command()
if (fsExtra.existsSync(componentsJsonPath)) {
const existingConfig = await fsExtra.readJson(componentsJsonPath)
const config = rawConfigSchema.partial().parse(existingConfig)
const baseConfig = createConfig({
resolvedPaths: {
cwd: options.cwd,
},
})
shadowConfig = configWithDefaults({
...config,
resolvedPaths: {
...baseConfig.resolvedPaths,
cwd: options.cwd,
},
})
shadowConfig = configWithDefaults(config)
// Since components.json might not be valid at this point.
// Temporarily rename components.json to allow preflight to run.
@@ -201,7 +183,6 @@ export const init = new Command()
shadowConfig,
{
silent: true,
writeFile: false,
}
)
shadowConfig = updatedConfig
@@ -237,7 +218,7 @@ export const init = new Command()
)} Project initialization completed.\nYou may now add components.`
)
// We need when running with custom cwd.
// We need when runninng with custom cwd.
deleteFileBackup(path.resolve(options.cwd, "components.json"))
logger.break()
} catch (error) {

View File

@@ -7,12 +7,8 @@ export const transformImport: Transformer = async ({
config,
isRemote,
}) => {
const utilsAlias = config.aliases?.utils
const workspaceAlias =
typeof utilsAlias === "string" && utilsAlias.includes("/")
? utilsAlias.split("/")[0]
: "@"
const utilsImport = `${workspaceAlias}/lib/utils`
const workspaceAlias = config.aliases?.utils?.split("/")[0]?.slice(1)
const utilsImport = `@${workspaceAlias}/lib/utils`
if (![".tsx", ".ts", ".jsx", ".js"].includes(sourceFile.getExtension())) {
return sourceFile
@@ -35,9 +31,7 @@ export const transformImport: Transformer = async ({
?.getNamedImports()
.some((namedImport) => namedImport.getName() === "cn")
if (!isCnImport || !config.aliases.utils) {
continue
}
if (!isCnImport) continue
specifier.setLiteralValue(
utilsImport === updated

View File

@@ -898,6 +898,6 @@ export function isColorValue(value: string) {
value.startsWith("rgb") ||
value.startsWith("#") ||
value.startsWith("oklch") ||
value.includes("--color-")
value.startsWith("var(--color-")
)
}

View File

@@ -262,32 +262,13 @@ function updateCssPlugin(css: z.infer<typeof registryItemCssSchema>) {
)
}
// Check if a keyframe with the same name already exists
const existingKeyframesRule = themeInline.nodes?.find(
(node): node is AtRule =>
node.type === "atrule" &&
node.name === "keyframes" &&
node.params === params
)
const keyframesRule = postcss.atRule({
name: "keyframes",
params,
raws: { semicolon: true, between: " ", before: "\n " },
})
let keyframesRule: AtRule
if (existingKeyframesRule) {
// Replace existing keyframe
keyframesRule = postcss.atRule({
name: "keyframes",
params,
raws: { semicolon: true, between: " ", before: "\n " },
})
existingKeyframesRule.replaceWith(keyframesRule)
} else {
// Create new keyframe
keyframesRule = postcss.atRule({
name: "keyframes",
params,
raws: { semicolon: true, between: " ", before: "\n " },
})
themeInline.append(keyframesRule)
}
themeInline.append(keyframesRule)
if (typeof properties === "object") {
for (const [step, stepProps] of Object.entries(properties)) {

View File

@@ -716,7 +716,7 @@ export function toAliasedImport(
// if noExt is empty (i.e. file was exactly at the root), we import the root
let suffix = noExt === "" ? "" : `/${noExt}`
// Remove /src from suffix.
// Rremove /src from suffix.
// Alias will handle this.
suffix = suffix.replace("/src", "")

View File

@@ -70,7 +70,7 @@ import { Foo } from "bar"
import { Label} from "ui/label"
import { Box } from "~/src/components/box"
import { cn, foo, bar } from "~/lib"
import { cn, foo, bar } from "~/lib/utils"
import { bar } from "~/lib/utils/bar"
"
`;
@@ -82,7 +82,7 @@ import { Foo } from "bar"
import { Label} from "ui/label"
import { Box } from "~/src/components/box"
import { cn } from "~/src/utils"
import { cn } from "~/lib/utils"
import { bar } from "~/lib/utils/bar"
"
`;
@@ -94,7 +94,7 @@ import { Foo } from "bar"
import { Label} from "ui/label"
import { Box } from "~/src/components/box"
import { cn } from "~/src/utils"
import { cn } from "~/lib/utils"
import { bar } from "~/lib/utils/bar"
"
`;
@@ -106,7 +106,7 @@ import { Foo } from "bar"
import { Label} from "ui/label"
import { Box } from "~/src/components/box"
import { cn } from "~/src/utils"
import { cn } from "~/lib/utils"
import { bar } from "~/lib/utils/bar"
"
`;

View File

@@ -2,38 +2,6 @@ import { expect, test } from "vitest"
import { transform } from "../../src/utils/transformers"
test('transform nested workspace folder for utils, website/src/utils', async () => {
expect(
await transform({
filename: "test.ts",
raw: `import { Button } from "website/src/components/ui/button"
import { Box } from "website/src/components/box"
import { cn } from "website/src/utils"
`,
config: {
tsx: true,
tailwind: {
baseColor: "neutral",
cssVariables: true,
},
aliases: {
components: "website/src/components",
lib: "website/src/lib",
utils: "website/src/utils",
},
},
})
).toMatchInlineSnapshot(`
"import { Button } from "website/src/components/ui/button"
import { Box } from "website/src/components/box"
import { cn } from "website/src/utils"
"
`)
})
test("transform import", async () => {
expect(
await transform({

View File

@@ -1417,8 +1417,6 @@ describe("isColorValue", () => {
["oklch(0.5 0.2 180)", true],
["var(--color-background)", true],
["var(--color-blue-500)", true],
["--alpha(var(--color-black) / 10%)", true],
["--alpha(var(--color-black) / 4%)", true],
["var(--radius)", false],
["var(--spacing)", false],
["0.5rem", false],

View File

@@ -852,36 +852,4 @@ describe("transformCss", () => {
}"
`)
})
test("should replace existing keyframes instead of duplicating", async () => {
const input = `@import "tailwindcss";
@theme inline {
@keyframes skeleton {
to {
background-position: "-100% 0";
}
}
}`
const result = await transformCss(input, {
"@keyframes skeleton": {
"to": {
"background-position": "-200% 0",
},
},
})
expect(result).toMatchInlineSnapshot(`
"@import "tailwindcss";
@theme inline {
@keyframes skeleton {
to {
background-position: -200% 0;
}
}
}"
`)
})
})

1925
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -14,19 +14,17 @@
"dependencies": {
"@workspace/ui": "workspace:*",
"lucide-react": "^0.475.0",
"next": "^16.0.7",
"next": "^15.4.5",
"next-themes": "^0.4.6",
"react": "^19.2.1",
"react-dom": "^19.2.1"
"react": "^19.1.1",
"react-dom": "^19.1.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.17",
"@types/node": "^20.19.25",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/node": "^20.19.9",
"@types/react": "^19.1.9",
"@types/react-dom": "^19.1.7",
"@workspace/eslint-config": "workspace:^",
"@workspace/typescript-config": "workspace:*",
"eslint": "^9.39.1",
"typescript": "^5.9.3"
"typescript": "^5.9.2"
}
}

View File

@@ -11,8 +11,8 @@
"devDependencies": {
"@workspace/eslint-config": "workspace:*",
"@workspace/typescript-config": "workspace:*",
"prettier": "^3.7.4",
"turbo": "^2.6.3",
"prettier": "^3.6.2",
"turbo": "^2.5.5",
"typescript": "5.7.3"
},
"packageManager": "pnpm@10.4.1",

View File

@@ -9,7 +9,6 @@
"./react-internal": "./react-internal.js"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@next/eslint-plugin-next": "^15.4.5",
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/parser": "^8.39.0",

View File

@@ -26,7 +26,6 @@
"@types/react-dom": "^19.1.7",
"@workspace/eslint-config": "workspace:*",
"@workspace/typescript-config": "workspace:*",
"eslint": "^9.32.0",
"tailwindcss": "^4.1.11",
"typescript": "^5.9.2"
},

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
{
"projectName": "start-app",
"mode": "file-router",
"typescript": true,
"tailwind": true,
"packageManager": "pnpm",
"addOnOptions": {},
"git": true,
"version": 1,
"framework": "react-cra",
"chosenAddOns": [
"eslint",
"nitro",
"start"
]
}

View File

@@ -1,13 +0,0 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
count.txt
.env
.nitro
.tanstack
.wrangler
.output
.vinxi
todos.json

View File

@@ -1,3 +0,0 @@
package-lock.json
pnpm-lock.yaml
yarn.lock

View File

@@ -1,3 +0,0 @@
# TanStack Start + shadcn/ui
This is a template for a new TanStack Start project with React, TypeScript, and shadcn/ui.

View File

@@ -1,5 +0,0 @@
// @ts-check
import { tanstackConfig } from '@tanstack/eslint-config'
export default [...tanstackConfig]

View File

@@ -1,44 +0,0 @@
{
"name": "start-app",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev --port 3000",
"build": "vite build",
"preview": "vite preview",
"test": "vitest run",
"lint": "eslint",
"format": "prettier",
"check": "prettier --write . && eslint --fix"
},
"dependencies": {
"@tailwindcss/vite": "^4.0.6",
"@tanstack/react-devtools": "^0.7.0",
"@tanstack/react-router": "^1.132.0",
"@tanstack/react-router-devtools": "^1.132.0",
"@tanstack/react-router-ssr-query": "^1.131.7",
"@tanstack/react-start": "^1.132.0",
"@tanstack/router-plugin": "^1.132.0",
"nitro": "latest",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"tailwindcss": "^4.0.6",
"vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {
"@tanstack/devtools-vite": "^0.3.11",
"@tanstack/eslint-config": "^0.3.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0",
"@types/node": "^22.10.2",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^5.0.4",
"jsdom": "^27.0.0",
"prettier": "^3.5.3",
"typescript": "^5.7.2",
"vite": "^7.1.7",
"vitest": "^3.0.5",
"web-vitals": "^5.1.0"
}
}

View File

@@ -1,10 +0,0 @@
// @ts-check
/** @type {import('prettier').Config} */
const config = {
semi: false,
singleQuote: true,
trailingComma: "all",
};
export default config;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -1,25 +0,0 @@
{
"short_name": "TanStack App",
"name": "Create TanStack App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -1,68 +0,0 @@
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index'
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/'
fileRoutesByTo: FileRoutesByTo
to: '/'
id: '__root__' | '/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
}
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}

View File

@@ -1,15 +0,0 @@
import { createRouter } from '@tanstack/react-router'
// Import the generated route tree
import { routeTree } from './routeTree.gen'
// Create a new router instance
export const getRouter = () => {
const router = createRouter({
routeTree,
scrollRestoration: true,
defaultPreloadStaleTime: 0,
})
return router
}

View File

@@ -1,55 +0,0 @@
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
import { TanStackDevtools } from '@tanstack/react-devtools'
import appCss from '../styles.css?url'
export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: 'utf-8',
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1',
},
{
title: 'TanStack Start Starter',
},
],
links: [
{
rel: 'stylesheet',
href: appCss,
},
],
}),
shellComponent: RootDocument,
})
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body>
{children}
<TanStackDevtools
config={{
position: 'bottom-right',
}}
plugins={[
{
name: 'Tanstack Router',
render: <TanStackRouterDevtoolsPanel />,
},
]}
/>
<Scripts />
</body>
</html>
)
}

View File

@@ -1,11 +0,0 @@
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/")({ component: App });
function App() {
return (
<div className="min-h-screen flex flex-col items-center justify-center">
<div className="font-medium">Hello World</div>
</div>
);
}

View File

@@ -1 +0,0 @@
@import "tailwindcss";

View File

@@ -1,29 +0,0 @@
{
"include": ["**/*.ts", "**/*.tsx", "eslint.config.js", "prettier.config.js", "vite.config.js"],
"compilerOptions": {
"target": "ES2022",
"jsx": "react-jsx",
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"],
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": false,
"noEmit": true,
/* Linting */
"skipLibCheck": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@@ -1,23 +0,0 @@
import { defineConfig } from 'vite'
import { devtools } from '@tanstack/devtools-vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'
import viteTsConfigPaths from 'vite-tsconfig-paths'
import tailwindcss from '@tailwindcss/vite'
import { nitro } from 'nitro/vite'
const config = defineConfig({
plugins: [
devtools(),
nitro(),
// this is the plugin that enables path aliases
viteTsConfigPaths({
projects: ['./tsconfig.json'],
}),
tailwindcss(),
tanstackStart(),
viteReact(),
],
})
export default config

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,3 +0,0 @@
# React + TypeScript + Vite + shadcn/ui
This is a template for a new Vite project with React, TypeScript, and shadcn/ui.

View File

@@ -1,23 +0,0 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])

View File

@@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vite-app</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,32 +0,0 @@
{
"name": "vite-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.17",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"tailwindcss": "^4.1.17"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4"
}
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,9 +0,0 @@
export function App() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="font-medium">Hello World</div>
</div>
)
}
export default App

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1 +0,0 @@
@import "tailwindcss";

View File

@@ -1,11 +0,0 @@
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import "./index.css"
import App from "./App.tsx"
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>
)

View File

@@ -1,32 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"]
}

View File

@@ -1,13 +0,0 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@@ -1,26 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,14 +0,0 @@
import path from "path"
import tailwindcss from "@tailwindcss/vite"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
})