View code
@@ -20,13 +18,12 @@
import osimport openaiopenai.api_key = os.getenv("OPENAI_API_KEY" ) response = openai.Completion.create( model="davinci" , response = openai.Completion.create( model="davinci" , prompt="" , temperature=0.9 , max_tokens=5 ,
+ import type { Slider as SliderPrimitive } from "bits-ui";
+ import * as HoverCard from "$lib/registry/ui/hover-card/index.js";
+ import { Slider } from "$lib/registry/ui/slider/index.js";
+ import { Label } from "$lib/registry/ui/label/index.js";
+
+ let { value = $bindable(), ...restProps }: SliderPrimitive.RootProps = $props();
+
+
+
+
+
+ {#snippet child({ props })}
+
+
+ Maximum Length
+
+ {value}
+
+
+
+
+ {/snippet}
+
+
+ The maximum number of tokens to generate. Requests can use up to 2,048 or 4,000 tokens,
+ shared between prompt and completion. The exact limit varies by model.
+
+
+
diff --git a/docs/src/routes/(app)/examples/playground/(components)/model-item.svelte b/docs/src/routes/(app)/examples/playground/(components)/model-item.svelte
new file mode 100644
index 0000000000..edefef2932
--- /dev/null
+++ b/docs/src/routes/(app)/examples/playground/(components)/model-item.svelte
@@ -0,0 +1,53 @@
+
+
+
+ {#snippet child({ props })}
+
+ {model.name}
+ {#if isSelected}
+
+ {/if}
+
+ {/snippet}
+
diff --git a/docs/src/routes/(app)/examples/playground/(components)/model-selector.svelte b/docs/src/routes/(app)/examples/playground/(components)/model-selector.svelte
new file mode 100644
index 0000000000..4ff5856229
--- /dev/null
+++ b/docs/src/routes/(app)/examples/playground/(components)/model-selector.svelte
@@ -0,0 +1,142 @@
+
+
+
+
+
+ {#snippet child({ props })}
+
+ Model
+
+ {/snippet}
+
+
+ The model which will generate the completion. Some models are suitable for natural
+ language tasks, others specialize in code. Learn more.
+
+
+
+
+
+ {selectedValue}
+
+
+
+
+
+ {#if peekedModel && hoverCardIsOpen}
+
+
+ {peekedModel.name}
+
+
+ {peekedModel.description}
+
+ {#if peekedModel.strengths}
+
+
Strengths
+
+ {peekedModel.strengths}
+
+
+ {/if}
+
+ {/if}
+
+
+
+
+ No models found.
+ {#each types as type (type)}
+
+ {#each models.filter((model) => model.type === type) as model (model.id)}
+
+ {#snippet child({ props })}
+
+ {
+ value = model.id;
+ closeAndFocusTrigger(triggerId);
+ }}
+ onPeek={() => {
+ handlePeek(model);
+ }}
+ isSelected={value === model.id}
+ />
+
+ {/snippet}
+
+ {/each}
+
+ {/each}
+
+
+
+
+
+
diff --git a/docs/src/routes/(app)/examples/playground/(components)/preset-actions.svelte b/docs/src/routes/(app)/examples/playground/(components)/preset-actions.svelte
new file mode 100644
index 0000000000..00c7a5ff6a
--- /dev/null
+++ b/docs/src/routes/(app)/examples/playground/(components)/preset-actions.svelte
@@ -0,0 +1,78 @@
+
+
+
+
+ Actions
+
+
+
+ (open = true)}>
+ Content filter preferences
+
+
+ (showDeleteDialog = true)} class="text-red-600">
+ Delete preset
+
+
+
+
+
+
+ Content filter preferences
+
+ The content filter flags text that may violate our content policy. It's powered
+ by our moderation endpoint which is free to use to moderate your OpenAI API traffic.
+ Learn more.
+
+
+
+
Playground Warnings
+
+
+
+ Show a warning when content is flagged
+
+ A warning will be shown when sexual, hateful, violent or self-harm content
+ is detected.
+
+
+
+
+
+ (open = false)}>Close
+
+
+
+
+
+
+ Are you sure absolutely sure?
+
+ This action cannot be undone. This preset will no longer be accessible by you or
+ others you've shared it with.
+
+
+
+ Cancel
+ {
+ showDeleteDialog = false;
+ }}
+ >
+ Delete
+
+
+
+
diff --git a/docs/src/routes/(app)/examples/playground/(components)/preset-save.svelte b/docs/src/routes/(app)/examples/playground/(components)/preset-save.svelte
new file mode 100644
index 0000000000..f61c6d841f
--- /dev/null
+++ b/docs/src/routes/(app)/examples/playground/(components)/preset-save.svelte
@@ -0,0 +1,32 @@
+
+
+
+ Save
+
+
+ Save preset
+
+ This will save the current playground state as a preset which you can access later
+ or share with others.
+
+
+
+
+ Save
+
+
+
diff --git a/docs/src/routes/(app)/examples/playground/(components)/preset-selector.svelte b/docs/src/routes/(app)/examples/playground/(components)/preset-selector.svelte
new file mode 100644
index 0000000000..75f873d223
--- /dev/null
+++ b/docs/src/routes/(app)/examples/playground/(components)/preset-selector.svelte
@@ -0,0 +1,73 @@
+
+
+
+
+ {selectedValue}
+
+
+
+
+
+
+ No presets found.
+
+ {#each presets as preset (preset)}
+ {
+ value = preset.name;
+ closeAndFocusTrigger(triggerId);
+ }}
+ >
+ {preset.name}
+
+
+ {/each}
+
+
+
+
+
diff --git a/docs/src/routes/(app)/examples/playground/(components)/preset-share.svelte b/docs/src/routes/(app)/examples/playground/(components)/preset-share.svelte
new file mode 100644
index 0000000000..25d965de7b
--- /dev/null
+++ b/docs/src/routes/(app)/examples/playground/(components)/preset-share.svelte
@@ -0,0 +1,34 @@
+
+
+
+ Share
+
+
+
Share preset
+
+ Anyone who has this link and an OpenAI account will be able to view this.
+
+
+
+
+ Link
+
+
+
+ Copy
+
+
+
+
+
diff --git a/docs/src/routes/(app)/examples/playground/(components)/temperature-selector.svelte b/docs/src/routes/(app)/examples/playground/(components)/temperature-selector.svelte
new file mode 100644
index 0000000000..950a76aeba
--- /dev/null
+++ b/docs/src/routes/(app)/examples/playground/(components)/temperature-selector.svelte
@@ -0,0 +1,40 @@
+
+
+
+
+
+ {#snippet child({ props })}
+
+
+ Temperature
+
+ {value}
+
+
+
+
+ {/snippet}
+
+
+ Controls randomness: lowering results in less random completions. As the temperature
+ approaches zero, the model will become deterministic and repetitive.
+
+
+
diff --git a/docs/src/routes/(app)/examples/playground/(components)/top-p-selector.svelte b/docs/src/routes/(app)/examples/playground/(components)/top-p-selector.svelte
new file mode 100644
index 0000000000..26bf8988a4
--- /dev/null
+++ b/docs/src/routes/(app)/examples/playground/(components)/top-p-selector.svelte
@@ -0,0 +1,40 @@
+
+
+
+
+
+ {#snippet child({ props })}
+
+
+ Top P
+
+ {value}
+
+
+
+
+ {/snippet}
+
+
+ Control diversity via nucleus sampling: 0.5 means half of all likelihood-weighted
+ options are considered.
+
+
+
diff --git a/sites/docs/src/routes/(app)/examples/playground/(data)/models.ts b/docs/src/routes/(app)/examples/playground/(data)/models.ts
similarity index 100%
rename from sites/docs/src/routes/(app)/examples/playground/(data)/models.ts
rename to docs/src/routes/(app)/examples/playground/(data)/models.ts
diff --git a/sites/docs/src/routes/(app)/examples/playground/(data)/presets.ts b/docs/src/routes/(app)/examples/playground/(data)/presets.ts
similarity index 100%
rename from sites/docs/src/routes/(app)/examples/playground/(data)/presets.ts
rename to docs/src/routes/(app)/examples/playground/(data)/presets.ts
diff --git a/docs/src/routes/(app)/examples/playground/+page.svelte b/docs/src/routes/(app)/examples/playground/+page.svelte
new file mode 100644
index 0000000000..911da694e7
--- /dev/null
+++ b/docs/src/routes/(app)/examples/playground/+page.svelte
@@ -0,0 +1,307 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#snippet child({ props })}
+
+ Mode
+
+ {/snippet}
+
+
+ Choose the interface that best suits your task. You can provide: a
+ simple prompt to complete, starting and ending text to insert a
+ completion within, or some text with instructions to edit it.
+
+
+
+
+ Complete
+
+
+
+
+
+
+
+
+
+
+
+ Insert
+
+
+
+
+
+
+
+
+ Edit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Submit
+
+ Show history
+
+
+
+
+
+
+
+
+
+ Submit
+
+ Show history
+
+
+
+
+
+
+
+
+
+
+ Input
+
+
+
+ Instructions
+
+
+
+
+
+
+ Submit
+
+ Show history
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/src/routes/(app)/examples/tasks/+page.svelte b/docs/src/routes/(app)/examples/tasks/+page.svelte
new file mode 100644
index 0000000000..1aaa0171c5
--- /dev/null
+++ b/docs/src/routes/(app)/examples/tasks/+page.svelte
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
Welcome back!
+
Here's a list of your tasks for this month.
+
+
+
+
+
+
+
diff --git a/docs/src/routes/(app)/examples/tasks/components/data-table-cell.svelte b/docs/src/routes/(app)/examples/tasks/components/data-table-cell.svelte
new file mode 100644
index 0000000000..b17f8d5240
--- /dev/null
+++ b/docs/src/routes/(app)/examples/tasks/components/data-table-cell.svelte
@@ -0,0 +1,9 @@
+
+
+
+ {@render children?.()}
+
diff --git a/docs/src/routes/(app)/examples/tasks/components/data-table-faceted-filter.svelte b/docs/src/routes/(app)/examples/tasks/components/data-table-faceted-filter.svelte
new file mode 100644
index 0000000000..0c6ac64d38
--- /dev/null
+++ b/docs/src/routes/(app)/examples/tasks/components/data-table-faceted-filter.svelte
@@ -0,0 +1,121 @@
+
+
+
+
+ {#snippet child({ props })}
+
+
+ {title}
+ {#if selectedValues.size > 0}
+
+
+ {selectedValues.size}
+
+
+ {#if selectedValues.size > 2}
+
+ {selectedValues.size} selected
+
+ {:else}
+ {#each options.filter( (opt) => selectedValues.has(opt.value) ) as option (option)}
+
+ {option.label}
+
+ {/each}
+ {/if}
+
+ {/if}
+
+ {/snippet}
+
+
+
+
+
+ No results found.
+
+ {#each options as option (option)}
+ {@const isSelected = selectedValues.has(option.value)}
+ {
+ if (isSelected) {
+ selectedValues.delete(option.value);
+ } else {
+ selectedValues.add(option.value);
+ }
+ const filterValues = Array.from(selectedValues);
+ column?.setFilterValue(
+ filterValues.length ? filterValues : undefined
+ );
+ }}
+ >
+
+
+
+ {#if option.icon}
+ {@const Icon = option.icon}
+
+ {/if}
+
+ {option.label}
+ {#if facets?.get(option.value)}
+
+ {facets.get(option.value)}
+
+ {/if}
+
+ {/each}
+
+ {#if selectedValues.size > 0}
+
+
+ column?.setFilterValue(undefined)}
+ class="justify-center text-center"
+ >
+ Clear filters
+
+
+ {/if}
+
+
+
+
diff --git a/docs/src/routes/(app)/examples/tasks/components/data-table-toolbar.svelte b/docs/src/routes/(app)/examples/tasks/components/data-table-toolbar.svelte
new file mode 100644
index 0000000000..8dc5aa87e7
--- /dev/null
+++ b/docs/src/routes/(app)/examples/tasks/components/data-table-toolbar.svelte
@@ -0,0 +1,49 @@
+
+
+
+
+ {
+ table.getColumn("title")?.setFilterValue(e.currentTarget.value);
+ }}
+ onchange={(e) => {
+ table.getColumn("title")?.setFilterValue(e.currentTarget.value);
+ }}
+ class="h-8 w-[150px] lg:w-[250px]"
+ />
+
+ {#if statusCol}
+
+ {/if}
+ {#if priorityCol}
+
+ {/if}
+
+ {#if isFiltered}
+ table.resetColumnFilters()}
+ class="h-8 px-2 lg:px-3"
+ >
+ Reset
+
+
+ {/if}
+
+
+
diff --git a/docs/src/routes/(app)/examples/tasks/components/data-table-view-options.svelte b/docs/src/routes/(app)/examples/tasks/components/data-table-view-options.svelte
new file mode 100644
index 0000000000..d56d7cdfb9
--- /dev/null
+++ b/docs/src/routes/(app)/examples/tasks/components/data-table-view-options.svelte
@@ -0,0 +1,37 @@
+
+
+
+
+
+ View
+
+
+
+ Toggle columns
+
+ {#each table
+ .getAllColumns()
+ .filter((col) => typeof col.accessorFn !== "undefined" && col.getCanHide()) as column (column)}
+ column.getIsVisible(), (v) => column.toggleVisibility(!!v)}
+ class="capitalize"
+ >
+ {column.id}
+
+ {/each}
+
+
+
diff --git a/docs/src/routes/(app)/examples/tasks/components/data-table.svelte b/docs/src/routes/(app)/examples/tasks/components/data-table.svelte
new file mode 100644
index 0000000000..1c802b9735
--- /dev/null
+++ b/docs/src/routes/(app)/examples/tasks/components/data-table.svelte
@@ -0,0 +1,454 @@
+
+
+{#snippet StatusCell({ value }: { value: string })}
+ {@const status = statuses.find((status) => status.value === value)}
+ {#if status}
+
+
+ {status.label}
+
+ {/if}
+{/snippet}
+
+{#snippet TitleCell({ value, labelValue }: { value: string; labelValue: string })}
+ {@const label = labels.find((label) => label.value === labelValue)}
+
+ {#if label}
+ {label.label}
+ {/if}
+
+ {value}
+
+
+{/snippet}
+
+{#snippet PriorityCell({ value }: { value: string })}
+ {@const priority = priorities.find((priority) => priority.value === value)}
+ {#if priority}
+
+ {/if}
+{/snippet}
+
+{#snippet RowActions({ row }: { row: Row })}
+ {@const task = taskSchema.parse(row.original)}
+
+
+ {#snippet child({ props })}
+
+
+ Open Menu
+
+ {/snippet}
+
+
+ Edit
+ Make a copy
+ Favorite
+
+
+ Labels
+
+
+ {#each labels as label (label.value)}
+
+ {label.label}
+
+ {/each}
+
+
+
+
+
+ Delete
+ ⌘⌫
+
+
+
+{/snippet}
+
+{#snippet Pagination({ table }: { table: TableType })}
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of
+ {table.getFilteredRowModel().rows.length} row(s) selected.
+
+
+
+
Rows per page
+
{
+ table.setPageSize(Number(value));
+ }}
+ >
+
+ {String(table.getState().pagination.pageSize)}
+
+
+ {#each [10, 20, 30, 40, 50] as pageSize (pageSize)}
+
+ {pageSize}
+
+ {/each}
+
+
+
+
+ Page {table.getState().pagination.pageIndex + 1} of
+ {table.getPageCount()}
+
+
+ table.setPageIndex(0)}
+ disabled={!table.getCanPreviousPage()}
+ >
+ Go to first page
+
+
+ table.previousPage()}
+ disabled={!table.getCanPreviousPage()}
+ >
+ Go to previous page
+
+
+ table.nextPage()}
+ disabled={!table.getCanNextPage()}
+ >
+ Go to next page
+
+
+ table.setPageIndex(table.getPageCount() - 1)}
+ disabled={!table.getCanNextPage()}
+ >
+ Go to last page
+
+
+
+
+
+{/snippet}
+
+{#snippet ColumnHeader({
+ column,
+ title,
+ class: className,
+ ...restProps
+}: { column: Column; title: string } & HTMLAttributes)}
+ {#if !column?.getCanSort()}
+
+ {title}
+
+ {:else}
+
+
+
+ {#snippet child({ props })}
+
+
+ {title}
+
+ {#if column.getIsSorted() === "desc"}
+
+ {:else if column.getIsSorted() === "asc"}
+
+ {:else}
+
+ {/if}
+
+ {/snippet}
+
+
+ column.toggleSorting(false)}>
+
+ Asc
+
+ column.toggleSorting(true)}>
+
+ Desc
+
+
+ column.toggleVisibility(false)}>
+
+ Hide
+
+
+
+
+ {/if}
+{/snippet}
+
+
+
+
+
+
+ {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
+
+ {#each headerGroup.headers as header (header.id)}
+
+ {#if !header.isPlaceholder}
+
+ {/if}
+
+ {/each}
+
+ {/each}
+
+
+ {#each table.getRowModel().rows as row (row.id)}
+
+ {#each row.getVisibleCells() as cell (cell.id)}
+
+
+
+ {/each}
+
+ {:else}
+
+
+ No results.
+
+
+ {/each}
+
+
+
+ {@render Pagination({ table })}
+
diff --git a/docs/src/routes/(app)/examples/tasks/components/index.ts b/docs/src/routes/(app)/examples/tasks/components/index.ts
new file mode 100644
index 0000000000..f1744700d2
--- /dev/null
+++ b/docs/src/routes/(app)/examples/tasks/components/index.ts
@@ -0,0 +1,3 @@
+export { default as DataTableToolbar } from "./data-table-toolbar.svelte";
+export { default as DataTableViewOptions } from "./data-table-view-options.svelte";
+export { default as DataTableFacetedFilter } from "./data-table-faceted-filter.svelte";
diff --git a/docs/src/routes/(app)/examples/tasks/components/user-nav.svelte b/docs/src/routes/(app)/examples/tasks/components/user-nav.svelte
new file mode 100644
index 0000000000..562bed1779
--- /dev/null
+++ b/docs/src/routes/(app)/examples/tasks/components/user-nav.svelte
@@ -0,0 +1,49 @@
+
+
+
+
+ {#snippet child({ props })}
+
+
+
+ SC
+
+
+ {/snippet}
+
+
+
+
+
+
shadcn
+
m@example.com
+
+
+
+
+
+ Profile
+ ⇧⌘P
+
+
+ Billing
+ ⌘B
+
+
+ Settings
+ ⌘S
+
+ New Team
+
+
+
+ Log out
+ ⇧⌘Q
+
+
+
+
diff --git a/docs/src/routes/(app)/examples/tasks/data/data.ts b/docs/src/routes/(app)/examples/tasks/data/data.ts
new file mode 100644
index 0000000000..c03fd00d2a
--- /dev/null
+++ b/docs/src/routes/(app)/examples/tasks/data/data.ts
@@ -0,0 +1,69 @@
+import ArrowDownIcon from "@lucide/svelte/icons/arrow-down";
+import ArrowRightIcon from "@lucide/svelte/icons/arrow-right";
+import ArrowUpIcon from "@lucide/svelte/icons/arrow-up";
+import CircleCheckIcon from "@lucide/svelte/icons/circle-check";
+import CircleIcon from "@lucide/svelte/icons/circle";
+import CircleOffIcon from "@lucide/svelte/icons/circle-off";
+import CircleHelpIcon from "@lucide/svelte/icons/circle-help";
+import TimerIcon from "@lucide/svelte/icons/timer";
+
+export const labels = [
+ {
+ value: "bug",
+ label: "Bug",
+ },
+ {
+ value: "feature",
+ label: "Feature",
+ },
+ {
+ value: "documentation",
+ label: "Documentation",
+ },
+];
+
+export const statuses = [
+ {
+ value: "backlog",
+ label: "Backlog",
+ icon: CircleHelpIcon,
+ },
+ {
+ value: "todo",
+ label: "Todo",
+ icon: CircleIcon,
+ },
+ {
+ value: "in progress",
+ label: "In Progress",
+ icon: TimerIcon,
+ },
+ {
+ value: "done",
+ label: "Done",
+ icon: CircleCheckIcon,
+ },
+ {
+ value: "canceled",
+ label: "Canceled",
+ icon: CircleOffIcon,
+ },
+];
+
+export const priorities = [
+ {
+ label: "Low",
+ value: "low",
+ icon: ArrowDownIcon,
+ },
+ {
+ label: "Medium",
+ value: "medium",
+ icon: ArrowRightIcon,
+ },
+ {
+ label: "High",
+ value: "high",
+ icon: ArrowUpIcon,
+ },
+];
diff --git a/docs/src/routes/(app)/examples/tasks/data/schemas.ts b/docs/src/routes/(app)/examples/tasks/data/schemas.ts
new file mode 100644
index 0000000000..773feaf902
--- /dev/null
+++ b/docs/src/routes/(app)/examples/tasks/data/schemas.ts
@@ -0,0 +1,13 @@
+import { z } from "zod";
+
+// We're keeping a simple non-relational schema here.
+// IRL, you will have a schema for your data models.
+export const taskSchema = z.object({
+ id: z.string(),
+ title: z.string(),
+ status: z.string(),
+ label: z.string(),
+ priority: z.string(),
+});
+
+export type Task = z.output;
diff --git a/docs/src/routes/(app)/examples/tasks/data/tasks.ts b/docs/src/routes/(app)/examples/tasks/data/tasks.ts
new file mode 100644
index 0000000000..9a56900e7a
--- /dev/null
+++ b/docs/src/routes/(app)/examples/tasks/data/tasks.ts
@@ -0,0 +1,702 @@
+export const data = [
+ {
+ id: "TASK-8782",
+ title: "You can't compress the program without quantifying the open-source SSD pixel!",
+ status: "in progress",
+ label: "documentation",
+ priority: "medium",
+ },
+ {
+ id: "TASK-7878",
+ title: "Try to calculate the EXE feed, maybe it will index the multi-byte pixel!",
+ status: "backlog",
+ label: "documentation",
+ priority: "medium",
+ },
+ {
+ id: "TASK-7839",
+ title: "We need to bypass the neural TCP card!",
+ status: "todo",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-5562",
+ title: "The SAS interface is down, bypass the open-source pixel so we can back up the PNG bandwidth!",
+ status: "backlog",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-8686",
+ title: "I'll parse the wireless SSL protocol, that should driver the API panel!",
+ status: "canceled",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-1280",
+ title: "Use the digital TLS panel, then you can transmit the haptic system!",
+ status: "done",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-7262",
+ title: "The UTF8 application is down, parse the neural bandwidth so we can back up the PNG firewall!",
+ status: "done",
+ label: "feature",
+ priority: "high",
+ },
+ {
+ id: "TASK-1138",
+ title: "Generating the driver won't do anything, we need to quantify the 1080p SMTP bandwidth!",
+ status: "in progress",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-7184",
+ title: "We need to program the back-end THX pixel!",
+ status: "todo",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-5160",
+ title: "Calculating the bus won't do anything, we need to navigate the back-end JSON protocol!",
+ status: "in progress",
+ label: "documentation",
+ priority: "high",
+ },
+ {
+ id: "TASK-5618",
+ title: "Generating the driver won't do anything, we need to index the online SSL application!",
+ status: "done",
+ label: "documentation",
+ priority: "medium",
+ },
+ {
+ id: "TASK-6699",
+ title: "I'll transmit the wireless JBOD capacitor, that should hard drive the SSD feed!",
+ status: "backlog",
+ label: "documentation",
+ priority: "medium",
+ },
+ {
+ id: "TASK-2858",
+ title: "We need to override the online UDP bus!",
+ status: "backlog",
+ label: "bug",
+ priority: "medium",
+ },
+ {
+ id: "TASK-9864",
+ title: "I'll reboot the 1080p FTP panel, that should matrix the HEX hard drive!",
+ status: "done",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-8404",
+ title: "We need to generate the virtual HEX alarm!",
+ status: "in progress",
+ label: "bug",
+ priority: "low",
+ },
+ {
+ id: "TASK-5365",
+ title: "Backing up the pixel won't do anything, we need to transmit the primary IB array!",
+ status: "in progress",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-1780",
+ title: "The CSS feed is down, index the bluetooth transmitter so we can compress the CLI protocol!",
+ status: "todo",
+ label: "documentation",
+ priority: "high",
+ },
+ {
+ id: "TASK-6938",
+ title: "Use the redundant SCSI application, then you can hack the optical alarm!",
+ status: "todo",
+ label: "documentation",
+ priority: "high",
+ },
+ {
+ id: "TASK-9885",
+ title: "We need to compress the auxiliary VGA driver!",
+ status: "backlog",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-3216",
+ title: "Transmitting the transmitter won't do anything, we need to compress the virtual HDD sensor!",
+ status: "backlog",
+ label: "documentation",
+ priority: "medium",
+ },
+ {
+ id: "TASK-9285",
+ title: "The IP monitor is down, copy the haptic alarm so we can generate the HTTP transmitter!",
+ status: "todo",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-1024",
+ title: "Overriding the microchip won't do anything, we need to transmit the digital OCR transmitter!",
+ status: "in progress",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-7068",
+ title: "You can't generate the capacitor without indexing the wireless HEX pixel!",
+ status: "canceled",
+ label: "bug",
+ priority: "low",
+ },
+ {
+ id: "TASK-6502",
+ title: "Navigating the microchip won't do anything, we need to bypass the back-end SQL bus!",
+ status: "todo",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-5326",
+ title: "We need to hack the redundant UTF8 transmitter!",
+ status: "todo",
+ label: "bug",
+ priority: "low",
+ },
+ {
+ id: "TASK-6274",
+ title: "Use the virtual PCI circuit, then you can parse the bluetooth alarm!",
+ status: "canceled",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-1571",
+ title: "I'll input the neural DRAM circuit, that should protocol the SMTP interface!",
+ status: "in progress",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-9518",
+ title: "Compressing the interface won't do anything, we need to compress the online SDD matrix!",
+ status: "canceled",
+ label: "documentation",
+ priority: "medium",
+ },
+ {
+ id: "TASK-5581",
+ title: "I'll synthesize the digital COM pixel, that should transmitter the UTF8 protocol!",
+ status: "backlog",
+ label: "documentation",
+ priority: "high",
+ },
+ {
+ id: "TASK-2197",
+ title: "Parsing the feed won't do anything, we need to copy the bluetooth DRAM bus!",
+ status: "todo",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-8484",
+ title: "We need to parse the solid state UDP firewall!",
+ status: "in progress",
+ label: "bug",
+ priority: "low",
+ },
+ {
+ id: "TASK-9892",
+ title: "If we back up the application, we can get to the UDP application through the multi-byte THX capacitor!",
+ status: "done",
+ label: "documentation",
+ priority: "high",
+ },
+ {
+ id: "TASK-9616",
+ title: "We need to synthesize the cross-platform ASCII pixel!",
+ status: "in progress",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-9744",
+ title: "Use the back-end IP card, then you can input the solid state hard drive!",
+ status: "done",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-1376",
+ title: "Generating the alarm won't do anything, we need to generate the mobile IP capacitor!",
+ status: "backlog",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-7382",
+ title: "If we back up the firewall, we can get to the RAM alarm through the primary UTF8 pixel!",
+ status: "todo",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-2290",
+ title: "I'll compress the virtual JSON panel, that should application the UTF8 bus!",
+ status: "canceled",
+ label: "documentation",
+ priority: "high",
+ },
+ {
+ id: "TASK-1533",
+ title: "You can't input the firewall without overriding the wireless TCP firewall!",
+ status: "done",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-4920",
+ title: "Bypassing the hard drive won't do anything, we need to input the bluetooth JSON program!",
+ status: "in progress",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-5168",
+ title: "If we synthesize the bus, we can get to the IP panel through the virtual TLS array!",
+ status: "in progress",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-7103",
+ title: "We need to parse the multi-byte EXE bandwidth!",
+ status: "canceled",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-4314",
+ title: "If we compress the program, we can get to the XML alarm through the multi-byte COM matrix!",
+ status: "in progress",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-3415",
+ title: "Use the cross-platform XML application, then you can quantify the solid state feed!",
+ status: "todo",
+ label: "feature",
+ priority: "high",
+ },
+ {
+ id: "TASK-8339",
+ title: "Try to calculate the DNS interface, maybe it will input the bluetooth capacitor!",
+ status: "in progress",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-6995",
+ title: "Try to hack the XSS bandwidth, maybe it will override the bluetooth matrix!",
+ status: "todo",
+ label: "feature",
+ priority: "high",
+ },
+ {
+ id: "TASK-8053",
+ title: "If we connect the program, we can get to the UTF8 matrix through the digital UDP protocol!",
+ status: "todo",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-4336",
+ title: "If we synthesize the microchip, we can get to the SAS sensor through the optical UDP program!",
+ status: "todo",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-8790",
+ title: "I'll back up the optical COM alarm, that should alarm the RSS capacitor!",
+ status: "done",
+ label: "bug",
+ priority: "medium",
+ },
+ {
+ id: "TASK-8980",
+ title: "Try to navigate the SQL transmitter, maybe it will back up the virtual firewall!",
+ status: "canceled",
+ label: "bug",
+ priority: "low",
+ },
+ {
+ id: "TASK-7342",
+ title: "Use the neural CLI card, then you can parse the online port!",
+ status: "backlog",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-5608",
+ title: "I'll hack the haptic SSL program, that should bus the UDP transmitter!",
+ status: "canceled",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-1606",
+ title: "I'll generate the bluetooth PNG firewall, that should pixel the SSL driver!",
+ status: "done",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-7872",
+ title: "Transmitting the circuit won't do anything, we need to reboot the 1080p RSS monitor!",
+ status: "canceled",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-4167",
+ title: "Use the cross-platform SMS circuit, then you can synthesize the optical feed!",
+ status: "canceled",
+ label: "bug",
+ priority: "medium",
+ },
+ {
+ id: "TASK-9581",
+ title: "You can't index the port without hacking the cross-platform XSS monitor!",
+ status: "backlog",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-8806",
+ title: "We need to bypass the back-end SSL panel!",
+ status: "done",
+ label: "bug",
+ priority: "medium",
+ },
+ {
+ id: "TASK-6542",
+ title: "Try to quantify the RSS firewall, maybe it will quantify the open-source system!",
+ status: "done",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-6806",
+ title: "The VGA protocol is down, reboot the back-end matrix so we can parse the CSS panel!",
+ status: "canceled",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-9549",
+ title: "You can't bypass the bus without connecting the neural JBOD bus!",
+ status: "todo",
+ label: "feature",
+ priority: "high",
+ },
+ {
+ id: "TASK-1075",
+ title: "Backing up the driver won't do anything, we need to parse the redundant RAM pixel!",
+ status: "done",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-1427",
+ title: "Use the auxiliary PCI circuit, then you can calculate the cross-platform interface!",
+ status: "done",
+ label: "documentation",
+ priority: "high",
+ },
+ {
+ id: "TASK-1907",
+ title: "Hacking the circuit won't do anything, we need to back up the online DRAM system!",
+ status: "todo",
+ label: "documentation",
+ priority: "high",
+ },
+ {
+ id: "TASK-4309",
+ title: "If we generate the system, we can get to the TCP sensor through the optical GB pixel!",
+ status: "backlog",
+ label: "bug",
+ priority: "medium",
+ },
+ {
+ id: "TASK-3973",
+ title: "I'll parse the back-end ADP array, that should bandwidth the RSS bandwidth!",
+ status: "todo",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-7962",
+ title: "Use the wireless RAM program, then you can hack the cross-platform feed!",
+ status: "canceled",
+ label: "bug",
+ priority: "low",
+ },
+ {
+ id: "TASK-3360",
+ title: "You can't quantify the program without synthesizing the neural OCR interface!",
+ status: "done",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-9887",
+ title: "Use the auxiliary ASCII sensor, then you can connect the solid state port!",
+ status: "backlog",
+ label: "bug",
+ priority: "medium",
+ },
+ {
+ id: "TASK-3649",
+ title: "I'll input the virtual USB system, that should circuit the DNS monitor!",
+ status: "in progress",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-3586",
+ title: "If we quantify the circuit, we can get to the CLI feed through the mobile SMS hard drive!",
+ status: "in progress",
+ label: "bug",
+ priority: "low",
+ },
+ {
+ id: "TASK-5150",
+ title: "I'll hack the wireless XSS port, that should transmitter the IP interface!",
+ status: "canceled",
+ label: "feature",
+ priority: "medium",
+ },
+ {
+ id: "TASK-3652",
+ title: "The SQL interface is down, override the optical bus so we can program the ASCII interface!",
+ status: "backlog",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-6884",
+ title: "Use the digital PCI circuit, then you can synthesize the multi-byte microchip!",
+ status: "canceled",
+ label: "feature",
+ priority: "high",
+ },
+ {
+ id: "TASK-1591",
+ title: "We need to connect the mobile XSS driver!",
+ status: "in progress",
+ label: "feature",
+ priority: "high",
+ },
+ {
+ id: "TASK-3802",
+ title: "Try to override the ASCII protocol, maybe it will parse the virtual matrix!",
+ status: "in progress",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-7253",
+ title: "Programming the capacitor won't do anything, we need to bypass the neural IB hard drive!",
+ status: "backlog",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-9739",
+ title: "We need to hack the multi-byte HDD bus!",
+ status: "done",
+ label: "documentation",
+ priority: "medium",
+ },
+ {
+ id: "TASK-4424",
+ title: "Try to hack the HEX alarm, maybe it will connect the optical pixel!",
+ status: "in progress",
+ label: "documentation",
+ priority: "medium",
+ },
+ {
+ id: "TASK-3922",
+ title: "You can't back up the capacitor without generating the wireless PCI program!",
+ status: "backlog",
+ label: "bug",
+ priority: "low",
+ },
+ {
+ id: "TASK-4921",
+ title: "I'll index the open-source IP feed, that should system the GB application!",
+ status: "canceled",
+ label: "bug",
+ priority: "low",
+ },
+ {
+ id: "TASK-5814",
+ title: "We need to calculate the 1080p AGP feed!",
+ status: "backlog",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-2645",
+ title: "Synthesizing the system won't do anything, we need to navigate the multi-byte HDD firewall!",
+ status: "todo",
+ label: "documentation",
+ priority: "medium",
+ },
+ {
+ id: "TASK-4535",
+ title: "Try to copy the JSON circuit, maybe it will connect the wireless feed!",
+ status: "in progress",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-4463",
+ title: "We need to copy the solid state AGP monitor!",
+ status: "done",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-9745",
+ title: "If we connect the protocol, we can get to the GB system through the bluetooth PCI microchip!",
+ status: "canceled",
+ label: "feature",
+ priority: "high",
+ },
+ {
+ id: "TASK-2080",
+ title: "If we input the bus, we can get to the RAM matrix through the auxiliary RAM card!",
+ status: "todo",
+ label: "bug",
+ priority: "medium",
+ },
+ {
+ id: "TASK-3838",
+ title: "I'll bypass the online TCP application, that should panel the AGP system!",
+ status: "backlog",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-1340",
+ title: "We need to navigate the virtual PNG circuit!",
+ status: "todo",
+ label: "bug",
+ priority: "medium",
+ },
+ {
+ id: "TASK-6665",
+ title: "If we parse the monitor, we can get to the SSD hard drive through the cross-platform AGP alarm!",
+ status: "canceled",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-7585",
+ title: "If we calculate the hard drive, we can get to the SSL program through the multi-byte CSS microchip!",
+ status: "backlog",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-6319",
+ title: "We need to copy the multi-byte SCSI program!",
+ status: "backlog",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-4369",
+ title: "Try to input the SCSI bus, maybe it will generate the 1080p pixel!",
+ status: "backlog",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-9035",
+ title: "We need to override the solid state PNG array!",
+ status: "canceled",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-3970",
+ title: "You can't index the transmitter without quantifying the haptic ASCII card!",
+ status: "todo",
+ label: "documentation",
+ priority: "medium",
+ },
+ {
+ id: "TASK-4473",
+ title: "You can't bypass the protocol without overriding the neural RSS program!",
+ status: "todo",
+ label: "documentation",
+ priority: "low",
+ },
+ {
+ id: "TASK-4136",
+ title: "You can't hack the hard drive without hacking the primary JSON program!",
+ status: "canceled",
+ label: "bug",
+ priority: "medium",
+ },
+ {
+ id: "TASK-3939",
+ title: "Use the back-end SQL firewall, then you can connect the neural hard drive!",
+ status: "done",
+ label: "feature",
+ priority: "low",
+ },
+ {
+ id: "TASK-2007",
+ title: "I'll input the back-end USB protocol, that should bandwidth the PCI system!",
+ status: "backlog",
+ label: "bug",
+ priority: "high",
+ },
+ {
+ id: "TASK-7516",
+ title: "Use the primary SQL program, then you can generate the auxiliary transmitter!",
+ status: "done",
+ label: "documentation",
+ priority: "medium",
+ },
+ {
+ id: "TASK-6906",
+ title: "Try to back up the DRAM system, maybe it will reboot the online transmitter!",
+ status: "done",
+ label: "feature",
+ priority: "high",
+ },
+ {
+ id: "TASK-5207",
+ title: "The SMS interface is down, copy the bluetooth bus so we can quantify the VGA card!",
+ status: "in progress",
+ label: "bug",
+ priority: "low",
+ },
+];
diff --git a/docs/src/routes/(app)/themes/+layout.svelte b/docs/src/routes/(app)/themes/+layout.svelte
new file mode 100644
index 0000000000..135fea4a29
--- /dev/null
+++ b/docs/src/routes/(app)/themes/+layout.svelte
@@ -0,0 +1,27 @@
+
+
+
+
+
+ {title}
+ {description}
+
+ Browse Themes
+ Documentation
+
+
+ {@render children()}
+
diff --git a/docs/src/routes/(app)/themes/+page.svelte b/docs/src/routes/(app)/themes/+page.svelte
new file mode 100644
index 0000000000..e47b65315c
--- /dev/null
+++ b/docs/src/routes/(app)/themes/+page.svelte
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/docs/src/routes/(view)/+layout.svelte b/docs/src/routes/(view)/+layout.svelte
new file mode 100644
index 0000000000..10ae783286
--- /dev/null
+++ b/docs/src/routes/(view)/+layout.svelte
@@ -0,0 +1,8 @@
+
+
+
+{@render children()}
diff --git a/docs/src/routes/(view)/view/[view=view]/+page.svelte b/docs/src/routes/(view)/view/[view=view]/+page.svelte
new file mode 100644
index 0000000000..e69d83ce51
--- /dev/null
+++ b/docs/src/routes/(view)/view/[view=view]/+page.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/docs/src/routes/(view)/view/[view=view]/+page.ts b/docs/src/routes/(view)/view/[view=view]/+page.ts
new file mode 100644
index 0000000000..bdebc59270
--- /dev/null
+++ b/docs/src/routes/(view)/view/[view=view]/+page.ts
@@ -0,0 +1,25 @@
+import { error } from "@sveltejs/kit";
+import type { EntryGenerator } from "./$types.js";
+import type { Component } from "svelte";
+import { blocks } from "../../../../__registry__/blocks.js";
+import { blockMeta } from "$lib/registry/registry-block-meta.js";
+
+export const prerender = true;
+
+export const entries: EntryGenerator = () => blocks.map((view) => ({ view }));
+
+export async function load({ params }) {
+ let comp: { default: Component } | undefined;
+
+ if (params.view.startsWith("demo-") || params.view.startsWith("calendar-")) {
+ comp = await import(`../../../../lib/registry/blocks/${params.view}.svelte`);
+ } else {
+ comp = await import(`../../../../lib/registry/blocks/${params.view}/+page.svelte`);
+ }
+
+ if (!comp) error(404, "Block not found");
+
+ const meta = blockMeta[params.view as keyof typeof blockMeta];
+
+ return { component: comp.default, meta };
+}
diff --git a/docs/src/routes/+layout.svelte b/docs/src/routes/+layout.svelte
new file mode 100644
index 0000000000..2458f0844a
--- /dev/null
+++ b/docs/src/routes/+layout.svelte
@@ -0,0 +1,7 @@
+
+
+{@render children()}
diff --git a/docs/src/themes.css b/docs/src/themes.css
new file mode 100644
index 0000000000..18bffae9c4
--- /dev/null
+++ b/docs/src/themes.css
@@ -0,0 +1,334 @@
+[data-theme="default"] .theme-container {
+ --chart-1: var(--color-blue-300);
+ --chart-2: var(--color-blue-500);
+ --chart-3: var(--color-blue-600);
+ --chart-4: var(--color-blue-700);
+ --chart-5: var(--color-blue-800);
+}
+
+[data-theme="blue"] .theme-container {
+ --primary: var(--color-blue-600);
+ --primary-foreground: var(--color-blue-50);
+ --ring: var(--color-blue-400);
+ --sidebar-primary: var(--color-blue-600);
+ --sidebar-primary-foreground: var(--color-blue-50);
+ --sidebar-ring: var(--color-blue-400);
+ --chart-1: var(--color-blue-300);
+ --chart-2: var(--color-blue-500);
+ --chart-3: var(--color-blue-600);
+ --chart-4: var(--color-blue-700);
+ --chart-5: var(--color-blue-800);
+
+ @variant dark {
+ --primary: var(--color-blue-500);
+ --primary-foreground: var(--color-blue-50);
+ --ring: var(--color-blue-900);
+ --sidebar-primary: var(--color-blue-500);
+ --sidebar-primary-foreground: var(--color-blue-50);
+ --sidebar-ring: var(--color-blue-900);
+ }
+}
+
+[data-theme="green"] .theme-container {
+ --primary: var(--color-lime-600);
+ --primary-foreground: var(--color-lime-50);
+ --ring: var(--color-lime-400);
+ --chart-1: var(--color-green-300);
+ --chart-2: var(--color-green-500);
+ --chart-3: var(--color-green-600);
+ --chart-4: var(--color-green-700);
+ --chart-5: var(--color-green-800);
+ --sidebar-primary: var(--color-lime-600);
+ --sidebar-primary-foreground: var(--color-lime-50);
+ --sidebar-ring: var(--color-lime-400);
+
+ @variant dark {
+ --primary: var(--color-lime-600);
+ --primary-foreground: var(--color-lime-50);
+ --ring: var(--color-lime-900);
+ --sidebar-primary: var(--color-lime-500);
+ --sidebar-primary-foreground: var(--color-lime-50);
+ --sidebar-ring: var(--color-lime-900);
+ }
+}
+
+[data-theme="amber"] .theme-container {
+ --primary: var(--color-amber-600);
+ --primary-foreground: var(--color-amber-50);
+ --ring: var(--color-amber-400);
+ --chart-1: var(--color-amber-300);
+ --chart-2: var(--color-amber-500);
+ --chart-3: var(--color-amber-600);
+ --chart-4: var(--color-amber-700);
+ --chart-5: var(--color-amber-800);
+ --sidebar-primary: var(--color-amber-600);
+ --sidebar-primary-foreground: var(--color-amber-50);
+ --sidebar-ring: var(--color-amber-400);
+
+ @variant dark {
+ --primary: var(--color-amber-500);
+ --primary-foreground: var(--color-amber-50);
+ --ring: var(--color-amber-900);
+ --sidebar-primary: var(--color-amber-500);
+ --sidebar-primary-foreground: var(--color-amber-50);
+ --sidebar-ring: var(--color-amber-900);
+ }
+}
+
+[data-theme="rose"] .theme-container {
+ --primary: var(--color-rose-600);
+ --primary-foreground: var(--color-rose-50);
+ --ring: var(--color-rose-400);
+ --chart-1: var(--color-rose-300);
+ --chart-2: var(--color-rose-500);
+ --chart-3: var(--color-rose-600);
+ --chart-4: var(--color-rose-700);
+ --chart-5: var(--color-rose-800);
+ --sidebar-primary: var(--color-rose-600);
+ --sidebar-primary-foreground: var(--color-rose-50);
+ --sidebar-ring: var(--color-rose-400);
+
+ @variant dark {
+ --primary: var(--color-rose-500);
+ --primary-foreground: var(--color-rose-50);
+ --ring: var(--color-rose-900);
+ --sidebar-primary: var(--color-rose-500);
+ --sidebar-primary-foreground: var(--color-rose-50);
+ --sidebar-ring: var(--color-rose-900);
+ }
+}
+
+[data-theme="purple"] .theme-container {
+ --primary: var(--color-purple-600);
+ --primary-foreground: var(--color-purple-50);
+ --ring: var(--color-purple-400);
+ --chart-1: var(--color-purple-300);
+ --chart-2: var(--color-purple-500);
+ --chart-3: var(--color-purple-600);
+ --chart-4: var(--color-purple-700);
+ --chart-5: var(--color-purple-800);
+ --sidebar-primary: var(--color-purple-600);
+ --sidebar-primary-foreground: var(--color-purple-50);
+ --sidebar-ring: var(--color-purple-400);
+
+ @variant dark {
+ --primary: var(--color-purple-500);
+ --primary-foreground: var(--color-purple-50);
+ --ring: var(--color-purple-900);
+ --sidebar-primary: var(--color-purple-500);
+ --sidebar-primary-foreground: var(--color-purple-50);
+ --sidebar-ring: var(--color-purple-900);
+ }
+}
+
+[data-theme="orange"] .theme-container {
+ --primary: var(--color-orange-600);
+ --primary-foreground: var(--color-orange-50);
+ --ring: var(--color-orange-400);
+ --chart-1: var(--color-orange-300);
+ --chart-2: var(--color-orange-500);
+ --chart-3: var(--color-orange-600);
+ --chart-4: var(--color-orange-700);
+ --chart-5: var(--color-orange-800);
+ --sidebar-primary: var(--color-orange-600);
+ --sidebar-primary-foreground: var(--color-orange-50);
+ --sidebar-ring: var(--color-orange-400);
+
+ @variant dark {
+ --primary: var(--color-orange-500);
+ --primary-foreground: var(--color-orange-50);
+ --ring: var(--color-orange-900);
+ --sidebar-primary: var(--color-orange-500);
+ --sidebar-primary-foreground: var(--color-orange-50);
+ --sidebar-ring: var(--color-orange-900);
+ }
+}
+
+[data-theme="teal"] .theme-container {
+ --primary: var(--color-teal-600);
+ --primary-foreground: var(--color-teal-50);
+ --chart-1: var(--color-teal-300);
+ --chart-2: var(--color-teal-500);
+ --chart-3: var(--color-teal-600);
+ --chart-4: var(--color-teal-700);
+ --chart-5: var(--color-teal-800);
+ --sidebar-primary: var(--color-teal-600);
+ --sidebar-primary-foreground: var(--color-teal-50);
+ --sidebar-ring: var(--color-teal-400);
+
+ @variant dark {
+ --primary: var(--color-teal-500);
+ --primary-foreground: var(--color-teal-50);
+ --sidebar-primary: var(--color-teal-500);
+ --sidebar-primary-foreground: var(--color-teal-50);
+ --sidebar-ring: var(--color-teal-900);
+ }
+}
+
+[data-theme="mono"] .theme-container {
+ --font-sans: var(--font-mono);
+ --primary: var(--color-stone-600);
+ --primary-foreground: var(--color-stone-50);
+ --chart-1: var(--color-stone-300);
+ --chart-2: var(--color-stone-500);
+ --chart-3: var(--color-stone-600);
+ --chart-4: var(--color-stone-700);
+ --chart-5: var(--color-stone-800);
+ --sidebar-primary: var(--color-stone-600);
+ --sidebar-primary-foreground: var(--color-stone-50);
+ --sidebar-ring: var(--color-stone-400);
+
+ @variant dark {
+ --primary: var(--color-stone-500);
+ --primary-foreground: var(--color-stone-50);
+ --sidebar-primary: var(--color-stone-500);
+ --sidebar-primary-foreground: var(--color-stone-50);
+ --sidebar-ring: var(--color-stone-900);
+ }
+
+ @media (min-width: 1024px) {
+ --font-sans: var(--font-mono);
+ --radius: 0.45em;
+ --text-lg: 1rem;
+ --text-xl: 1.1rem;
+ --text-2xl: 1.2rem;
+ --text-3xl: 1.3rem;
+ --text-4xl: 1.4rem;
+ --text-5xl: 1.5rem;
+ --text-6xl: 1.6rem;
+ --text-7xl: 1.7rem;
+ --text-8xl: 1.8rem;
+ --text-base: 0.85rem;
+ --text-sm: 0.8rem;
+ --spacing: 0.222222rem;
+ }
+
+ .rounded-xs,
+ .rounded-sm,
+ .rounded-md,
+ .rounded-lg,
+ .rounded-xl {
+ border-radius: 0;
+ }
+
+ .shadow-xs,
+ .shadow-sm,
+ .shadow-md,
+ .shadow-lg,
+ .shadow-xl {
+ box-shadow: none;
+ }
+
+ [data-slot="toggle-group"],
+ [data-slot="toggle-group-item"] {
+ @apply !rounded-none !shadow-none;
+ }
+}
+
+[data-theme="scaled"] .theme-container {
+ --chart-1: var(--color-blue-300);
+ --chart-2: var(--color-blue-500);
+ --chart-3: var(--color-blue-600);
+ --chart-4: var(--color-blue-700);
+ --chart-5: var(--color-blue-800);
+
+ @media (min-width: 1024px) {
+ --radius: 0.45em;
+ --text-lg: 1rem;
+ --text-xl: 1.1rem;
+ --text-2xl: 1.2rem;
+ --text-3xl: 1.3rem;
+ --text-4xl: 1.4rem;
+ --text-5xl: 1.5rem;
+ --text-6xl: 1.6rem;
+ --text-7xl: 1.7rem;
+ --text-8xl: 1.8rem;
+ --text-base: 0.85rem;
+ --text-sm: 0.8rem;
+ --spacing: 0.2rem;
+ }
+
+ [data-slot="select-trigger"],
+ [data-slot="toggle-group-item"] {
+ --spacing: 0.2rem;
+ }
+
+ [data-slot="card"] {
+ border-radius: var(--radius);
+ padding-block: calc(var(--spacing) * 4);
+ gap: calc(var(--spacing) * 2);
+ }
+
+ [data-slot="card"].pb-0 {
+ padding-bottom: 0;
+ }
+}
+
+[data-theme="red"] .theme-container {
+ --primary: var(--color-red-600);
+ --primary-foreground: var(--color-red-50);
+ --ring: var(--color-red-400);
+ --chart-1: var(--color-red-300);
+ --chart-2: var(--color-red-500);
+ --chart-3: var(--color-red-600);
+ --chart-4: var(--color-red-700);
+ --chart-5: var(--color-red-800);
+ --sidebar-primary: var(--color-red-600);
+ --sidebar-primary-foreground: var(--color-red-50);
+ --sidebar-ring: var(--color-red-400);
+
+ @variant dark {
+ --primary: var(--color-red-500);
+ --primary-foreground: var(--color-red-50);
+ --ring: var(--color-red-900);
+ --sidebar-primary: var(--color-red-500);
+ --sidebar-primary-foreground: var(--color-red-50);
+ --sidebar-ring: var(--color-red-900);
+ }
+}
+
+[data-theme="yellow"] .theme-container {
+ --primary: var(--color-yellow-400);
+ --primary-foreground: var(--color-yellow-900);
+ --ring: var(--color-yellow-400);
+ --chart-1: var(--color-yellow-300);
+ --chart-2: var(--color-yellow-500);
+ --chart-3: var(--color-yellow-600);
+ --chart-4: var(--color-yellow-700);
+ --chart-5: var(--color-yellow-800);
+ --sidebar-primary: var(--color-yellow-600);
+ --sidebar-primary-foreground: var(--color-yellow-50);
+ --sidebar-ring: var(--color-yellow-400);
+
+ @variant dark {
+ --primary: var(--color-yellow-500);
+ --primary-foreground: var(--color-yellow-900);
+ --ring: var(--color-yellow-900);
+ --sidebar-primary: var(--color-yellow-500);
+ --sidebar-primary-foreground: var(--color-yellow-50);
+ --sidebar-ring: var(--color-yellow-900);
+ }
+}
+
+[data-theme="violet"] .theme-container {
+ --primary: var(--color-violet-600);
+ --primary-foreground: var(--color-violet-50);
+ --ring: var(--color-violet-400);
+ --chart-1: var(--color-violet-300);
+ --chart-2: var(--color-violet-500);
+ --chart-3: var(--color-violet-600);
+ --chart-4: var(--color-violet-700);
+ --chart-5: var(--color-violet-800);
+ --sidebar-primary: var(--color-violet-600);
+ --sidebar-primary-foreground: var(--color-violet-50);
+ --sidebar-ring: var(--color-violet-400);
+
+ @variant dark {
+ --primary: var(--color-violet-500);
+ --primary-foreground: var(--color-violet-50);
+ --ring: var(--color-violet-900);
+ --sidebar-primary: var(--color-violet-500);
+ --sidebar-primary-foreground: var(--color-violet-50);
+ --sidebar-ring: var(--color-violet-900);
+ }
+}
diff --git a/sites/docs/static/android-chrome-192x192.png b/docs/static/android-chrome-192x192.png
similarity index 100%
rename from sites/docs/static/android-chrome-192x192.png
rename to docs/static/android-chrome-192x192.png
diff --git a/sites/docs/static/android-chrome-512x512.png b/docs/static/android-chrome-512x512.png
similarity index 100%
rename from sites/docs/static/android-chrome-512x512.png
rename to docs/static/android-chrome-512x512.png
diff --git a/sites/docs/static/apple-touch-icon.png b/docs/static/apple-touch-icon.png
similarity index 100%
rename from sites/docs/static/apple-touch-icon.png
rename to docs/static/apple-touch-icon.png
diff --git a/sites/docs/static/avatars/01.png b/docs/static/avatars/01.png
similarity index 100%
rename from sites/docs/static/avatars/01.png
rename to docs/static/avatars/01.png
diff --git a/sites/docs/static/avatars/02.png b/docs/static/avatars/02.png
similarity index 100%
rename from sites/docs/static/avatars/02.png
rename to docs/static/avatars/02.png
diff --git a/sites/docs/static/avatars/03.png b/docs/static/avatars/03.png
similarity index 100%
rename from sites/docs/static/avatars/03.png
rename to docs/static/avatars/03.png
diff --git a/sites/docs/static/avatars/04.png b/docs/static/avatars/04.png
similarity index 100%
rename from sites/docs/static/avatars/04.png
rename to docs/static/avatars/04.png
diff --git a/sites/docs/static/avatars/05.png b/docs/static/avatars/05.png
similarity index 100%
rename from sites/docs/static/avatars/05.png
rename to docs/static/avatars/05.png
diff --git a/docs/static/avatars/shadcn.jpg b/docs/static/avatars/shadcn.jpg
new file mode 100644
index 0000000000..7d3b7a4dab
Binary files /dev/null and b/docs/static/avatars/shadcn.jpg differ
diff --git a/sites/docs/static/favicon-16x16.png b/docs/static/favicon-16x16.png
similarity index 100%
rename from sites/docs/static/favicon-16x16.png
rename to docs/static/favicon-16x16.png
diff --git a/sites/docs/static/favicon-32x32.png b/docs/static/favicon-32x32.png
similarity index 100%
rename from sites/docs/static/favicon-32x32.png
rename to docs/static/favicon-32x32.png
diff --git a/sites/docs/static/favicon.ico b/docs/static/favicon.ico
similarity index 100%
rename from sites/docs/static/favicon.ico
rename to docs/static/favicon.ico
diff --git a/sites/docs/static/fonts/Geist/LICENSE.TXT b/docs/static/fonts/Geist/LICENSE.TXT
similarity index 100%
rename from sites/docs/static/fonts/Geist/LICENSE.TXT
rename to docs/static/fonts/Geist/LICENSE.TXT
diff --git a/sites/docs/static/fonts/Geist/geist.woff2 b/docs/static/fonts/Geist/geist.woff2
similarity index 100%
rename from sites/docs/static/fonts/Geist/geist.woff2
rename to docs/static/fonts/Geist/geist.woff2
diff --git a/sites/docs/src/lib/img/blocks/dashboard-1-dark.jpg b/docs/static/img/blocks/dashboard-1-dark.jpg
similarity index 100%
rename from sites/docs/src/lib/img/blocks/dashboard-1-dark.jpg
rename to docs/static/img/blocks/dashboard-1-dark.jpg
diff --git a/sites/docs/src/lib/img/blocks/dashboard-1.jpg b/docs/static/img/blocks/dashboard-1.jpg
similarity index 100%
rename from sites/docs/src/lib/img/blocks/dashboard-1.jpg
rename to docs/static/img/blocks/dashboard-1.jpg
diff --git a/sites/docs/src/lib/img/blocks/dashboard-2-dark.jpg b/docs/static/img/blocks/dashboard-2-dark.jpg
similarity index 100%
rename from sites/docs/src/lib/img/blocks/dashboard-2-dark.jpg
rename to docs/static/img/blocks/dashboard-2-dark.jpg
diff --git a/sites/docs/src/lib/img/blocks/dashboard-2.jpg b/docs/static/img/blocks/dashboard-2.jpg
similarity index 100%
rename from sites/docs/src/lib/img/blocks/dashboard-2.jpg
rename to docs/static/img/blocks/dashboard-2.jpg
diff --git a/sites/docs/src/lib/img/blocks/dashboard-3-dark.jpg b/docs/static/img/blocks/dashboard-3-dark.jpg
similarity index 100%
rename from sites/docs/src/lib/img/blocks/dashboard-3-dark.jpg
rename to docs/static/img/blocks/dashboard-3-dark.jpg
diff --git a/sites/docs/src/lib/img/blocks/dashboard-3.jpg b/docs/static/img/blocks/dashboard-3.jpg
similarity index 100%
rename from sites/docs/src/lib/img/blocks/dashboard-3.jpg
rename to docs/static/img/blocks/dashboard-3.jpg
diff --git a/docs/static/img/examples/authentication-dark.png b/docs/static/img/examples/authentication-dark.png
new file mode 100644
index 0000000000..40c30cd1e9
Binary files /dev/null and b/docs/static/img/examples/authentication-dark.png differ
diff --git a/docs/static/img/examples/authentication-light.png b/docs/static/img/examples/authentication-light.png
new file mode 100644
index 0000000000..2b94c8e54c
Binary files /dev/null and b/docs/static/img/examples/authentication-light.png differ
diff --git a/docs/static/img/examples/cards-dark.png b/docs/static/img/examples/cards-dark.png
new file mode 100644
index 0000000000..d0e388f918
Binary files /dev/null and b/docs/static/img/examples/cards-dark.png differ
diff --git a/docs/static/img/examples/cards-light.png b/docs/static/img/examples/cards-light.png
new file mode 100644
index 0000000000..7e06120162
Binary files /dev/null and b/docs/static/img/examples/cards-light.png differ
diff --git a/docs/static/img/examples/dashboard-dark.png b/docs/static/img/examples/dashboard-dark.png
new file mode 100644
index 0000000000..4ca59cf593
Binary files /dev/null and b/docs/static/img/examples/dashboard-dark.png differ
diff --git a/docs/static/img/examples/dashboard-light.png b/docs/static/img/examples/dashboard-light.png
new file mode 100644
index 0000000000..fca14b5b47
Binary files /dev/null and b/docs/static/img/examples/dashboard-light.png differ
diff --git a/docs/static/img/examples/forms-dark.png b/docs/static/img/examples/forms-dark.png
new file mode 100644
index 0000000000..4656216df0
Binary files /dev/null and b/docs/static/img/examples/forms-dark.png differ
diff --git a/docs/static/img/examples/forms-light.png b/docs/static/img/examples/forms-light.png
new file mode 100644
index 0000000000..217bd423cd
Binary files /dev/null and b/docs/static/img/examples/forms-light.png differ
diff --git a/docs/static/img/examples/mail-dark.png b/docs/static/img/examples/mail-dark.png
new file mode 100644
index 0000000000..eaf26dc284
Binary files /dev/null and b/docs/static/img/examples/mail-dark.png differ
diff --git a/docs/static/img/examples/mail-light.png b/docs/static/img/examples/mail-light.png
new file mode 100644
index 0000000000..2d56866b12
Binary files /dev/null and b/docs/static/img/examples/mail-light.png differ
diff --git a/docs/static/img/examples/music-dark.png b/docs/static/img/examples/music-dark.png
new file mode 100644
index 0000000000..a83f903966
Binary files /dev/null and b/docs/static/img/examples/music-dark.png differ
diff --git a/docs/static/img/examples/music-light.png b/docs/static/img/examples/music-light.png
new file mode 100644
index 0000000000..154af0f809
Binary files /dev/null and b/docs/static/img/examples/music-light.png differ
diff --git a/docs/static/img/examples/playground-dark.png b/docs/static/img/examples/playground-dark.png
new file mode 100644
index 0000000000..53963e3003
Binary files /dev/null and b/docs/static/img/examples/playground-dark.png differ
diff --git a/docs/static/img/examples/playground-light.png b/docs/static/img/examples/playground-light.png
new file mode 100644
index 0000000000..42447c91e8
Binary files /dev/null and b/docs/static/img/examples/playground-light.png differ
diff --git a/docs/static/img/examples/tasks-dark.png b/docs/static/img/examples/tasks-dark.png
new file mode 100644
index 0000000000..410b36e1c3
Binary files /dev/null and b/docs/static/img/examples/tasks-dark.png differ
diff --git a/docs/static/img/examples/tasks-light.png b/docs/static/img/examples/tasks-light.png
new file mode 100644
index 0000000000..dde19006f1
Binary files /dev/null and b/docs/static/img/examples/tasks-light.png differ
diff --git a/sites/docs/static/images/placeholder-logo.svg b/docs/static/img/placeholder-logo.svg
similarity index 100%
rename from sites/docs/static/images/placeholder-logo.svg
rename to docs/static/img/placeholder-logo.svg
diff --git a/sites/docs/static/images/placeholder-user.jpg b/docs/static/img/placeholder-user.jpg
similarity index 100%
rename from sites/docs/static/images/placeholder-user.jpg
rename to docs/static/img/placeholder-user.jpg
diff --git a/sites/docs/static/images/placeholder.svg b/docs/static/img/placeholder.svg
similarity index 100%
rename from sites/docs/static/images/placeholder.svg
rename to docs/static/img/placeholder.svg
diff --git a/docs/static/img/registry/dashboard-01-dark.png b/docs/static/img/registry/dashboard-01-dark.png
new file mode 100644
index 0000000000..df5cf9fda0
Binary files /dev/null and b/docs/static/img/registry/dashboard-01-dark.png differ
diff --git a/docs/static/img/registry/dashboard-01-light.png b/docs/static/img/registry/dashboard-01-light.png
new file mode 100644
index 0000000000..521403321f
Binary files /dev/null and b/docs/static/img/registry/dashboard-01-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-controlled-dark.png b/docs/static/img/registry/demo-sidebar-controlled-dark.png
new file mode 100644
index 0000000000..d24740a589
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-controlled-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-controlled-light.png b/docs/static/img/registry/demo-sidebar-controlled-light.png
new file mode 100644
index 0000000000..142e23e4fb
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-controlled-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-dark.png b/docs/static/img/registry/demo-sidebar-dark.png
new file mode 100644
index 0000000000..d05488f88b
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-footer-dark.png b/docs/static/img/registry/demo-sidebar-footer-dark.png
new file mode 100644
index 0000000000..788a31cbb0
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-footer-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-footer-light.png b/docs/static/img/registry/demo-sidebar-footer-light.png
new file mode 100644
index 0000000000..3e10b647cc
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-footer-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-group-action-dark.png b/docs/static/img/registry/demo-sidebar-group-action-dark.png
new file mode 100644
index 0000000000..7c32fba0e7
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-group-action-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-group-action-light.png b/docs/static/img/registry/demo-sidebar-group-action-light.png
new file mode 100644
index 0000000000..6804b34173
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-group-action-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-group-collapsible-dark.png b/docs/static/img/registry/demo-sidebar-group-collapsible-dark.png
new file mode 100644
index 0000000000..befeaa8d27
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-group-collapsible-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-group-collapsible-light.png b/docs/static/img/registry/demo-sidebar-group-collapsible-light.png
new file mode 100644
index 0000000000..d5dca6cafe
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-group-collapsible-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-group-dark.png b/docs/static/img/registry/demo-sidebar-group-dark.png
new file mode 100644
index 0000000000..5b3fcc65b0
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-group-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-group-light.png b/docs/static/img/registry/demo-sidebar-group-light.png
new file mode 100644
index 0000000000..25dbe94393
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-group-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-header-dark.png b/docs/static/img/registry/demo-sidebar-header-dark.png
new file mode 100644
index 0000000000..fe7de267e2
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-header-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-header-light.png b/docs/static/img/registry/demo-sidebar-header-light.png
new file mode 100644
index 0000000000..26a6520612
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-header-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-light.png b/docs/static/img/registry/demo-sidebar-light.png
new file mode 100644
index 0000000000..883b1c2ac6
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-menu-action-dark.png b/docs/static/img/registry/demo-sidebar-menu-action-dark.png
new file mode 100644
index 0000000000..7a0e491433
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-menu-action-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-menu-action-light.png b/docs/static/img/registry/demo-sidebar-menu-action-light.png
new file mode 100644
index 0000000000..6d97cf414c
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-menu-action-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-menu-badge-dark.png b/docs/static/img/registry/demo-sidebar-menu-badge-dark.png
new file mode 100644
index 0000000000..5852e40800
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-menu-badge-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-menu-badge-light.png b/docs/static/img/registry/demo-sidebar-menu-badge-light.png
new file mode 100644
index 0000000000..d428a49fac
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-menu-badge-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-menu-collapsible-dark.png b/docs/static/img/registry/demo-sidebar-menu-collapsible-dark.png
new file mode 100644
index 0000000000..0fb81c4e07
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-menu-collapsible-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-menu-collapsible-light.png b/docs/static/img/registry/demo-sidebar-menu-collapsible-light.png
new file mode 100644
index 0000000000..9c01854649
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-menu-collapsible-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-menu-dark.png b/docs/static/img/registry/demo-sidebar-menu-dark.png
new file mode 100644
index 0000000000..48a380d10c
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-menu-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-menu-light.png b/docs/static/img/registry/demo-sidebar-menu-light.png
new file mode 100644
index 0000000000..13b4af6d52
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-menu-light.png differ
diff --git a/docs/static/img/registry/demo-sidebar-menu-sub-dark.png b/docs/static/img/registry/demo-sidebar-menu-sub-dark.png
new file mode 100644
index 0000000000..5bfd7b61fa
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-menu-sub-dark.png differ
diff --git a/docs/static/img/registry/demo-sidebar-menu-sub-light.png b/docs/static/img/registry/demo-sidebar-menu-sub-light.png
new file mode 100644
index 0000000000..adaa674c13
Binary files /dev/null and b/docs/static/img/registry/demo-sidebar-menu-sub-light.png differ
diff --git a/docs/static/img/registry/login-01-dark.png b/docs/static/img/registry/login-01-dark.png
new file mode 100644
index 0000000000..8c5b4a93c2
Binary files /dev/null and b/docs/static/img/registry/login-01-dark.png differ
diff --git a/docs/static/img/registry/login-01-light.png b/docs/static/img/registry/login-01-light.png
new file mode 100644
index 0000000000..40a9bd37ba
Binary files /dev/null and b/docs/static/img/registry/login-01-light.png differ
diff --git a/docs/static/img/registry/login-02-dark.png b/docs/static/img/registry/login-02-dark.png
new file mode 100644
index 0000000000..c3f61e3dd6
Binary files /dev/null and b/docs/static/img/registry/login-02-dark.png differ
diff --git a/docs/static/img/registry/login-02-light.png b/docs/static/img/registry/login-02-light.png
new file mode 100644
index 0000000000..c68ce741a5
Binary files /dev/null and b/docs/static/img/registry/login-02-light.png differ
diff --git a/docs/static/img/registry/login-03-dark.png b/docs/static/img/registry/login-03-dark.png
new file mode 100644
index 0000000000..a2f82aa32e
Binary files /dev/null and b/docs/static/img/registry/login-03-dark.png differ
diff --git a/docs/static/img/registry/login-03-light.png b/docs/static/img/registry/login-03-light.png
new file mode 100644
index 0000000000..9902838a2e
Binary files /dev/null and b/docs/static/img/registry/login-03-light.png differ
diff --git a/docs/static/img/registry/login-04-dark.png b/docs/static/img/registry/login-04-dark.png
new file mode 100644
index 0000000000..7142d1e8b1
Binary files /dev/null and b/docs/static/img/registry/login-04-dark.png differ
diff --git a/docs/static/img/registry/login-04-light.png b/docs/static/img/registry/login-04-light.png
new file mode 100644
index 0000000000..3be9ae33c7
Binary files /dev/null and b/docs/static/img/registry/login-04-light.png differ
diff --git a/docs/static/img/registry/login-05-dark.png b/docs/static/img/registry/login-05-dark.png
new file mode 100644
index 0000000000..dc8ad16faf
Binary files /dev/null and b/docs/static/img/registry/login-05-dark.png differ
diff --git a/docs/static/img/registry/login-05-light.png b/docs/static/img/registry/login-05-light.png
new file mode 100644
index 0000000000..1a9a37c942
Binary files /dev/null and b/docs/static/img/registry/login-05-light.png differ
diff --git a/docs/static/img/registry/sidebar-01-dark.png b/docs/static/img/registry/sidebar-01-dark.png
new file mode 100644
index 0000000000..cb6167345b
Binary files /dev/null and b/docs/static/img/registry/sidebar-01-dark.png differ
diff --git a/docs/static/img/registry/sidebar-01-light.png b/docs/static/img/registry/sidebar-01-light.png
new file mode 100644
index 0000000000..3626d28575
Binary files /dev/null and b/docs/static/img/registry/sidebar-01-light.png differ
diff --git a/docs/static/img/registry/sidebar-02-dark.png b/docs/static/img/registry/sidebar-02-dark.png
new file mode 100644
index 0000000000..248cf2805a
Binary files /dev/null and b/docs/static/img/registry/sidebar-02-dark.png differ
diff --git a/docs/static/img/registry/sidebar-02-light.png b/docs/static/img/registry/sidebar-02-light.png
new file mode 100644
index 0000000000..0da3d51273
Binary files /dev/null and b/docs/static/img/registry/sidebar-02-light.png differ
diff --git a/docs/static/img/registry/sidebar-03-dark.png b/docs/static/img/registry/sidebar-03-dark.png
new file mode 100644
index 0000000000..7c17c33be3
Binary files /dev/null and b/docs/static/img/registry/sidebar-03-dark.png differ
diff --git a/docs/static/img/registry/sidebar-03-light.png b/docs/static/img/registry/sidebar-03-light.png
new file mode 100644
index 0000000000..ea8474b509
Binary files /dev/null and b/docs/static/img/registry/sidebar-03-light.png differ
diff --git a/docs/static/img/registry/sidebar-04-dark.png b/docs/static/img/registry/sidebar-04-dark.png
new file mode 100644
index 0000000000..a9356bc3bb
Binary files /dev/null and b/docs/static/img/registry/sidebar-04-dark.png differ
diff --git a/docs/static/img/registry/sidebar-04-light.png b/docs/static/img/registry/sidebar-04-light.png
new file mode 100644
index 0000000000..32e11ac364
Binary files /dev/null and b/docs/static/img/registry/sidebar-04-light.png differ
diff --git a/docs/static/img/registry/sidebar-05-dark.png b/docs/static/img/registry/sidebar-05-dark.png
new file mode 100644
index 0000000000..97ae1f699f
Binary files /dev/null and b/docs/static/img/registry/sidebar-05-dark.png differ
diff --git a/docs/static/img/registry/sidebar-05-light.png b/docs/static/img/registry/sidebar-05-light.png
new file mode 100644
index 0000000000..7537cc4d6d
Binary files /dev/null and b/docs/static/img/registry/sidebar-05-light.png differ
diff --git a/docs/static/img/registry/sidebar-06-dark.png b/docs/static/img/registry/sidebar-06-dark.png
new file mode 100644
index 0000000000..c540f8822d
Binary files /dev/null and b/docs/static/img/registry/sidebar-06-dark.png differ
diff --git a/docs/static/img/registry/sidebar-06-light.png b/docs/static/img/registry/sidebar-06-light.png
new file mode 100644
index 0000000000..19351e98f2
Binary files /dev/null and b/docs/static/img/registry/sidebar-06-light.png differ
diff --git a/docs/static/img/registry/sidebar-07-dark.png b/docs/static/img/registry/sidebar-07-dark.png
new file mode 100644
index 0000000000..467d516060
Binary files /dev/null and b/docs/static/img/registry/sidebar-07-dark.png differ
diff --git a/docs/static/img/registry/sidebar-07-light.png b/docs/static/img/registry/sidebar-07-light.png
new file mode 100644
index 0000000000..9f68e3f532
Binary files /dev/null and b/docs/static/img/registry/sidebar-07-light.png differ
diff --git a/docs/static/img/registry/sidebar-08-dark.png b/docs/static/img/registry/sidebar-08-dark.png
new file mode 100644
index 0000000000..03b5a7bda2
Binary files /dev/null and b/docs/static/img/registry/sidebar-08-dark.png differ
diff --git a/docs/static/img/registry/sidebar-08-light.png b/docs/static/img/registry/sidebar-08-light.png
new file mode 100644
index 0000000000..836429171a
Binary files /dev/null and b/docs/static/img/registry/sidebar-08-light.png differ
diff --git a/docs/static/img/registry/sidebar-09-dark.png b/docs/static/img/registry/sidebar-09-dark.png
new file mode 100644
index 0000000000..d7cd98cd27
Binary files /dev/null and b/docs/static/img/registry/sidebar-09-dark.png differ
diff --git a/docs/static/img/registry/sidebar-09-light.png b/docs/static/img/registry/sidebar-09-light.png
new file mode 100644
index 0000000000..c1ff4aa573
Binary files /dev/null and b/docs/static/img/registry/sidebar-09-light.png differ
diff --git a/docs/static/img/registry/sidebar-10-dark.png b/docs/static/img/registry/sidebar-10-dark.png
new file mode 100644
index 0000000000..1ba0abf6b1
Binary files /dev/null and b/docs/static/img/registry/sidebar-10-dark.png differ
diff --git a/docs/static/img/registry/sidebar-10-light.png b/docs/static/img/registry/sidebar-10-light.png
new file mode 100644
index 0000000000..3990b2c31a
Binary files /dev/null and b/docs/static/img/registry/sidebar-10-light.png differ
diff --git a/docs/static/img/registry/sidebar-11-dark.png b/docs/static/img/registry/sidebar-11-dark.png
new file mode 100644
index 0000000000..fd02b4e247
Binary files /dev/null and b/docs/static/img/registry/sidebar-11-dark.png differ
diff --git a/docs/static/img/registry/sidebar-11-light.png b/docs/static/img/registry/sidebar-11-light.png
new file mode 100644
index 0000000000..51346aca18
Binary files /dev/null and b/docs/static/img/registry/sidebar-11-light.png differ
diff --git a/docs/static/img/registry/sidebar-12-dark.png b/docs/static/img/registry/sidebar-12-dark.png
new file mode 100644
index 0000000000..d3ee0c0e4c
Binary files /dev/null and b/docs/static/img/registry/sidebar-12-dark.png differ
diff --git a/docs/static/img/registry/sidebar-12-light.png b/docs/static/img/registry/sidebar-12-light.png
new file mode 100644
index 0000000000..67f2d51172
Binary files /dev/null and b/docs/static/img/registry/sidebar-12-light.png differ
diff --git a/docs/static/img/registry/sidebar-13-dark.png b/docs/static/img/registry/sidebar-13-dark.png
new file mode 100644
index 0000000000..85478b41e7
Binary files /dev/null and b/docs/static/img/registry/sidebar-13-dark.png differ
diff --git a/docs/static/img/registry/sidebar-13-light.png b/docs/static/img/registry/sidebar-13-light.png
new file mode 100644
index 0000000000..bd5fff8a21
Binary files /dev/null and b/docs/static/img/registry/sidebar-13-light.png differ
diff --git a/docs/static/img/registry/sidebar-14-dark.png b/docs/static/img/registry/sidebar-14-dark.png
new file mode 100644
index 0000000000..5ef70a0f5d
Binary files /dev/null and b/docs/static/img/registry/sidebar-14-dark.png differ
diff --git a/docs/static/img/registry/sidebar-14-light.png b/docs/static/img/registry/sidebar-14-light.png
new file mode 100644
index 0000000000..5afe72d017
Binary files /dev/null and b/docs/static/img/registry/sidebar-14-light.png differ
diff --git a/docs/static/img/registry/sidebar-15-dark.png b/docs/static/img/registry/sidebar-15-dark.png
new file mode 100644
index 0000000000..1781ff9028
Binary files /dev/null and b/docs/static/img/registry/sidebar-15-dark.png differ
diff --git a/docs/static/img/registry/sidebar-15-light.png b/docs/static/img/registry/sidebar-15-light.png
new file mode 100644
index 0000000000..80cb8a9747
Binary files /dev/null and b/docs/static/img/registry/sidebar-15-light.png differ
diff --git a/docs/static/img/registry/sidebar-16-dark.png b/docs/static/img/registry/sidebar-16-dark.png
new file mode 100644
index 0000000000..54f06d15fc
Binary files /dev/null and b/docs/static/img/registry/sidebar-16-dark.png differ
diff --git a/docs/static/img/registry/sidebar-16-light.png b/docs/static/img/registry/sidebar-16-light.png
new file mode 100644
index 0000000000..6eb30ea471
Binary files /dev/null and b/docs/static/img/registry/sidebar-16-light.png differ
diff --git a/docs/static/img/sidebar/sidebar-menu-dark.png b/docs/static/img/sidebar/sidebar-menu-dark.png
new file mode 100644
index 0000000000..e21b25420a
Binary files /dev/null and b/docs/static/img/sidebar/sidebar-menu-dark.png differ
diff --git a/docs/static/img/sidebar/sidebar-menu.png b/docs/static/img/sidebar/sidebar-menu.png
new file mode 100644
index 0000000000..b7ff06ddfd
Binary files /dev/null and b/docs/static/img/sidebar/sidebar-menu.png differ
diff --git a/docs/static/img/sidebar/sidebar-structure-dark.png b/docs/static/img/sidebar/sidebar-structure-dark.png
new file mode 100644
index 0000000000..8806dd2511
Binary files /dev/null and b/docs/static/img/sidebar/sidebar-structure-dark.png differ
diff --git a/docs/static/img/sidebar/sidebar-structure.png b/docs/static/img/sidebar/sidebar-structure.png
new file mode 100644
index 0000000000..6c91f67062
Binary files /dev/null and b/docs/static/img/sidebar/sidebar-structure.png differ
diff --git a/sites/docs/static/og.png b/docs/static/og.png
similarity index 100%
rename from sites/docs/static/og.png
rename to docs/static/og.png
diff --git a/docs/static/placeholder.svg b/docs/static/placeholder.svg
new file mode 100644
index 0000000000..e763910b27
--- /dev/null
+++ b/docs/static/placeholder.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/static/schema.json b/docs/static/schema.json
new file mode 100644
index 0000000000..6ad9426602
--- /dev/null
+++ b/docs/static/schema.json
@@ -0,0 +1,96 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "$schema": {
+ "type": "string"
+ },
+ "style": {
+ "description": "DEPRECATED IN TAILWIND v4! The style for your components. This cannot be changed after initialization.",
+ "type": "string"
+ },
+ "tailwind": {
+ "type": "object",
+ "properties": {
+ "css": {
+ "description": "Path to the CSS file that imports Tailwind CSS into your project.",
+ "type": "string"
+ },
+ "baseColor": {
+ "description": "Used to generate the default color palette for your components. This cannot be changed after initialization.",
+ "type": "string"
+ },
+ "config": {
+ "description": "DEPRECATED IN TAILWIND v4! The path to your `tailwind.config.[js|ts]` file.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "css",
+ "baseColor"
+ ],
+ "additionalProperties": false
+ },
+ "aliases": {
+ "description": "The CLI uses these values and the `alias` config from your `svelte.config.js` file to place generated components in the correct location.",
+ "type": "object",
+ "properties": {
+ "components": {
+ "description": "Import alias for your components.",
+ "type": "string"
+ },
+ "utils": {
+ "description": "Import alias for your utility functions.",
+ "type": "string"
+ },
+ "ui": {
+ "description": "Import alias for your UI components. Defaults to `$lib/components/ui`.",
+ "type": "string"
+ },
+ "hooks": {
+ "description": "Import alias for your hooks. Defaults to `$lib/hooks`.",
+ "type": "string"
+ },
+ "lib": {
+ "description": "Import alias for your library, which is typically where you store your components, utils, hooks, etc. Defaults to `$lib`.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "components",
+ "utils"
+ ],
+ "additionalProperties": false
+ },
+ "registry": {
+ "description": "The registry URL tells the CLI where to fetch the shadcn-svelte components/registry from. You can pin this to a specific preview release or your own fork of the registry.",
+ "type": "string"
+ },
+ "typescript": {
+ "description": "Used to determine if Typescript is used for this project. When set to `false`, `.js` files will be installed instead. Defaults to `true`.",
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "config": {
+ "description": "Path to the tsconfig/jsconfig file.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "config"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ }
+ },
+ "required": [
+ "tailwind",
+ "aliases"
+ ],
+ "additionalProperties": false
+}
\ No newline at end of file
diff --git a/sites/docs/static/site.webmanifest b/docs/static/site.webmanifest
similarity index 100%
rename from sites/docs/static/site.webmanifest
rename to docs/static/site.webmanifest
diff --git a/docs/svelte.config.js b/docs/svelte.config.js
new file mode 100644
index 0000000000..161d5ef1e4
--- /dev/null
+++ b/docs/svelte.config.js
@@ -0,0 +1,114 @@
+// @ts-check
+import { mdsx } from "mdsx";
+import adapter from "@sveltejs/adapter-cloudflare";
+import MagicString from "magic-string";
+import { mdsxConfig } from "./mdsx.config.js";
+
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+ preprocess: [mdsx(mdsxConfig), componentPreviews()],
+ extensions: [".svelte", ".md"],
+
+ kit: {
+ // https://kit.svelte.dev/docs/adapter-cloudflare#options
+ adapter: adapter({
+ routes: {
+ // Since we have so many static assets, we'll manually define
+ // the globs for them to save our 100 include/exclude limit
+ exclude: [
+ "",
+ // pre-rendered content
+ "/docs/*",
+ "/docs.html",
+ "/blocks/*",
+ "/blocks.html",
+ "/view/*",
+ // static
+ "/registry/*",
+ "/fonts/*",
+ "/avatars/*",
+ "/img/*",
+ "/schema/*",
+ "/android-chrome-192x192.png",
+ "/android-chrome-512x512.png",
+ "/apple-touch-icon.png",
+ "/favicon-16x16.png",
+ "/favicon-32x32.png",
+ "/favicon.ico",
+ "/og.png",
+ "/schema.json",
+ "/site.webmanifest",
+ "/themes.css",
+ ],
+ },
+ }),
+ prerender: {
+ handleMissingId: (details) => {
+ if (details.id === "#") return;
+ console.warn(details.message);
+ },
+ handleHttpError: (details) => {
+ // TODO: remove once all referenced pages are added
+ console.warn(details.message);
+ },
+ },
+ alias: {
+ "$content/*": ".velite/*",
+ },
+ },
+};
+
+export default config;
+
+/**
+ * Detects the `name` of the previewing component, imports it directly and
+ * passes it to the `ComponentPreview` as a prop.
+ * @returns {import("svelte/compiler").PreprocessorGroup}
+ */
+function componentPreviews() {
+ const TARGET = " s.replace(/-./g, (w) => w[1].toUpperCase());
+
+ return {
+ name: "inject-component-preview",
+ markup: ({ content, filename }) => {
+ if (!filename?.endsWith(".md") || !content.includes(TARGET)) return;
+
+ const ms = new MagicString(content);
+ const results = content.matchAll(/ {
+ return {
+ ...data,
+ slug: data.path.split("/").slice(1).join("/"),
+ slugFull: `/${data.path}`,
+ };
+ });
+
+const gettingStarted = defineCollection({
+ name: "gettingStarted",
+ pattern: "./*.md",
+ schema: docSchema,
+});
+
+const migration = defineCollection({
+ name: "migration",
+ pattern: "./migration/**/*.md",
+ schema: docSchema,
+});
+
+const components = defineCollection({
+ name: "components",
+ pattern: "./components/**/*.md",
+ schema: docSchema,
+});
+
+const installation = defineCollection({
+ name: "installation",
+ pattern: "./installation/**/*.md",
+ schema: docSchema,
+});
+
+const darkMode = defineCollection({
+ name: "darkMode",
+ pattern: "./dark-mode/**/*.md",
+ schema: docSchema,
+});
+
+const registry = defineCollection({
+ name: "registry",
+ pattern: "./registry/**/*.md",
+ schema: docSchema,
+});
+
+export default defineConfig({
+ root: "./content",
+ collections: {
+ gettingStarted,
+ migration,
+ components,
+ installation,
+ darkMode,
+ registry,
+ },
+ output: { assets: "static" },
+});
diff --git a/docs/vite.config.ts b/docs/vite.config.ts
new file mode 100644
index 0000000000..85421810b4
--- /dev/null
+++ b/docs/vite.config.ts
@@ -0,0 +1,95 @@
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { execSync } from "node:child_process";
+import { toJSONSchema } from "zod/v4";
+import { minimatch } from "minimatch";
+import { defineConfig } from "vite";
+import tailwindcss from "@tailwindcss/vite";
+import { sveltekit } from "@sveltejs/kit/vite";
+import { registrySchema, registryItemSchema, componentsJsonSchema } from "@shadcn-svelte/registry";
+import { build } from "./scripts/build-registry.js";
+import { visualizer } from "rollup-plugin-visualizer";
+
+// don't build when we're running `vite preview`
+if (!process.argv.includes("preview")) {
+ console.log("Building registry...");
+ writeJsonSchemas();
+ await buildRegistry();
+ console.log("Registry built.");
+}
+
+const __dirname = fileURLToPath(new URL(".", import.meta.url));
+export const veliteDirPath = path.join(__dirname, ".velite");
+export const staticDirPath = path.join(__dirname, "src/registry/json");
+export const contentDirPath = path.join(__dirname, "content");
+
+export default defineConfig({
+ plugins: [
+ visualizer({
+ emitFile: true,
+ filename: "stats.html",
+ }),
+ tailwindcss(),
+ sveltekit(),
+ {
+ name: "registry-builder",
+ enforce: "pre",
+ async watchChange(id) {
+ if (!minimatch(id, "**/src/lib/registry/**")) return;
+ this.info("Registry file updated. Rebuilding registry...");
+ await buildRegistry();
+ this.info("Registry built.");
+ },
+ },
+ ],
+ server: {
+ fs: {
+ allow: [veliteDirPath, staticDirPath, contentDirPath],
+ },
+ },
+ build: {
+ rollupOptions: {
+ output: {
+ manualChunks: {
+ icons: ["@lucide/svelte", "@tabler/icons-svelte"],
+ },
+ },
+ },
+ },
+});
+
+function writeJsonSchemas() {
+ const schemaDir = path.resolve("static", "schema");
+ if (!fs.existsSync(schemaDir)) {
+ fs.mkdirSync(schemaDir, { recursive: true });
+ }
+
+ const componentsJSON = toJSONSchema(componentsJsonSchema);
+ fs.writeFileSync(
+ path.resolve("static", "schema.json"),
+ JSON.stringify(componentsJSON, null, "\t")
+ );
+
+ const registry = toJSONSchema(registrySchema);
+ fs.writeFileSync(
+ path.resolve(schemaDir, "registry.json"),
+ JSON.stringify(registry, null, "\t")
+ );
+
+ const registryItem = toJSONSchema(registryItemSchema);
+ fs.writeFileSync(
+ path.resolve(schemaDir, "registry-item.json"),
+ JSON.stringify(registryItem, null, "\t")
+ );
+}
+
+async function buildRegistry() {
+ await build();
+ execSync("pnpm shadcn-svelte registry build --output static/registry", {
+ stdio: ["pipe", "pipe", "inherit"],
+ });
+ fs.cpSync(path.resolve("static", "registry"), path.resolve("src", "__registry__", "json"), {
+ recursive: true,
+ });
+}
diff --git a/eslint.config.js b/eslint.config.js
index 3640345fa9..09d61e8772 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,3 +1,64 @@
-import config, { DEFAULT_IGNORES } from "@huntabyte/eslint-config";
+import { fileURLToPath } from "node:url";
+import js from "@eslint/js";
+import { includeIgnoreFile } from "@eslint/compat";
+import prettier from "eslint-config-prettier";
+import svelte from "eslint-plugin-svelte";
+import globals from "globals";
+import ts from "typescript-eslint";
-export default config({ svelte: true, ignorePatterns: [...DEFAULT_IGNORES, ".github/**/*"] });
+const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url));
+
+export default ts.config(
+ includeIgnoreFile(gitignorePath),
+ js.configs.recommended,
+ ...ts.configs.recommended,
+ ...svelte.configs["flat/recommended"],
+ prettier,
+ ...svelte.configs["flat/prettier"],
+ {
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ ...globals.node,
+ },
+ },
+ },
+ {
+ files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
+ languageOptions: {
+ parserOptions: {
+ // Only uncomment this if you want it to take 3 minutes https://github.com/sveltejs/eslint-plugin-svelte/issues/1084
+ // projectService: true,
+ extraFileExtensions: [".svelte"],
+ parser: ts.parser,
+ },
+ },
+ rules: {
+ "svelte/no-useless-mustaches": "warn",
+ },
+ },
+ {
+ rules: {
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ {
+ argsIgnorePattern: "^_",
+ varsIgnorePattern: "^_",
+ },
+ ],
+ "@typescript-eslint/no-unused-expressions": "off",
+ },
+ },
+ {
+ ignores: [
+ "build/",
+ ".svelte-kit/",
+ "dist/",
+ ".svelte-kit/**/*",
+ "docs/.svelte-kit/**/*",
+ ".svelte-kit",
+ "playgrounds/**/*",
+ "packages/cli/dist/**/*",
+ ],
+ }
+);
diff --git a/package.json b/package.json
index 213bd67b76..208468574f 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "name": "shadcn-svelte",
+ "name": "@shadcn-svelte/monorepo",
"version": "0.0.1",
"description": "monorepo for shadcn-svelte",
"author": {
@@ -8,29 +8,26 @@
},
"private": true,
"scripts": {
- "build": "pnpm build:registry && pnpm build:site",
- "build:cli": "pnpm -F shadcn-svelte build",
- "build:registry": "pnpm -F docs build:registry",
- "build:site": "pnpm -F docs build",
- "dev": "pnpm build:registry && pnpm -F docs dev",
- "dev:cli": "pnpm -F shadcn-svelte dev",
+ "build": "pnpm build:docs",
+ "build:cli": "pnpm -r -F \"./packages/**\" build",
+ "build:docs": "pnpm -F docs build",
+ "dev": "pnpm -F docs dev",
+ "dev:cli": "pnpm -F \"./packages/**\" --parallel --reporter append-only --color dev",
"preview": "pnpm -F docs preview",
- "test": "pnpm -F docs test",
- "check": "pnpm -F docs check",
- "check:watch": "pnpm -F docs check:watch",
- "test:unit": "pnpm -F docs test:unit",
+ "test": "pnpm -F shadcn-svelte test",
+ "build:registry-template": "pnpm build:cli && pnpm -F registry-template build:registry",
+ "check": "pnpm -F docs check && pnpm -F \"./packages/**\" check",
"lint": "prettier --check . && eslint .",
- "lint:fix": "eslint --fix .",
"format": "prettier --write .",
"preinstall": "npx only-allow pnpm",
- "postinstall": "pnpm -r sync",
+ "postinstall": "pnpm build:cli && pnpm -r sync",
"ci:publish": "changeset publish",
"ci:build": "pnpm build:cli",
"ci:release": "pnpm ci:build && pnpm ci:publish"
},
"engines": {
"pnpm": ">=9",
- "node": ">=18"
+ "node": ">=20"
},
"packageManager": "pnpm@9.7.1",
"repository": {
@@ -40,21 +37,26 @@
"license": "MIT",
"type": "module",
"devDependencies": {
- "@changesets/cli": "^2.27.1",
- "@huntabyte/eslint-config": "^0.3.2",
- "@huntabyte/eslint-plugin": "^0.1.0",
- "eslint": "^9.0.0",
- "eslint-plugin-svelte": "^2.37.0",
- "prettier": "^3.2.5",
- "prettier-plugin-svelte": "^3.2.2",
- "prettier-plugin-tailwindcss": "0.5.13",
- "pretty-quick": "^4.0.0",
- "simple-git-hooks": "^2.10.0",
- "svelte": "^4.2.12",
- "svelte-eslint-parser": "^0.41.0",
- "wrangler": "^3.95.0"
+ "@changesets/cli": "^2.29.4",
+ "@eslint/compat": "^1.2.9",
+ "@eslint/js": "^9.28.0",
+ "@types/node": "^22.15.30",
+ "eslint": "^9.28.0",
+ "eslint-config-prettier": "^10.1.5",
+ "eslint-plugin-svelte": "^3.9.1",
+ "globals": "^16.2.0",
+ "minimatch": "^10.0.1",
+ "prettier": "^3.5.3",
+ "prettier-plugin-svelte": "^3.4.0",
+ "prettier-plugin-tailwindcss": "^0.6.12",
+ "pretty-quick": "^4.2.2",
+ "svelte": "^5.33.17",
+ "typescript": "^5.8.3",
+ "typescript-eslint": "^8.33.1"
},
- "simple-git-hooks": {
- "pre-commit": "pnpm -r format:staged --staged"
+ "pnpm": {
+ "onlyBuiltDependencies": [
+ "esbuild"
+ ]
}
}
diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore
index 22c2664e59..fd6a38af0f 100644
--- a/packages/cli/.gitignore
+++ b/packages/cli/.gitignore
@@ -6,6 +6,7 @@ node_modules
.env
.env.*
!.env.example
+!test/**/*
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.turbo
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index 3ddcb735d3..c5cabb6147 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,128 @@
# shadcn-svelte
+## 1.0.0-next.19
+
+### Patch Changes
+
+- 9c94564: feat: add support for registry-item type `registry:style`
+
+## 1.0.0-next.18
+
+### Patch Changes
+
+- 670a3e4: fix: respect `utils` path-alias
+
+## 1.0.0-next.17
+
+### Patch Changes
+
+- 44b2445: feat: allow import maps to be used as path aliases
+- 425edb7: fix: disable ES transformations when stripping types
+
+## 1.0.0-next.16
+
+### Patch Changes
+
+- 9488a61: feat: add customizable `typescript.config` path option to `components.json`
+
+## 1.0.0-next.15
+
+### Patch Changes
+
+- 6c54fae: fix: always strip trailing slashes from path aliases
+
+## 1.0.0-next.14
+
+### Patch Changes
+
+- 633a6de: fix: ensure path aliases resolve to the correct path
+
+## 1.0.0-next.13
+
+### Patch Changes
+
+- ea9f77c: fix: remove dangling comma in 'components to install' list
+
+## 1.0.0-next.12
+
+### Patch Changes
+
+- 5bddbc6: fix: resolve deps from node_modules when evaluating their versions
+- 34d0a9f: chore: display the output of a package manager's `install` process during dependency installation
+
+## 1.0.0-next.11
+
+### Patch Changes
+
+- ad4cd6b: breaking: svelte 5 + tailwindcss v4
+
+## 1.0.0-next.10
+
+### Patch Changes
+
+- dc196c3: chore: point to `tw3.shadcn-svelte.com` subdomain on `init`
+
+## 1.0.0-next.9
+
+### Patch Changes
+
+- 962d8be: fix: Improve error message when failing to fetch base colors.
+
+## 1.0.0-next.8
+
+### Patch Changes
+
+- d764bdf: fix(cli)(next): Add missing ui alias option
+
+## 1.0.0-next.7
+
+### Patch Changes
+
+- 8826014: feat: Improved error message when fetching the registry fails
+
+## 1.0.0-next.6
+
+### Patch Changes
+
+- 7669720: fix: workaround caching issue preventing the correct registry from being fetched
+
+## 1.0.0-next.5
+
+### Patch Changes
+
+- 61d98fd: chore: Add warning for incompatible dependency
+
+## 1.0.0-next.4
+
+### Patch Changes
+
+- f932494: fix: ensure `ui` and `hooks` paths are normalized
+
+## 1.0.0-next.3
+
+### Patch Changes
+
+- 11d0ff3: fix: Ensure `svelte-kit sync` executes locally
+
+## 1.0.0-next.2
+
+### Patch Changes
+
+- fb7c683: fix(next): add newline to end of `components.json`
+
+## 1.0.0-next.1
+
+### Patch Changes
+
+- e29f8f5: fix: Ensure `utils.(js|ts)` is not fetched from the registry on `update` command
+- e29f8f5: fix: `update` command now properly updates components
+
+## 1.0.0-next.0
+
+### Major Changes
+
+- b479077: Svelte 5
+
## 0.14.3
### Patch Changes
diff --git a/packages/cli/README.md b/packages/cli/README.md
index 4397e6ca2e..3e754e7342 100644
--- a/packages/cli/README.md
+++ b/packages/cli/README.md
@@ -6,7 +6,7 @@ A CLI for adding shadcn components to your project.
Use the `init` command to initialize dependencies for a new project.
-The `init` command installs dependencies, adds the `cn` util, configures `tailwind.config.cjs`, and sets up CSS variables for the project.
+The `init` command installs dependencies, adds the `cn` util, configures, and sets up CSS variables for the project.
```bash
npx shadcn-svelte init
diff --git a/packages/cli/__mocks__/fs.cjs b/packages/cli/__mocks__/fs.cjs
new file mode 100644
index 0000000000..bb707dfbd4
--- /dev/null
+++ b/packages/cli/__mocks__/fs.cjs
@@ -0,0 +1,5 @@
+// we can also use `import`, but then
+// every export should be explicitly defined
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+const { fs } = require("memfs");
+module.exports = fs;
diff --git a/packages/cli/__mocks__/fs/promises.cjs b/packages/cli/__mocks__/fs/promises.cjs
new file mode 100644
index 0000000000..0786df6d28
--- /dev/null
+++ b/packages/cli/__mocks__/fs/promises.cjs
@@ -0,0 +1,5 @@
+// we can also use `import`, but then
+// every export should be explicitly defined
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+const { fs } = require("memfs");
+module.exports = fs.promises;
diff --git a/packages/cli/package.json b/packages/cli/package.json
index f9787c7161..2f9aed0953 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "shadcn-svelte",
- "version": "0.14.3",
+ "version": "1.0.0-next.19",
"description": "Add components to your apps.",
"license": "MIT",
"author": {
@@ -32,30 +32,40 @@
"build": "tsup --minify",
"check": "tsc --noEmit",
"start": "node dist/index.js",
- "start:dev": "cross-env COMPONENTS_REGISTRY_URL=http://localhost:5173 node dist/index.js",
+ "start:dev": "cross-env COMPONENTS_REGISTRY_URL=http://localhost:5173/registry node dist/index.js",
"start:proxy": "pnpm dlx straightforward@latest --port 9000",
- "release": "changeset version",
- "test": "vitest"
+ "test": "pnpm -w build:registry-template && vitest"
},
"dependencies": {
- "@clack/core": "^0.3.4",
- "chalk": "5.2.0",
- "commander": "^10.0.1",
- "execa": "^7.2.0",
- "is-unicode-supported": "^2.0.0",
- "node-fetch-native": "^1.6.4"
+ "commander": "^13.1.0",
+ "node-fetch-native": "^1.6.4",
+ "postcss": "^8.4.39"
},
"devDependencies": {
+ "@clack/prompts": "^1.0.0-alpha.0",
+ "@shadcn-svelte/registry": "workspace:*",
+ "@svecosystem/strip-types": "^0.0.2",
+ "@sveltejs/acorn-typescript": "^1.0.5",
"@types/node": "^18.19.22",
+ "@types/semver": "^7.5.8",
+ "acorn": "^8.13.0",
+ "chalk": "^5.4.0",
"cross-env": "^7.0.3",
+ "deepmerge": "^4.3.1",
+ "empathic": "^1.1.0",
+ "estree-walker": "^3.0.3",
"get-tsconfig": "^4.7.3",
- "ignore": "^5.3.1",
+ "ignore": "^7.0.4",
+ "memfs": "^4.17.2",
"package-manager-detector": "^1.2.0",
- "sisteransi": "^1.0.5",
- "tsup": "^8.0.0",
+ "resolve.exports": "^2.0.3",
+ "semver": "^7.7.1",
+ "sucrase": "^3.35.0",
+ "tinyexec": "^1.0.1",
+ "tsup": "^8.4.0",
"type-fest": "^3.13.1",
- "typescript": "^5.0.0",
- "valibot": "^0.36.0",
- "vitest": "^0.34.6"
+ "typescript": "^5.8.3",
+ "vitest": "^3.1.3",
+ "zod": "^3.25.17"
}
}
diff --git a/packages/cli/src/commands/add.ts b/packages/cli/src/commands/add.ts
deleted file mode 100644
index 3dceacd63f..0000000000
--- a/packages/cli/src/commands/add.ts
+++ /dev/null
@@ -1,269 +0,0 @@
-import { existsSync, promises as fs } from "node:fs";
-import path from "node:path";
-import process from "node:process";
-import color from "chalk";
-import { Command } from "commander";
-import { execa } from "execa";
-import * as v from "valibot";
-import { resolveCommand } from "package-manager-detector";
-import { type Config, getConfig } from "../utils/get-config.js";
-import { getEnvProxy } from "../utils/get-env-proxy.js";
-import { ConfigError, error, handleError } from "../utils/errors.js";
-import {
- fetchTree,
- getItemTargetPath,
- // getRegistryBaseColor,
- getRegistryIndex,
- resolveTree,
-} from "../utils/registry";
-import { transformImports } from "../utils/transformers.js";
-import * as p from "../utils/prompts.js";
-import { intro, prettifyList } from "../utils/prompt-helpers.js";
-import { detectPM } from "../utils/auto-detect.js";
-
-const highlight = (...args: unknown[]) => color.bold.cyan(...args);
-
-const addOptionsSchema = v.object({
- components: v.optional(v.array(v.string())),
- yes: v.boolean(),
- all: v.boolean(),
- overwrite: v.boolean(),
- cwd: v.string(),
- path: v.optional(v.string()),
- deps: v.boolean(),
- proxy: v.optional(v.string()),
-});
-
-type AddOptions = v.InferOutput;
-
-export const add = new Command()
- .command("add")
- .description("add components to your project")
- .argument("[components...]", "name of components")
- .option("-c, --cwd ", "the working directory", process.cwd())
- .option("--no-deps", "skips adding & installing package dependencies")
- .option("-a, --all", "install all components to your project", false)
- .option("-y, --yes", "skip confirmation prompt", false)
- .option("-o, --overwrite", "overwrite existing files", false)
- .option("--proxy ", "fetch components from registry using a proxy", getEnvProxy())
- .option("-p, --path ", "the path to add the component to")
- .action(async (components, opts) => {
- try {
- intro();
- const options = v.parse(addOptionsSchema, {
- components,
- ...opts,
- });
-
- const cwd = path.resolve(options.cwd);
-
- if (!existsSync(cwd)) {
- throw error(`The path ${color.cyan(cwd)} does not exist. Please try again.`);
- }
-
- const config = await getConfig(cwd);
- if (!config) {
- throw new ConfigError(
- `Configuration file is missing. Please run ${color.green("init")} to create a ${highlight("components.json")} file.`
- );
- }
-
- await runAdd(cwd, config, options);
-
- p.outro(`${color.green("Success!")} Component installation completed.`);
- } catch (error) {
- handleError(error);
- }
- });
-
-async function runAdd(cwd: string, config: Config, options: AddOptions) {
- if (options.proxy !== undefined) {
- process.env.HTTP_PROXY = options.proxy;
- p.log.info(`You are using the provided proxy: ${color.green(options.proxy)}`);
- }
-
- const registryIndex = await getRegistryIndex();
-
- let selectedComponents = new Set(
- options.all ? registryIndex.map(({ name }) => name) : options.components
- );
-
- const registryDepMap = new Map();
- for (const item of registryIndex) {
- registryDepMap.set(item.name, item.registryDependencies);
- }
-
- if (selectedComponents === undefined || selectedComponents.size === 0) {
- const components = await p.multiselect({
- message: `Which ${highlight("components")} would you like to install?`,
- maxItems: 10,
- options: registryIndex.map(({ name, dependencies, registryDependencies }) => {
- const deps = [...(options.deps ? dependencies : []), ...registryDependencies];
- return {
- label: name,
- value: name,
- hint: deps.length ? `also installs: ${deps.join(", ")}` : undefined,
- };
- }),
- });
-
- if (p.isCancel(components)) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
- selectedComponents = new Set(components);
- } else {
- const prettyList = prettifyList(Array.from(selectedComponents));
- p.log.step(`Components to install:\n${color.gray(prettyList)}`);
- }
-
- // adds `registryDependency` to `selectedComponents` so that they can be individually overwritten
- for (const name of selectedComponents) {
- const regDeps = registryDepMap.get(name);
- regDeps?.forEach((dep) => selectedComponents.add(dep));
- }
-
- const tree = await resolveTree(registryIndex, Array.from(selectedComponents), false);
- const payload = await fetchTree(config, tree);
- // const baseColor = await getRegistryBaseColor(config.tailwind.baseColor);
-
- if (payload.length === 0) {
- p.cancel("Selected components not found.");
- process.exit(0);
- }
-
- // build a list of existing components
- const existingComponents: string[] = [];
- const targetPath = options.path ? path.resolve(cwd, options.path) : undefined;
- for (const item of payload) {
- if (selectedComponents.has(item.name) === false) continue;
-
- const targetDir = getItemTargetPath(config, item, targetPath);
- if (targetDir === null) continue;
-
- const componentExists = item.files.some((file) => {
- return existsSync(path.resolve(targetDir, item.name, file.name));
- });
-
- if (componentExists) {
- existingComponents.push(item.name);
- }
- }
-
- // prompt if the user wants to overwrite ALL components or individually
- if (options.overwrite === false && existingComponents.length > 0) {
- const prettyList = prettifyList(existingComponents);
- p.log.warn(
- `The following components ${color.bold.yellow("already exists")}:\n${color.gray(prettyList)}`
- );
-
- const overwrite = await p.confirm({
- message: `Would you like to ${color.bold.red("overwrite")} all existing components?`,
- active: "Yes, overwrite everything",
- inactive: "No, let me decide individually",
- initialValue: false,
- });
-
- if (p.isCancel(overwrite)) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
-
- options.overwrite = overwrite;
- }
-
- if (options.yes === false) {
- const proceed = await p.confirm({
- message: `Ready to install ${highlight("components")}${options.deps ? ` and ${highlight("dependencies")}?` : "?"}`,
- initialValue: true,
- });
-
- if (p.isCancel(proceed) || proceed === false) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
- }
-
- const skippedDeps = new Set();
- const dependencies = new Set();
- const tasks: p.Task[] = [];
- for (const item of payload) {
- const targetDir = getItemTargetPath(config, item, targetPath);
- if (targetDir === null) continue;
-
- if (!existsSync(targetDir)) {
- await fs.mkdir(targetDir, { recursive: true });
- }
-
- const componentPath = path.relative(process.cwd(), path.resolve(targetDir, item.name));
-
- if (!options.overwrite && existingComponents.includes(item.name)) {
- // Only confirm overwrites for selected components and not transitive dependencies
- if (selectedComponents.has(item.name)) {
- p.log.warn(
- `Component ${highlight(item.name)} already exists at ${color.gray(componentPath)}`
- );
- const overwrite = await p.confirm({
- message: `Would you like to ${color.bold.red("overwrite")} your existing ${highlight(item.name)} component?`,
- });
- if (p.isCancel(overwrite)) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
- if (overwrite === false) continue;
- }
- }
-
- // Add dependencies to the install list
- if (options.deps) {
- item.dependencies.forEach((dep) => dependencies.add(dep));
- } else {
- item.dependencies.forEach((dep) => skippedDeps.add(dep));
- }
-
- // Install Component
- tasks.push({
- title: `Installing ${highlight(item.name)}`,
- async task() {
- for (const file of item.files) {
- const componentDir = path.resolve(targetDir, item.name);
- const filePath = path.resolve(targetDir, item.name, file.name);
-
- // Run transformers.
- const content = transformImports(file.content, config);
-
- if (!existsSync(componentDir)) {
- await fs.mkdir(componentDir, { recursive: true });
- }
-
- await fs.writeFile(filePath, content);
- }
-
- return `${highlight(item.name)} installed at ${color.gray(componentPath)}`;
- },
- });
- }
-
- // Install dependencies.
- const pm = await detectPM(cwd, options.deps);
- if (pm) {
- const addCmd = resolveCommand(pm, "add", ["-D", ...dependencies])!;
- tasks.push({
- title: `${highlight(pm)}: Installing dependencies`,
- enabled: dependencies.size > 0,
- async task() {
- await execa(addCmd.command, addCmd.args, { cwd });
- return `Dependencies installed with ${highlight(pm)}`;
- },
- });
- }
-
- await p.tasks(tasks);
-
- if (!options.deps) {
- const prettyList = prettifyList([...skippedDeps], 7);
- p.log.warn(
- `Components have been installed ${color.bold.red("without")} the following ${highlight("dependencies")}:\n${color.gray(prettyList)}`
- );
- }
-}
diff --git a/packages/cli/src/commands/add/index.ts b/packages/cli/src/commands/add/index.ts
new file mode 100644
index 0000000000..4a6dc9deed
--- /dev/null
+++ b/packages/cli/src/commands/add/index.ts
@@ -0,0 +1,136 @@
+import path from "node:path";
+import process from "node:process";
+import { existsSync } from "node:fs";
+import color from "chalk";
+import { z } from "zod/v4";
+import { Command } from "commander";
+import { ConfigError, error, handleError } from "../../utils/errors.js";
+import * as cliConfig from "../../utils/get-config.js";
+import { getEnvProxy } from "../../utils/get-env-proxy.js";
+import { cancel, intro, prettifyList } from "../../utils/prompt-helpers.js";
+import * as p from "@clack/prompts";
+import * as registry from "../../utils/registry/index.js";
+import { preflightAdd } from "./preflight.js";
+import { addRegistryItems } from "../../utils/add-registry-items.js";
+import { highlight } from "../../utils/utils.js";
+import { installDependencies } from "../../utils/install-deps.js";
+
+const addOptionsSchema = z.object({
+ components: z.string().array().optional(),
+ yes: z.boolean(),
+ all: z.boolean(),
+ overwrite: z.boolean(),
+ cwd: z.string(),
+ deps: z.boolean(),
+ proxy: z.string().optional(),
+});
+
+type AddOptions = z.infer;
+
+export const add = new Command()
+ .command("add")
+ .description("add components to your project")
+ .argument("[components...]", "the components to add or a url to the component")
+ .option("-c, --cwd ", "the working directory", process.cwd())
+ .option("--no-deps", "skips adding & installing package dependencies")
+ .option("-a, --all", "install all components to your project", false)
+ .option("-y, --yes", "skip confirmation prompt", false)
+ .option("-o, --overwrite", "overwrite existing files", false)
+ .option("--proxy ", "fetch components from registry using a proxy", getEnvProxy())
+ .action(async (components, opts) => {
+ try {
+ intro();
+ const options = addOptionsSchema.parse({ components, ...opts });
+
+ const cwd = path.resolve(options.cwd);
+
+ if (!existsSync(cwd)) {
+ throw error(`The path ${color.cyan(cwd)} does not exist. Please try again.`);
+ }
+
+ await preflightAdd(cwd);
+
+ const config = await cliConfig.getConfig(cwd);
+ if (!config) {
+ throw new ConfigError(
+ `Configuration file is missing. Please run ${color.green("init")} to create a ${highlight("components.json")} file.`
+ );
+ }
+
+ await runAdd(cwd, config, options);
+
+ p.outro(`${color.green("Success!")} Components added.`);
+ } catch (error) {
+ handleError(error);
+ }
+ });
+
+async function runAdd(cwd: string, config: cliConfig.ResolvedConfig, options: AddOptions) {
+ if (options.proxy !== undefined) {
+ process.env.HTTP_PROXY = options.proxy;
+ p.log.info(`You are using the provided proxy: ${color.green(options.proxy)}`);
+ }
+
+ const registryUrl = registry.getRegistryUrl(config);
+ const shadcnIndex = await registry.getRegistryIndex(registryUrl);
+ const uiOnly = shadcnIndex.filter((f) => f.type === "registry:ui");
+
+ let selectedComponents = new Set(
+ options.all ? uiOnly.map(({ name }) => name) : options.components
+ );
+
+ // if the user hasn't passed any components prompt them to select components
+ if (selectedComponents.size === 0) {
+ const components = await p.multiselect({
+ message: `Which ${highlight("components")} would you like to add?`,
+ maxItems: 10,
+ options: uiOnly.map((item) => {
+ let deps = [...(item.registryDependencies ?? [])];
+ if (options.deps) {
+ deps = deps.concat(item.dependencies ?? []).concat(item.devDependencies ?? []);
+ }
+ return {
+ label: item.name,
+ value: item.name,
+ hint: deps.length ? `also adds: ${deps.join(", ")}` : undefined,
+ };
+ }),
+ });
+
+ if (p.isCancel(components)) cancel();
+ selectedComponents = new Set(components);
+ } else {
+ const prettyList = prettifyList(Array.from(selectedComponents));
+ p.log.step(`Components to install:\n${color.gray(prettyList)}`);
+ }
+
+ if (options.yes === false) {
+ const proceed = await p.confirm({
+ message: `Ready to install ${highlight("components")}${options.deps ? ` and ${highlight("dependencies")}?` : "?"}`,
+ initialValue: true,
+ });
+
+ if (p.isCancel(proceed) || proceed === false) cancel();
+ }
+
+ const result = await addRegistryItems({
+ config,
+ deps: options.deps,
+ overwrite: options.overwrite,
+ selectedItems: Array.from(selectedComponents),
+ });
+
+ if (options.deps) {
+ await installDependencies({
+ cwd,
+ prompt: options.deps,
+ dependencies: Array.from(result.dependencies),
+ devDependencies: Array.from(result.devDependencies),
+ });
+ } else if (result.skippedDeps.size) {
+ const prettyList = prettifyList([...result.skippedDeps], 7);
+ p.log.warn(
+ `Components have been installed ${color.bold.red("without")} the following ${highlight("dependencies")}:\n${color.gray(prettyList)}`
+ );
+ }
+}
diff --git a/packages/cli/src/commands/add/preflight.ts b/packages/cli/src/commands/add/preflight.ts
new file mode 100644
index 0000000000..650acfdf5c
--- /dev/null
+++ b/packages/cli/src/commands/add/preflight.ts
@@ -0,0 +1,91 @@
+import color from "chalk";
+import * as semver from "semver";
+import { ConfigError, error } from "../../utils/errors.js";
+import * as cliConfig from "../../utils/get-config.js";
+import { SITE_BASE_URL, TW3_SITE_BASE_URL } from "../../constants.js";
+import { highlight } from "../../utils/utils.js";
+import { getDependencyPackageInfo, getProjectPackageInfo } from "../../utils/get-package-info.js";
+
+/**
+ * Runs preflight checks for the `add` command.
+ * `add` in this CLI version should work for both Tailwind CSS v3 and v4 and Svelte 5,
+ * but fail for Svelte 4.
+ *
+ * @param cwd - The current working directory.
+ */
+export async function preflightAdd(cwd: string) {
+ const config = await cliConfig.getConfig(cwd);
+ if (!config) {
+ throw new ConfigError(
+ `Configuration file is missing. Please run ${color.green("init")} to create a ${highlight("components.json")} file.`
+ );
+ }
+
+ const sveltePkg = getDependencyPackageInfo(cwd, "svelte")?.pkg;
+ const tailwindPkg = getDependencyPackageInfo(cwd, "tailwindcss")?.pkg;
+
+ const pkg = getProjectPackageInfo(cwd);
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
+
+ const svelte = sveltePkg?.version ?? deps["svelte"];
+ const tailwindcss = tailwindPkg?.version ?? deps["tailwindcss"];
+
+ checkAddDependencies({ svelte, tailwindcss }, cwd, config);
+}
+
+/**
+ * Checks the dependencies of the user's project to ensure that the project is compatible
+ * with the expected dependencies.
+ */
+function checkAddDependencies(
+ dependencies: Partial>,
+ cwd: string,
+ config: cliConfig.RawConfig
+) {
+ if (!dependencies.tailwindcss || !dependencies.svelte) {
+ throw error(`This CLI version requires Tailwind CSS (v3 or v4) and Svelte v5.\n`);
+ }
+
+ const isSvelte5 = semver.satisfies(semver.coerce(dependencies.svelte) || "", "^5.0.0");
+ const isTailwind4 = semver.satisfies(semver.coerce(dependencies.tailwindcss) || "", "^4.0.0");
+ const isTailwind3 = semver.satisfies(semver.coerce(dependencies.tailwindcss) || "", "^3.0.0");
+
+ // this is the happy path
+ if (isTailwind4 && isSvelte5) {
+ const host = new URL(config.registry).host;
+ if (host === "next.shadcn-svelte.com") {
+ // update `next` registry and schema urls
+ config.$schema = cliConfig.DEFAULT_CONFIG.$schema;
+ config.registry = cliConfig.DEFAULT_CONFIG.registry;
+ cliConfig.writeConfig(cwd, config);
+ }
+ return;
+ }
+
+ if (isTailwind3 && isSvelte5) {
+ // if no `style` field, then we can assume their components.json is already updated
+ if (!config.style) return;
+
+ updateLegacyConfig(cwd, config);
+ return;
+ }
+
+ // if incompatible, throw error
+ throw error(
+ `This CLI version requires Tailwind CSS (v3 or v4) and Svelte v5.\n\n` +
+ `If you are on Svelte v4, use ${highlight("shadcn-svelte@0.14 add")} instead, or consider migrating to Svelte 5: ${SITE_BASE_URL}/docs/migration/svelte-5`
+ );
+}
+
+/**
+ * Updates a legacy config (Tailwind v3 / Svelte v5) to be compatible with the new CLI.
+ * It does so by updating the `registry` field to point to the Tailwind v3/Svelte v5 registry
+ * based on their `style` field and removes the `style` field before writing the config back.
+ */
+function updateLegacyConfig(cwd: string, config: cliConfig.RawConfig) {
+ // should we just write the config back here or prompt them to confirm?
+ cliConfig.writeConfig(cwd, {
+ ...config,
+ registry: `${TW3_SITE_BASE_URL}/registry/${config.style}`,
+ });
+}
diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts
index 37db0838c0..841cbae111 100644
--- a/packages/cli/src/commands/index.ts
+++ b/packages/cli/src/commands/index.ts
@@ -1,3 +1,4 @@
-export * from "./add.js";
-export * from "./init.js";
-export * from "./update.js";
+export { add } from "./add/index.js";
+export { init } from "./init/index.js";
+export { update } from "./update/index.js";
+export { registry } from "./registry/index.js";
diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts
deleted file mode 100644
index 3930e1b0af..0000000000
--- a/packages/cli/src/commands/init.ts
+++ /dev/null
@@ -1,388 +0,0 @@
-import { existsSync, promises as fs } from "node:fs";
-import path from "node:path";
-import process from "node:process";
-import color from "chalk";
-import * as v from "valibot";
-import { Command, Option } from "commander";
-import { execa } from "execa";
-import { resolveCommand } from "package-manager-detector";
-import * as cliConfig from "../utils/get-config.js";
-import type { Config } from "../utils/get-config.js";
-import { error, handleError } from "../utils/errors.js";
-import { getBaseColors, getRegistryBaseColor, getStyles } from "../utils/registry";
-import * as templates from "../utils/templates.js";
-import * as p from "../utils/prompts.js";
-import { intro, prettifyList } from "../utils/prompt-helpers.js";
-import { resolveImport } from "../utils/resolve-imports.js";
-import { syncSvelteKit } from "../utils/sveltekit.js";
-import {
- type DetectLanguageResult,
- detectConfigs,
- detectLanguage,
- detectPM,
-} from "../utils/auto-detect.js";
-
-const PROJECT_DEPENDENCIES = ["tailwind-variants", "clsx", "tailwind-merge"] as const;
-const highlight = (...args: unknown[]) => color.bold.cyan(...args);
-
-const baseColors = getBaseColors();
-const styles = getStyles();
-
-const initOptionsSchema = v.object({
- cwd: v.string(),
- style: v.optional(v.string()),
- baseColor: v.optional(v.string()),
- css: v.optional(v.string()),
- tailwindConfig: v.optional(v.string()),
- componentsAlias: v.optional(v.string()),
- utilsAlias: v.optional(v.string()),
- deps: v.boolean(),
-});
-
-type InitOptions = v.InferOutput;
-
-export const init = new Command()
- .command("init")
- .description("initialize your project and install dependencies")
- .option("-c, --cwd ", "the working directory", process.cwd())
- .option("--no-deps", "disable adding & installing dependencies")
- .addOption(
- new Option("--style ", "the style for the components").choices(
- styles.map((style) => style.name)
- )
- )
- .addOption(
- new Option("--base-color ", "the base color for the components").choices(
- baseColors.map((color) => color.name)
- )
- )
- .option("--css ", "path to the global CSS file")
- .option("--tailwind-config ", "path to the tailwind config file")
- .option("--components-alias ", "import alias for components")
- .option("--utils-alias ", "import alias for utils")
- .action(async (opts) => {
- intro();
- const options = v.parse(initOptionsSchema, opts);
- const cwd = path.resolve(options.cwd);
-
- try {
- // Ensure target directory exists.
- if (!existsSync(cwd)) {
- throw error(`The path ${color.cyan(cwd)} does not exist. Please try again.`);
- }
-
- // Read config.
- const existingConfig = await cliConfig.getConfig(cwd);
- const config = await promptForConfig(cwd, existingConfig, options);
-
- await runInit(cwd, config, options);
-
- p.outro(`${color.green("Success!")} Project initialization completed.`);
- } catch (e) {
- handleError(e);
- }
- });
-
-function validateOptions(cwd: string, options: InitOptions, langConfig: DetectLanguageResult) {
- if (options.css) {
- if (!existsSync(path.resolve(cwd, options.css))) {
- throw error(
- `The provided global CSS file path ${color.cyan(options.css)} does not exist. Please enter a valid path.`
- );
- }
- }
-
- if (options.tailwindConfig) {
- if (!existsSync(path.resolve(cwd, options.tailwindConfig))) {
- throw error(
- `The provided tailwind config file path ${color.cyan(options.tailwindConfig)} does not exist. Please enter a valid path.`
- );
- }
- }
-
- if (options.componentsAlias) {
- const validationResult = validateImportAlias(options.componentsAlias, langConfig);
- if (validationResult) {
- throw error(validationResult);
- }
- }
-
- if (options.utilsAlias) {
- const validationResult = validateImportAlias(options.utilsAlias, langConfig);
- if (validationResult) {
- throw error(validationResult);
- }
- }
-}
-
-async function promptForConfig(cwd: string, defaultConfig: Config | null, options: InitOptions) {
- // if it's a SvelteKit project, run sync so that the aliases are always up to date
- await syncSvelteKit(cwd);
-
- const detectedConfigs = detectConfigs(cwd, { relative: true });
-
- const langConfig = detectLanguage(cwd);
- if (langConfig === undefined) {
- throw error(
- `Failed to find a ${highlight("tsconfig.json")} or ${highlight("jsconfig.json")} file. See: ${color.underline("https://www.shadcn-svelte.com/docs/installation#opt-out-of-typescript")}`
- );
- }
-
- // Validation for any paths provided by flags
- validateOptions(cwd, options, langConfig);
-
- // Styles
- let style = styles.find((style) => style.name === options.style)?.name;
- if (style === undefined) {
- const input = await p.select({
- message: `Which ${highlight("style")} would you like to use?`,
- initialValue: defaultConfig?.style ?? cliConfig.DEFAULT_STYLE,
- options: styles.map((style) => ({
- label: style.label,
- value: style.name,
- })),
- });
-
- if (p.isCancel(input)) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
-
- style = input;
- }
-
- // Base Color
- let tailwindBaseColor = baseColors.find((color) => color.name === options.baseColor)?.name;
- if (tailwindBaseColor === undefined) {
- const input = await p.select({
- message: `Which ${highlight("base color")} would you like to use?`,
- initialValue:
- defaultConfig?.tailwind.baseColor ?? cliConfig.DEFAULT_TAILWIND_BASE_COLOR,
- options: baseColors.map((color) => ({
- label: color.label,
- value: color.name,
- })),
- });
-
- if (p.isCancel(input)) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
-
- tailwindBaseColor = input;
- }
-
- // Global CSS File
- let globalCss = options.css;
- if (globalCss === undefined) {
- const input = await p.text({
- message: `Where is your ${highlight("global CSS")} file? ${color.gray("(this file will be overwritten)")}`,
- initialValue:
- defaultConfig?.tailwind.css ??
- detectedConfigs.cssPath ??
- cliConfig.DEFAULT_TAILWIND_CSS,
- placeholder: detectedConfigs.cssPath ?? cliConfig.DEFAULT_TAILWIND_CSS,
- validate: (value) => {
- if (value && existsSync(path.resolve(cwd, value))) {
- return;
- }
- return `"${color.bold(value)}" does not exist. Please enter a valid path.`;
- },
- });
-
- if (p.isCancel(input)) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
-
- globalCss = input;
- }
-
- // Tailwind Config
- let tailwindConfig = options.tailwindConfig;
- if (tailwindConfig === undefined) {
- const input = await p.text({
- message: `Where is your ${highlight("Tailwind config")} located? ${color.gray("(this file will be overwritten)")}`,
- initialValue:
- defaultConfig?.tailwind.config ??
- detectedConfigs.tailwindPath ??
- cliConfig.DEFAULT_TAILWIND_CONFIG,
- placeholder: detectedConfigs.tailwindPath ?? cliConfig.DEFAULT_TAILWIND_CONFIG,
- validate: (value) => {
- if (value && existsSync(path.resolve(cwd, value))) {
- return;
- }
- return `"${color.bold(value)}" does not exist. Please enter a valid path.`;
- },
- });
-
- if (p.isCancel(input)) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
-
- tailwindConfig = input;
- }
-
- // Components Alias
- let componentAlias = options.componentsAlias;
- if (componentAlias === undefined) {
- const promptResult = await p.text({
- message: `Configure the import alias for ${highlight("components")}:`,
- initialValue: defaultConfig?.aliases.components ?? cliConfig.DEFAULT_COMPONENTS,
- placeholder: cliConfig.DEFAULT_COMPONENTS,
- validate: (value) => validateImportAlias(value, langConfig),
- });
-
- if (p.isCancel(promptResult)) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
-
- componentAlias = promptResult;
- }
-
- // Utils Alias
- let utilsAlias = options.utilsAlias;
- if (utilsAlias === undefined) {
- const input = await p.text({
- message: `Configure the import alias for ${highlight("utils")}:`,
- initialValue:
- defaultConfig?.aliases.utils ??
- // infers the alias from `components`. if `components = @/comps` then suggest `utils = @/utils`
- `${componentAlias?.split("/").slice(0, -1).join("/")}/utils` ??
- cliConfig.DEFAULT_UTILS,
- placeholder: cliConfig.DEFAULT_UTILS,
- validate: (value) => validateImportAlias(value, langConfig),
- });
-
- if (p.isCancel(input)) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
-
- utilsAlias = input;
- }
-
- const config = v.parse(cliConfig.rawConfigSchema, {
- $schema: "https://shadcn-svelte.com/schema.json",
- style,
- typescript: langConfig.type === "tsconfig.json",
- tailwind: {
- config: tailwindConfig,
- css: globalCss,
- baseColor: tailwindBaseColor,
- },
- aliases: {
- utils: utilsAlias,
- components: componentAlias,
- },
- });
-
- // Delete `tailwind.config.cjs` and rename to `.js`
- if (config.tailwind.config.endsWith(".cjs")) {
- p.log.info(`Your tailwind config has been renamed to ${highlight("tailwind.config.js")}.`);
- await fs.unlink(config.tailwind.config).catch(() => null);
- const renamedTailwindConfigPath = config.tailwind.config.replace(".cjs", ".js");
- config.tailwind.config = renamedTailwindConfigPath;
- }
-
- const configPaths = await cliConfig.resolveConfigPaths(cwd, config);
- return configPaths;
-}
-
-function validateImportAlias(alias: string, langConfig: DetectLanguageResult) {
- const resolvedPath = resolveImport(alias, langConfig.config);
- if (resolvedPath !== undefined) {
- return;
- }
- return `"${color.bold(alias)}" does not use an existing path alias defined in your ${color.bold(langConfig.type)}. See: ${color.underline("https://www.shadcn-svelte.com/docs/installation/manual#configure-path-aliases")}`;
-}
-
-export async function runInit(cwd: string, config: Config, options: InitOptions) {
- const tasks: p.Task[] = [];
-
- // Write to file.
- tasks.push({
- title: "Creating config file",
- async task() {
- const targetPath = path.resolve(cwd, "components.json");
- const conf = v.parse(cliConfig.rawConfigSchema, config); // inefficient, but it'll do
- await fs.writeFile(targetPath, JSON.stringify(conf, null, "\t"), "utf8");
- return `Config file ${highlight("components.json")} created`;
- },
- });
-
- // Initialize project
- tasks.push({
- title: "Initializing project",
- async task() {
- // Ensure all resolved paths directories exist.
- for (const [key, resolvedPath] of Object.entries(config.resolvedPaths)) {
- // Determine if the path is a file or directory.
- let dirname = path.extname(resolvedPath)
- ? path.dirname(resolvedPath)
- : resolvedPath;
-
- // If the utils alias is set to something like "@/lib/utils",
- // assume this is a file and remove the "utils" file name.
- // TODO: In future releases we should add support for individual utils.
- if (key === "utils" && resolvedPath.endsWith("/utils")) {
- // Remove /utils at the end.
- dirname = dirname.replace(/\/utils$/, "");
- }
-
- if (!existsSync(dirname) && key !== "utils") {
- await fs.mkdir(dirname, { recursive: true });
- }
- }
-
- // Write tailwind config.
- const { TS, JS } = templates.TAILWIND_CONFIG_WITH_VARIABLES;
- const tailwindConfigContent = config.resolvedPaths.tailwindConfig.endsWith(".ts")
- ? TS
- : JS;
- await fs.writeFile(config.resolvedPaths.tailwindConfig, tailwindConfigContent, "utf8");
-
- // Write css file.
- const baseColor = await getRegistryBaseColor(config.tailwind.baseColor);
- if (baseColor) {
- await fs.writeFile(
- config.resolvedPaths.tailwindCss,
- baseColor.cssVarsTemplate,
- "utf8"
- );
- }
-
- const utilsPath = config.resolvedPaths.utils + (config.typescript ? ".ts" : ".js");
- const utilsTemplate = config.typescript ? templates.UTILS : templates.UTILS_JS;
- // Write cn file.
- await fs.writeFile(utilsPath, utilsTemplate, "utf8");
-
- return "Project initialized";
- },
- });
-
- // Install dependencies.
- const pm = await detectPM(cwd, options.deps);
- if (pm) {
- const addCmd = resolveCommand(pm, "add", ["-D", ...PROJECT_DEPENDENCIES])!;
- tasks.push({
- title: `${highlight(pm)}: Installing dependencies`,
- enabled: options.deps,
- async task() {
- await execa(addCmd.command, addCmd.args, { cwd });
- return `Dependencies installed with ${highlight(pm)}`;
- },
- });
- }
-
- await p.tasks(tasks);
-
- if (!options.deps) {
- const prettyList = prettifyList([...PROJECT_DEPENDENCIES], 7);
- p.log.warn(
- `shadcn-svelte has been initialized ${color.bold.red("without")} the following ${highlight("dependencies")}:\n${color.gray(prettyList)}`
- );
- }
-}
diff --git a/packages/cli/src/commands/init/index.ts b/packages/cli/src/commands/init/index.ts
new file mode 100644
index 0000000000..0103bcf892
--- /dev/null
+++ b/packages/cli/src/commands/init/index.ts
@@ -0,0 +1,326 @@
+import color from "chalk";
+import { Command, Option } from "commander";
+import { existsSync, promises as fs } from "node:fs";
+import path from "node:path";
+import process from "node:process";
+import { z } from "zod/v4";
+import * as p from "@clack/prompts";
+import { detectConfigs } from "../../utils/auto-detect.js";
+import { error, handleError } from "../../utils/errors.js";
+import type { ResolvedConfig } from "../../utils/get-config.js";
+import * as cliConfig from "../../utils/get-config.js";
+import { cancel, intro, prettifyList } from "../../utils/prompt-helpers.js";
+import * as registry from "../../utils/registry/index.js";
+import { resolveImportAlias } from "../../utils/resolve-imports.js";
+import { syncSvelteKit } from "../../utils/sveltekit.js";
+import { SITE_BASE_URL } from "../../constants.js";
+import { preflightInit } from "./preflight.js";
+import { addRegistryItems } from "../../utils/add-registry-items.js";
+import { getEnvProxy } from "../../utils/get-env-proxy.js";
+import { highlight, stripTrailingSlash } from "../../utils/utils.js";
+import { installDependencies } from "../../utils/install-deps.js";
+import type { TsConfigResult } from "get-tsconfig";
+
+const baseColors = registry.getBaseColors();
+
+const initOptionsSchema = z.object({
+ cwd: z.string(),
+ baseColor: z.string().optional(),
+ css: z.string().optional(),
+ componentsAlias: z.string().optional(),
+ utilsAlias: z.string().optional(),
+ libAlias: z.string().optional(),
+ hooksAlias: z.string().optional(),
+ uiAlias: z.string().optional(),
+ deps: z.boolean(),
+ overwrite: z.boolean(),
+ proxy: z.string().optional(),
+});
+
+type InitOptions = z.infer;
+
+export const init = new Command()
+ .command("init")
+ .description("initialize your project and install dependencies")
+ .option("-c, --cwd ", "the working directory", process.cwd())
+ .option("-o, --overwrite", "overwrite existing files", false)
+ .option("--no-deps", "disable adding & installing dependencies")
+ .addOption(
+ new Option("--base-color ", "the base color for the components").choices(
+ baseColors.map((color) => color.name)
+ )
+ )
+ .option("--css ", "path to the global CSS file")
+ .option("--components-alias ", "import alias for components")
+ .option("--lib-alias ", "import alias for lib")
+ .option("--utils-alias ", "import alias for utils")
+ .option("--hooks-alias ", "import alias for hooks")
+ .option("--ui-alias ", "import alias for ui")
+ .option("--proxy ", "fetch items from registry using a proxy", getEnvProxy())
+ .action(async (opts) => {
+ intro();
+ const options = initOptionsSchema.parse(opts);
+ const cwd = path.resolve(options.cwd);
+
+ try {
+ // Ensure target directory exists.
+ if (!existsSync(cwd)) {
+ throw error(`The path ${color.cyan(cwd)} does not exist. Please try again.`);
+ }
+
+ preflightInit(cwd);
+
+ // Read config.
+ const existingConfig = cliConfig.loadConfig(cwd);
+ const config = await promptForConfig(cwd, existingConfig, options);
+
+ await runInit(cwd, config, options);
+
+ p.outro(`${color.green("Success!")} Project initialization completed.`);
+ } catch (e) {
+ handleError(e);
+ }
+ });
+
+function validateOptions(cwd: string, options: InitOptions, tsconfig: TsConfigResult) {
+ if (options.css) {
+ if (!existsSync(path.resolve(cwd, options.css))) {
+ throw error(
+ `The provided global CSS file path ${color.cyan(options.css)} does not exist. Please enter a valid path.`
+ );
+ }
+ }
+
+ for (const [alias, path] of Object.entries(options)) {
+ if (!alias.endsWith("Alias")) continue;
+ const importPath = path as string;
+ const validationResult = validateImportAlias({ cwd, importPath, tsconfig });
+ if (validationResult) {
+ throw error(validationResult);
+ }
+ }
+}
+
+async function promptForConfig(
+ cwd: string,
+ existingConfig: cliConfig.RawConfig | undefined,
+ options: InitOptions
+) {
+ const config: cliConfig.RawConfig = existingConfig ?? structuredClone(cliConfig.DEFAULT_CONFIG);
+
+ // if it's a SvelteKit project, run sync so that the aliases are always up to date
+ await syncSvelteKit(cwd);
+
+ const { cssPath, tsconfigPath } = detectConfigs(cwd, { relative: true });
+
+ let tsconfig;
+ if (existingConfig) {
+ tsconfig = cliConfig.resolveTSConfig(cwd, existingConfig);
+ } else {
+ if (!tsconfigPath) {
+ const input = await p.text({
+ message: `Where is your ${highlight("tsconfig/jsconfig")} file?`,
+ // initialValue: "tsconfig.json",
+ placeholder: "tsconfig.json",
+ validate: (value) => {
+ const tsconfigPath = path.resolve(cwd, value);
+ if (value && existsSync(tsconfigPath)) {
+ return;
+ }
+ return `"${color.bold(value)}" does not exist. Please enter a valid path.`;
+ },
+ });
+
+ if (p.isCancel(input)) cancel();
+
+ config.typescript = { config: input };
+ } else {
+ config.typescript = tsconfigPath.includes("tsconfig");
+ }
+
+ tsconfig = cliConfig.resolveTSConfig(cwd, config);
+ }
+
+ // Validation for any paths provided by flags
+ validateOptions(cwd, options, tsconfig);
+
+ // Base Color
+ let tailwindBaseColor = baseColors.find((color) => color.name === options.baseColor)?.name;
+ if (tailwindBaseColor === undefined) {
+ const input = await p.select({
+ message: `Which ${highlight("base color")} would you like to use?`,
+ initialValue:
+ existingConfig?.tailwind.baseColor ?? cliConfig.DEFAULT_CONFIG.tailwind.baseColor,
+ options: baseColors.map((color) => ({ label: color.label, value: color.name })),
+ });
+
+ if (p.isCancel(input)) cancel();
+
+ tailwindBaseColor = input;
+ }
+
+ // Global CSS File
+ let globalCss = options.css;
+ if (globalCss === undefined) {
+ const cssDefault = cliConfig.DEFAULT_CONFIG.tailwind.css;
+ const input = await p.text({
+ message: `Where is your ${highlight("global CSS")} file? ${color.gray("(this file will be overwritten)")}`,
+ initialValue: existingConfig?.tailwind.css ?? cssPath ?? cssDefault,
+ placeholder: cssPath ?? cssDefault,
+ validate: (value) => {
+ if (value && existsSync(path.resolve(cwd, value))) {
+ return;
+ }
+ return `"${color.bold(value)}" does not exist. Please enter a valid path.`;
+ },
+ });
+
+ if (p.isCancel(input)) cancel();
+
+ globalCss = input;
+ }
+
+ const promptAlias = async (alias: keyof cliConfig.RawConfig["aliases"], initial: string) => {
+ let path = options[`${alias}Alias`];
+ if (path === undefined) {
+ const input = await p.text({
+ message: `Configure the import alias for ${highlight(alias)}:`,
+ initialValue: existingConfig?.aliases[alias] ?? initial,
+ placeholder: cliConfig.DEFAULT_CONFIG.aliases[alias],
+ validate: (value) => validateImportAlias({ cwd, tsconfig, importPath: value }),
+ });
+
+ if (p.isCancel(input)) cancel();
+
+ path = stripTrailingSlash(input);
+ }
+ return path;
+ };
+
+ // Lib Alias
+ const libAlias = await promptAlias("lib", "$lib");
+
+ // Components Alias
+ const componentAlias = await promptAlias("components", `${libAlias}/components`);
+
+ // UI Alias
+ const uiAlias = await promptAlias("ui", `${componentAlias}/ui`);
+
+ // Utils Alias
+ const utilsAlias = await promptAlias("utils", `${libAlias}/utils`);
+
+ // Hooks Alias
+ const hooksAlias = await promptAlias("hooks", `${libAlias}/hooks`);
+
+ const rawConfig = cliConfig.rawConfigSchema.parse({
+ ...config,
+ tailwind: {
+ css: globalCss,
+ baseColor: tailwindBaseColor,
+ },
+ aliases: {
+ utils: utilsAlias,
+ lib: libAlias,
+ components: componentAlias,
+ hooks: hooksAlias,
+ ui: uiAlias,
+ },
+ });
+
+ const configPaths = await cliConfig.resolveConfig(cwd, rawConfig);
+ return configPaths;
+}
+
+function validateImportAlias(opts: Parameters[0]) {
+ const resolvedPath = resolveImportAlias(opts);
+ if (resolvedPath !== undefined) return;
+
+ return `"${color.bold(opts.importPath)}" does not use an existing path alias defined in your ${color.bold(path.basename(opts.tsconfig.path))}. See: ${color.underline(`${SITE_BASE_URL}/docs/installation/manual#configure-path-aliases`)}`;
+}
+
+export async function runInit(cwd: string, config: ResolvedConfig, options: InitOptions) {
+ if (options.proxy !== undefined) {
+ process.env.HTTP_PROXY = options.proxy;
+ p.log.info(`You are using the provided proxy: ${color.green(options.proxy)}`);
+ }
+ const registryUrl = registry.getRegistryUrl(config);
+
+ const tasks: p.Task[] = [];
+
+ tasks.push({
+ title: "Creating config file",
+ async task() {
+ cliConfig.writeConfig(cwd, config);
+ return `Config file ${highlight("components.json")} created`;
+ },
+ });
+
+ tasks.push({
+ title: "Validating alias paths",
+ async task() {
+ // Ensure all resolved paths directories exist.
+ for (const [key, resolvedPath] of Object.entries(config.resolvedPaths)) {
+ // Determine if the path is a file or directory.
+ let dirname = path.extname(resolvedPath)
+ ? path.dirname(resolvedPath)
+ : resolvedPath;
+
+ // If the utils alias is set to something like "@/lib/utils",
+ // assume this is a file and remove the "utils" file name.
+ // TODO: In future releases we should add support for individual utils.
+ if (key === "utils" && resolvedPath.endsWith("/utils")) {
+ // Remove /utils at the end.
+ dirname = dirname.replace(/\/utils$/, "");
+ }
+
+ if (!existsSync(dirname) && key !== "utils") {
+ await fs.mkdir(dirname, { recursive: true });
+ }
+ }
+ return `Alias paths validated`;
+ },
+ });
+
+ await p.tasks(tasks);
+
+ const result = await addRegistryItems({
+ selectedItems: ["init"],
+ config,
+ deps: options.deps,
+ overwrite: options.overwrite,
+ });
+
+ // update stylesheet
+ await p.tasks([
+ {
+ title: "Updating stylesheet",
+ async task() {
+ const baseColor = await registry.getRegistryBaseColor(
+ registryUrl,
+ config.tailwind.baseColor
+ );
+ const relative = path.relative(cwd, config.resolvedPaths.tailwindCss);
+ await fs.writeFile(
+ config.resolvedPaths.tailwindCss,
+ baseColor.cssVarsTemplate,
+ "utf8"
+ );
+ return `${highlight("Stylesheet")} updated at ${color.dim(relative)}`;
+ },
+ },
+ ]);
+
+ if (options.deps) {
+ await installDependencies({
+ cwd,
+ prompt: options.deps,
+ dependencies: Array.from(result.dependencies),
+ devDependencies: Array.from(result.devDependencies),
+ });
+ } else if (result.skippedDeps.size) {
+ const prettyList = prettifyList([...result.skippedDeps], 7);
+ p.log.warn(
+ `shadcn-svelte has been initialized ${color.bold.red("without")} the following ${highlight("dependencies")}:\n${color.gray(prettyList)}`
+ );
+ }
+}
diff --git a/packages/cli/src/commands/init/preflight.ts b/packages/cli/src/commands/init/preflight.ts
new file mode 100644
index 0000000000..d8e77189c1
--- /dev/null
+++ b/packages/cli/src/commands/init/preflight.ts
@@ -0,0 +1,77 @@
+import color from "chalk";
+import * as semver from "semver";
+import { error } from "../../utils/errors.js";
+import { highlight } from "../../utils/utils.js";
+import { TW3_SITE_BASE_URL, SITE_BASE_URL } from "../../constants.js";
+import { getDependencyPackageInfo, getProjectPackageInfo } from "../../utils/get-package-info.js";
+
+/**
+ * Runs preflight checks for the `init` command.
+ * `init` in this CLI version should only run if the user has a project that
+ * is using Tailwind CSS v4 and Svelte v5.
+ *
+ * If the user is using Tailwind CSS v3 and/or Svelte v4, we need to let them
+ * know that they need to upgrade their project in order to use this CLI.
+ *
+ * @param cwd - The current working directory.
+ */
+export function preflightInit(cwd: string) {
+ const sveltePkg = getDependencyPackageInfo(cwd, "svelte")?.pkg;
+ const tailwindPkg = getDependencyPackageInfo(cwd, "tailwindcss")?.pkg;
+
+ const pkg = getProjectPackageInfo(cwd);
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
+
+ const svelte = sveltePkg?.version ?? deps["svelte"];
+ const tailwindcss = tailwindPkg?.version ?? deps["tailwindcss"];
+
+ checkInitDependencies({ svelte, tailwindcss });
+}
+
+function checkInitDependencies(dependencies: Partial>) {
+ if (!dependencies.tailwindcss || !dependencies.svelte) {
+ throw error(
+ `This CLI version requires Tailwind CSS v4 and Svelte v5 to initialize a project.\n`
+ );
+ }
+
+ const isTailwind3 = semver.satisfies(semver.coerce(dependencies.tailwindcss) || "", "^3.0.0");
+ const isTailwind4 = semver.satisfies(semver.coerce(dependencies.tailwindcss) || "", "^4.0.0");
+ const isSvelte4 = semver.satisfies(semver.coerce(dependencies.svelte) || "", "^4.0.0");
+ const isSvelte5 = semver.satisfies(semver.coerce(dependencies.svelte) || "", "^5.0.0");
+
+ // if running Tailwind v3 and Svelte v5, we throw an error with a helpful message because
+ // `init` is only supported for Tailwind v4 and Svelte v5
+ if (isTailwind3 && isSvelte5) {
+ throw error(
+ `Initializing a project with Tailwind v3 is not supported.\n\n` +
+ `This CLI version requires Tailwind v4 and Svelte v5 for the ` +
+ `${highlight("init")} command.\n\n` +
+ `You have two options:\n` +
+ `1. Update Tailwind CSS to v4 and try again.\n` +
+ `2. Use ${highlight("shadcn-svelte@1.0.0-next.10")} that supports initializing projects with Tailwind v3.\n\n` +
+ `References:\n` +
+ `Tailwind v4 Guide: ${color.underline(`${SITE_BASE_URL}/docs/migration/tailwind-v4`)}\n` +
+ `Legacy Tailwind v3 Docs: ${color.underline(`${TW3_SITE_BASE_URL}/docs`)}\n\n`
+ );
+ }
+
+ // if running Tailwind v3 and Svelte v4, we throw a different message. This will be useful when
+ // we move this branch into `main` to point people in the right direction.
+ // TODO: add link to upgrade guide?
+ if (isTailwind3 && isSvelte4) {
+ throw error(
+ `Initializing a project with Tailwind v3 and Svelte v4 is not supported.\n\n` +
+ `This CLI version requires Tailwind v4 and Svelte v5 for the ${highlight("init")} command.\n\n` +
+ `Please use ${highlight("shadcn-svelte@0.14")} that supports Tailwind v3 + Svelte v4.\n\n`
+ );
+ }
+
+ // if not Tailwind v4 and Svelte v5 by this point, they are using Tailwind v4 and Svelte v4
+ // which is kinda cursed
+ if (!isTailwind4 || !isSvelte5) {
+ throw error(
+ `This CLI version requires Tailwind CSS v4 and Svelte v5 to initialize a project.\n`
+ );
+ }
+}
diff --git a/packages/cli/src/commands/registry/build.ts b/packages/cli/src/commands/registry/build.ts
new file mode 100644
index 0000000000..13cf11cf80
--- /dev/null
+++ b/packages/cli/src/commands/registry/build.ts
@@ -0,0 +1,235 @@
+import path from "node:path";
+import process from "node:process";
+import { existsSync, promises as fs } from "node:fs";
+import color from "chalk";
+import { z } from "zod/v4";
+import { Command } from "commander";
+import * as schema from "@shadcn-svelte/registry";
+import * as p from "@clack/prompts";
+import { intro } from "../../utils/prompt-helpers.js";
+import { error, handleError } from "../../utils/errors.js";
+import { parseDependency, toArray } from "../../utils/utils.js";
+import { ALIAS_DEFAULTS, ALIASES, SITE_BASE_URL } from "../../constants.js";
+import { getFileDependencies, resolveProjectDeps } from "./deps-resolver.js";
+
+// TODO: perhaps a `--mini` flag to remove spacing?
+const SPACER = "\t";
+
+const buildOptionsSchema = z.object({
+ registry: z.string(),
+ cwd: z.string(),
+ output: z.string(),
+});
+
+type BuildOptions = z.infer;
+
+export const build = new Command()
+ .command("build")
+ .description("build components for a shadcn-svelte registry")
+ .argument("[registry]", "path to registry.json file", "./registry.json")
+ .option("-c, --cwd ", "the working directory", process.cwd())
+ .option("-o, --output ", "destination directory for json files", "./static/r")
+ .action(async (registryPath, opts) => {
+ try {
+ intro();
+ const options = buildOptionsSchema.parse({ registry: registryPath, ...opts });
+
+ // resolve paths
+ const cwd = path.resolve(options.cwd);
+ const output = path.resolve(options.cwd, options.output);
+ const registry = path.resolve(options.cwd, options.registry);
+
+ // validate options
+ for (const [option, path] of Object.entries({ cwd, registry })) {
+ if (!existsSync(path)) {
+ throw error(`The '${option}' path ${color.cyan(path)} does not exist.`);
+ }
+ }
+
+ await runBuild({ cwd, output, registry });
+
+ p.outro(`${color.green("Success!")} Registry build completed.`);
+ } catch (error) {
+ handleError(error);
+ }
+ });
+
+async function runBuild(options: BuildOptions) {
+ const spinner = p.spinner();
+
+ spinner.start(`Parsing registry schema`);
+ const registryJson = await fs.readFile(options.registry, "utf8");
+ const registry = schema.registrySchema.parse(JSON.parse(registryJson));
+ spinner.stop(
+ `Parsed registry schema at ${color.dim(path.relative(options.cwd, options.registry))}`
+ );
+
+ const registryIndex: schema.RegistryIndex = registry.items.map((item) => {
+ return { ...item, relativeUrl: `${item.name}.json` };
+ });
+
+ // write to output
+ if (!existsSync(options.output)) {
+ await fs.mkdir(options.output, { recursive: true });
+ }
+
+ const tasks: p.Task[] = [];
+
+ // Write registry index: `registry/index.json`
+ tasks.push({
+ title: "Building registry index",
+ async task() {
+ const indexPath = path.resolve(options.output, "index.json");
+ const parsedIndex = schema.registryIndexSchema.parse(registryIndex);
+
+ await fs.writeFile(indexPath, JSON.stringify(parsedIndex, null, SPACER), "utf8");
+
+ const relative = path.relative(options.cwd, indexPath);
+ return `Registry index written to ${color.dim(relative)}`;
+ },
+ });
+
+ // Write registry items: `registry/[item].json`
+ tasks.push({
+ title: "Building registry items",
+ async task(message) {
+ const projectDeps = resolveProjectDeps(options.cwd);
+
+ // apply overrides
+ if (registry.overrideDependencies) {
+ type Dependencies = (typeof projectDeps)["dependencies"];
+ const overrideDep = (override: string, deps: Dependencies) => {
+ const { name } = parseDependency(override);
+ const versioned = deps.versions[name];
+ if (versioned) {
+ const peers = deps.deps[versioned];
+ delete deps.deps[versioned];
+
+ deps.versions[name] = override;
+ deps.deps[override] = peers ?? [];
+ }
+ };
+ for (const override of registry.overrideDependencies) {
+ overrideDep(override, projectDeps.dependencies);
+ overrideDep(override, projectDeps.devDependencies);
+ }
+ }
+
+ for (const item of registry.items) {
+ message(`Building item ${color.cyan(item.name)}`);
+ const singleFile = item.files.length === 1;
+ const nested: schema.RegistryItemFileType[] = [
+ "registry:page",
+ "registry:ui",
+ "registry:file",
+ ];
+ const toResolve = item.files.map(async (file) => {
+ let content = await fs.readFile(file.path, "utf8");
+ content = transformAliases((registry.aliases ??= {}), content);
+
+ const name = path.basename(file.path);
+
+ let target;
+ if (singleFile) target = name;
+ else if (nested.includes(file.type)) target = `${item.name}/${name}`;
+ else target = name;
+
+ return { content, name, target, ...file };
+ });
+ const files = await Promise.all(toResolve);
+
+ const dependencies = new Set(item.dependencies);
+ const devDependencies = new Set(item.devDependencies);
+
+ const registryDependencies = new Set(item.registryDependencies.map(transformLocal));
+
+ const predefinedDeps = dependencies.size > 0 && devDependencies.size > 0;
+ if (!predefinedDeps) {
+ for (const file of files) {
+ const fileDeps = await getFileDependencies({
+ ...projectDeps,
+ filename: file.name,
+ source: file.content,
+ });
+
+ // don't add detected deps if they're already predefined
+ if (!item.dependencies)
+ fileDeps.dependencies?.forEach((dep) => {
+ // type def packages should be inserted into dev deps
+ if (dep.startsWith("@types/")) devDependencies.add(dep);
+ else dependencies.add(dep);
+ });
+ if (!item.devDependencies)
+ fileDeps.devDependencies?.forEach((dep) => devDependencies.add(dep));
+ }
+ }
+
+ const parsedItem = schema.registryItemSchema.parse(
+ {
+ ...item,
+ $schema: `${SITE_BASE_URL}/schema/registry-item.json`,
+ registryDependencies: toArray(registryDependencies),
+ dependencies: toArray(dependencies),
+ devDependencies: toArray(devDependencies),
+ files,
+ },
+ // maintains the schema defined property order
+ { jitless: true }
+ );
+
+ const outputPath = path.resolve(options.output, `${item.name}.json`);
+
+ await fs.writeFile(outputPath, JSON.stringify(parsedItem, null, SPACER), "utf8");
+ }
+
+ const relative = path.relative(options.cwd, options.output);
+ return `Registry items written to ${color.dim(relative)}`;
+ },
+ });
+
+ await p.tasks(tasks);
+}
+
+/**
+ * Transforms registryDependencies that start with `local:` into a path
+ * relative to the current registry-item's json file.
+ *
+ * ```
+ * "local:stepper"
+ *```
+ * transforms into:
+ * ```
+ * "./stepper.json"
+ * ```
+ */
+export function transformLocal(registryDep: string) {
+ if (registryDep.startsWith("local:")) {
+ const LOCAL_REGEX = /^local:(.*)/;
+ return registryDep.replace(LOCAL_REGEX, "./$1.json");
+ }
+ return registryDep;
+}
+
+/**
+ * Transforms registry import aliases into a standardized format.
+ *
+ * ```
+ * import Button from "$lib/registry/ui/button/index.js"
+ * ```
+ * transforms into:
+ * ```
+ * import Button from "$UI$/button/index.js"
+ * ```
+ */
+export function transformAliases(
+ aliases: NonNullable,
+ content: string
+) {
+ for (const alias of ALIASES) {
+ const defaults = ALIAS_DEFAULTS[alias];
+ const path = (aliases[alias] ??= defaults.defaultPath);
+ content = content.replaceAll(path, defaults.placeholder);
+ }
+
+ return content;
+}
diff --git a/packages/cli/src/commands/registry/deps-resolver.ts b/packages/cli/src/commands/registry/deps-resolver.ts
new file mode 100644
index 0000000000..754350aa18
--- /dev/null
+++ b/packages/cli/src/commands/registry/deps-resolver.ts
@@ -0,0 +1,184 @@
+import * as acorn from "acorn";
+import * as svelte from "svelte/compiler";
+import { walk, type Node } from "estree-walker";
+import { tsPlugin } from "@sveltejs/acorn-typescript";
+import type { PackageJson } from "type-fest";
+import { toArray } from "../../utils/utils.js";
+import { getProjectPackageInfo, getDependencyPackageInfo } from "../../utils/get-package-info.js";
+
+export type ResolvedDependencies = {
+ /** `` */
+ deps: Record;
+ /** `` */
+ versions: Record;
+};
+
+export type ProjectDependencies = {
+ dependencies: ResolvedDependencies;
+ devDependencies: ResolvedDependencies;
+};
+
+const tsParser = acorn.Parser.extend(tsPlugin());
+
+export function resolveProjectDeps(cwd: string): ProjectDependencies {
+ const pkg = getProjectPackageInfo(cwd);
+
+ // Record
+ const dependencies = resolvePeerDeps(pkg.dependencies, cwd);
+ const devDependencies = resolvePeerDeps(pkg.devDependencies, cwd);
+
+ let projectDeps = resolveTypeDeps({ dependencies, devDependencies });
+ projectDeps = resolvePeerVersions(projectDeps);
+
+ return projectDeps;
+}
+
+/**
+ * Adds a dependency's type definition package to their respective peer list (if applicable).
+ */
+export function resolveTypeDeps(projectDeps: ProjectDependencies) {
+ for (const dependencies of Object.values(projectDeps)) {
+ for (const [name, versioned] of Object.entries(dependencies.versions)) {
+ const peers = dependencies.deps[versioned]!;
+ // transforms orgs into the proper types package name (e.g. `@org/pkg-name` => `@types/org__pkg-name`)
+ const typesName = `@types/${name.replace(/^@(.*)\/(.*)/, "$1__$2")}`;
+ const typesVersion =
+ projectDeps.dependencies.versions[typesName] ??
+ projectDeps.devDependencies.versions[typesName];
+
+ // if the types package exists, we'll add it to the peers
+ if (typesVersion) {
+ peers.push(typesName);
+ }
+ }
+ }
+
+ return projectDeps;
+}
+
+/**
+ * Applies version tags to the peer dependencies in their respective lists.
+ *
+ * `dependencies.deps` goes from `` to ``
+ */
+export function resolvePeerVersions(projectDeps: ProjectDependencies): ProjectDependencies {
+ for (const dependencies of Object.values(projectDeps)) {
+ for (const [name, peers] of Object.entries(dependencies.deps)) {
+ dependencies.deps[name] = peers
+ .map(
+ (peer) =>
+ projectDeps.dependencies.versions[peer] ||
+ projectDeps.devDependencies.versions[peer]
+ )
+ .filter((peer) => peer !== undefined);
+ }
+ }
+
+ return projectDeps;
+}
+
+export const IGNORE_DEPS = ["svelte", "@sveltejs/kit", "tailwindcss", "vite"];
+
+/**
+ * Resolves peer dependencies from a given set of dependencies from a package.json.
+ *
+ * Optional peer dependencies are ignored.
+ */
+function resolvePeerDeps(
+ dependencies: PackageJson["dependencies"],
+ cwd: string
+): ResolvedDependencies {
+ const deps: ResolvedDependencies["deps"] = {};
+ const versions: ResolvedDependencies["versions"] = {};
+
+ for (const [name, version] of Object.entries(dependencies ?? {})) {
+ const versioned = version ? `${name}@${version}` : name;
+ const peers = (deps[versioned] ??= []);
+
+ versions[name] = versioned;
+
+ const pkg = getDependencyPackageInfo(cwd, name)?.pkg;
+ if (!pkg) continue;
+
+ const { peerDependencies = {}, peerDependenciesMeta = {} } = pkg;
+ for (const [peerName] of Object.entries(peerDependencies)) {
+ // ignores certain peer deps and optional peer deps
+ if (IGNORE_DEPS.includes(peerName) || peerDependenciesMeta[peerName]?.optional)
+ continue;
+ peers.push(peerName);
+ }
+ }
+ return { deps, versions };
+}
+
+type GetFileDepOpts = {
+ filename: string;
+ source: string;
+ dependencies: ReturnType;
+ devDependencies: ReturnType;
+};
+export async function getFileDependencies(opts: GetFileDepOpts) {
+ const { filename, source } = opts;
+ let ast: unknown;
+ let moduleAst: unknown;
+
+ if (filename.endsWith(".svelte")) {
+ const { code } = await svelte.preprocess(source, [], { filename });
+ const result = svelte.parse(code, { filename });
+ ast = result.instance;
+ if (result.module) {
+ moduleAst = result.module;
+ }
+ } else if (filename.endsWith(".ts") || filename.endsWith(".js")) {
+ ast = tsParser.parse(source, { ecmaVersion: "latest", sourceType: "module" });
+ } else {
+ // unknown file (e.g. `.env` or some config file)
+ return {};
+ }
+
+ const dependencies = new Set();
+ const devDependencies = new Set();
+
+ const enter = (node: Node) => {
+ if (node.type !== "ImportDeclaration") return;
+ const source = node.source.value as string;
+
+ const deps = resolveDepsFromImport(source, opts.dependencies);
+ deps.forEach((dep) => dependencies.add(dep));
+
+ const devDeps = resolveDepsFromImport(source, opts.devDependencies);
+ devDeps.forEach((dep) => devDependencies.add(dep));
+ };
+
+ // @ts-expect-error yea, stfu
+ walk(ast, { enter });
+
+ if (moduleAst) {
+ // @ts-expect-error yea, stfu x2
+ walk(moduleAst, { enter });
+ }
+
+ return {
+ dependencies: toArray(dependencies),
+ devDependencies: toArray(devDependencies),
+ };
+}
+
+/** Returns an array of found deps. */
+export function resolveDepsFromImport(source: string, dependencies: ResolvedDependencies) {
+ const depsFound: string[] = [];
+ const simple = dependencies.versions[source] ? source : undefined;
+ const depName =
+ simple ??
+ // considers deep imports
+ Object.keys(dependencies.versions).find((dep) => source.startsWith(dep));
+
+ if (depName && !IGNORE_DEPS.includes(depName)) {
+ const versioned = dependencies.versions[depName]!;
+ const peers = dependencies.deps[versioned];
+ depsFound.push(versioned);
+ peers?.forEach((dep) => depsFound.push(dep));
+ }
+
+ return depsFound;
+}
diff --git a/packages/cli/src/commands/registry/index.ts b/packages/cli/src/commands/registry/index.ts
new file mode 100644
index 0000000000..9c8af12cb7
--- /dev/null
+++ b/packages/cli/src/commands/registry/index.ts
@@ -0,0 +1,4 @@
+import { Command } from "commander";
+import { build } from "./build.js";
+
+export const registry = new Command().command("registry").addCommand(build);
diff --git a/packages/cli/src/commands/update.ts b/packages/cli/src/commands/update.ts
deleted file mode 100644
index 1e9c7bcb4e..0000000000
--- a/packages/cli/src/commands/update.ts
+++ /dev/null
@@ -1,252 +0,0 @@
-import { existsSync, promises as fs } from "node:fs";
-import path from "node:path";
-import process from "node:process";
-import color from "chalk";
-import { Command } from "commander";
-import { execa } from "execa";
-import * as v from "valibot";
-import { resolveCommand } from "package-manager-detector";
-import { type Config, getConfig } from "../utils/get-config.js";
-import { error, handleError } from "../utils/errors.js";
-import { fetchTree, getItemTargetPath, getRegistryIndex, resolveTree } from "../utils/registry";
-import { UTILS, UTILS_JS } from "../utils/templates.js";
-import { transformImports } from "../utils/transformers.js";
-import * as p from "../utils/prompts.js";
-import { intro, prettifyList } from "../utils/prompt-helpers.js";
-import { getEnvProxy } from "../utils/get-env-proxy.js";
-import { detectPM } from "../utils/auto-detect.js";
-
-const highlight = (msg: string) => color.bold.cyan(msg);
-
-const updateOptionsSchema = v.object({
- all: v.boolean(),
- components: v.optional(v.array(v.string())),
- cwd: v.string(),
- proxy: v.optional(v.string()),
- yes: v.boolean(),
-});
-
-type UpdateOptions = v.InferOutput;
-
-export const update = new Command()
- .command("update")
- .description("update components in your project")
- .argument("[components...]", "name of components")
- .option("-c, --cwd ", "the working directory", process.cwd())
- .option("-a, --all", "update all existing components", false)
- .option("-y, --yes", "skip confirmation prompt", false)
- .option("--proxy ", "fetch components from registry using a proxy", getEnvProxy())
- .action(async (components, opts) => {
- intro();
-
- try {
- const options = v.parse(updateOptionsSchema, {
- components,
- ...opts,
- });
-
- const cwd = path.resolve(options.cwd);
-
- if (!existsSync(cwd)) {
- throw error(`The path ${color.cyan(cwd)} does not exist. Please try again.`);
- }
-
- const config = await getConfig(cwd);
- if (!config) {
- throw error(
- `Configuration file is missing. Please run ${color.green("init")} to create a ${highlight("components.json")} file.`
- );
- }
-
- await runUpdate(cwd, config, options);
-
- p.note(
- `This action ${color.underline("does not")} update your ${highlight("dependencies")} to their ${color.bold("latest")} versions.\n\nConsider updating them as well.`
- );
-
- p.outro(`${color.green("Success!")} Component update completed.`);
- } catch (e) {
- handleError(e);
- }
- });
-
-async function runUpdate(cwd: string, config: Config, options: UpdateOptions) {
- if (options.proxy !== undefined) {
- process.env.HTTP_PROXY = options.proxy;
- p.log.info(`You are using the provided proxy: ${color.green(options.proxy)}`);
- }
-
- const components = options.components;
- const registryIndex = await getRegistryIndex();
-
- const componentDir = path.resolve(config.resolvedPaths.components, "ui");
- if (!existsSync(componentDir)) {
- throw error(`Component directory ${color.cyan(componentDir)} does not exist.`);
- }
-
- // Retrieve existing components in user's project
- const existingComponents: typeof registryIndex = [];
- const files = await fs.readdir(componentDir, {
- withFileTypes: true,
- });
- for (const file of files) {
- if (file.isDirectory()) {
- const component = registryIndex.find((comp) => comp.name === file.name);
- if (component) {
- // is a valid shadcn component
- existingComponents.push(component);
- }
- }
- }
- // add `utils` option to the end
- existingComponents.push({
- name: "utils",
- type: "components:ui",
- files: [],
- dependencies: [],
- registryDependencies: [],
- });
-
- // If the user specifies component args
- let selectedComponents = options.all ? existingComponents : [];
- if (selectedComponents.length === 0 && components !== undefined) {
- // ...only add valid components to the list
- selectedComponents = existingComponents.filter((component) =>
- components.includes(component.name)
- );
- }
-
- // If user didn't specify any component arguments
- if (selectedComponents.length === 0) {
- const selected = await p.multiselect({
- message: "Which components would you like to update?",
- maxItems: 10,
- options: existingComponents.map((component) => ({
- label: component.name,
- value: component,
- hint: component.registryDependencies.length
- ? `also updates: ${component.registryDependencies.join(", ")}`
- : undefined,
- })),
- });
-
- if (p.isCancel(selected)) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
-
- selectedComponents = selected;
- } else {
- const prettyList = prettifyList(selectedComponents.map(({ name }) => name));
- p.log.step(`Components to update:\n${color.gray(prettyList)}`);
- }
-
- if (options.yes === false) {
- const proceed = await p.confirm({
- message: `Ready to update ${highlight("components")}? ${color.gray("(Make sure you have committed your changes before proceeding!)")}`,
- initialValue: true,
- });
-
- if (p.isCancel(proceed) || proceed === false) {
- p.cancel("Operation cancelled.");
- process.exit(0);
- }
- }
-
- // `update utils` - update the utils.(ts|js) file
- if (selectedComponents.find((item) => item.name === "utils")) {
- const extension = config.typescript ? ".ts" : ".js";
- const utilsPath = config.resolvedPaths.utils + extension;
-
- if (!existsSync(utilsPath)) {
- throw error(`Failed to find ${highlight("utils")} at ${color.cyan(utilsPath)}`);
- }
-
- // utils.(ts|js) is not in the registry, it is a template, so we'll just overwrite it
- await fs.writeFile(utilsPath, config.typescript ? UTILS : UTILS_JS);
- }
-
- const tree = await resolveTree(
- registryIndex,
- selectedComponents.map((com) => com.name)
- );
- const payload = (await fetchTree(config, tree)).sort((a, b) => a.name.localeCompare(b.name));
-
- const componentsToRemove: Record = {};
- const dependencies = new Set();
- const tasks: p.Task[] = [];
- for (const item of payload) {
- const targetDir = getItemTargetPath(config, item);
- if (!targetDir) {
- continue;
- }
-
- // Add dependencies to the install list
- item.dependencies.forEach((dep) => dependencies.add(dep));
-
- // Update Components
- tasks.push({
- title: `Updating ${highlight(item.name)}`,
- async task() {
- if (!existsSync(targetDir)) {
- await fs.mkdir(targetDir, { recursive: true });
- }
-
- const componentDir = path.resolve(targetDir, item.name);
- if (!existsSync(componentDir)) {
- await fs.mkdir(componentDir, { recursive: true });
- }
-
- for (const file of item.files) {
- const filePath = path.resolve(targetDir, item.name, file.name);
-
- // Run transformers.
- const content = transformImports(file.content, config);
-
- await fs.writeFile(filePath, content);
- }
-
- const installedFiles = await fs.readdir(componentDir);
- const remoteFiles = item.files.map((file) => file.name);
- const filesToDelete = installedFiles
- .filter((file) => !remoteFiles.includes(file))
- .map((file) => path.resolve(targetDir, item.name, file));
-
- if (filesToDelete.length > 0) {
- componentsToRemove[item.name] = filesToDelete;
- }
-
- const componentPath = path.relative(
- process.cwd(),
- path.resolve(targetDir, item.name)
- );
- return `${highlight(item.name)} updated at ${color.gray(componentPath)}`;
- },
- });
- }
-
- // Install dependencies.
- const pm = await detectPM(cwd, true);
- if (pm) {
- const addCmd = resolveCommand(pm, "add", ["-D", ...dependencies])!;
- tasks.push({
- title: `${highlight(pm)}: Installing dependencies`,
- enabled: dependencies.size > 0,
- async task() {
- await execa(addCmd.command, addCmd.args, { cwd });
- return `Dependencies installed with ${highlight(pm)}`;
- },
- });
- }
-
- await p.tasks(tasks);
-
- for (const [component, files] of Object.entries(componentsToRemove)) {
- p.log.warn(
- `The ${highlight(component)} component does not use the following files:\n${files.map((file) => color.white(`- ${color.gray(path.relative(cwd, file))}`)).join("\n")}`
- );
- }
- if (Object.keys(componentsToRemove).length > 0) {
- p.log.message(color.bold("You may want to delete them."));
- }
-}
diff --git a/packages/cli/src/commands/update/index.ts b/packages/cli/src/commands/update/index.ts
new file mode 100644
index 0000000000..609ea30099
--- /dev/null
+++ b/packages/cli/src/commands/update/index.ts
@@ -0,0 +1,272 @@
+import path from "node:path";
+import process from "node:process";
+import { existsSync, promises as fs } from "node:fs";
+import color from "chalk";
+import { z } from "zod/v4";
+import merge from "deepmerge";
+import { Command } from "commander";
+import { error, handleError } from "../../utils/errors.js";
+import * as cliConfig from "../../utils/get-config.js";
+import { getEnvProxy } from "../../utils/get-env-proxy.js";
+import { cancel, intro, prettifyList } from "../../utils/prompt-helpers.js";
+import * as p from "@clack/prompts";
+import * as registry from "../../utils/registry/index.js";
+import { transformContent, transformCss } from "../../utils/transformers.js";
+import { checkPreconditions } from "../../utils/preconditions.js";
+import { highlight } from "../../utils/utils.js";
+import { installDependencies } from "../../utils/install-deps.js";
+
+const updateOptionsSchema = z.object({
+ all: z.boolean(),
+ components: z.string().array().optional(),
+ cwd: z.string(),
+ proxy: z.string().optional(),
+ yes: z.boolean(),
+});
+
+type UpdateOptions = z.infer