feat: add --pointer option (#10488)

This commit is contained in:
shadcn
2026-04-25 14:24:25 +04:00
committed by GitHub
parent f785bfab44
commit eb6e783fb3
19 changed files with 312 additions and 24 deletions

View File

@@ -14,7 +14,7 @@ import {
import { ensureRegistriesInConfig } from "@/src/utils/registries"
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
import { runInit } from "./init"
import { applyInitUrlOptions, initOptionsSchema, runInit } from "./init"
vi.mock("@/src/preflights/preflight-init", () => ({
preFlightInit: vi.fn(),
@@ -252,3 +252,32 @@ describe("runInit", () => {
expect(postInit).not.toHaveBeenCalled()
})
})
describe("init options", () => {
it("parses pointer flags", () => {
const result = initOptionsSchema.parse({
...createInitOptions("/tmp/project"),
pointer: true,
})
expect(result.pointer).toBe(true)
})
it("applies pointer to raw init urls", () => {
const url = applyInitUrlOptions(
new URL("https://ui.shadcn.com/init?preset=a0"),
{ pointer: true }
)
expect(url.searchParams.get("pointer")).toBe("true")
})
it("removes pointer from raw init urls when disabled", () => {
const url = applyInitUrlOptions(
new URL("https://ui.shadcn.com/init?preset=a0&pointer=true"),
{ pointer: false }
)
expect(url.searchParams.has("pointer")).toBe(false)
})
})

View File

@@ -76,6 +76,7 @@ export const initOptionsSchema = z.object({
isNewProject: z.boolean().default(false),
cssVariables: z.boolean().default(true),
rtl: z.boolean().optional(),
pointer: z.boolean().optional(),
base: z.enum(["radix", "base"]).optional(),
template: z.string().optional(),
monorepo: z.boolean().optional(),
@@ -94,6 +95,25 @@ export const initOptionsSchema = z.object({
iconLibrary: z.string().optional(),
})
export function applyInitUrlOptions(
url: URL,
options: Pick<z.infer<typeof initOptionsSchema>, "rtl" | "pointer">
) {
if (options.rtl) {
url.searchParams.set("rtl", "true")
} else if (options.rtl === false) {
url.searchParams.delete("rtl")
}
if (options.pointer) {
url.searchParams.set("pointer", "true")
} else if (options.pointer === false) {
url.searchParams.delete("pointer")
}
return url
}
export const init = new Command()
.name("init")
.alias("create")
@@ -125,6 +145,8 @@ export const init = new Command()
.option("--no-css-variables", "do not use css variables for theming.")
.option("--rtl", "enable RTL support.")
.option("--no-rtl", "disable RTL support.")
.option("--pointer", "enable pointer cursor for buttons.")
.option("--no-pointer", "disable pointer cursor for buttons.")
.option("--reinstall", "re-install existing UI components.")
.option("--no-reinstall", "do not re-install existing UI components.")
.action(async (components, opts) => {
@@ -369,6 +391,7 @@ export const init = new Command()
rtl: options.rtl ?? false,
template: options.template,
base: options.base!,
pointer: options.pointer,
})
components = [result.url, ...components]
presetBase = result.base
@@ -379,11 +402,7 @@ export const init = new Command()
if (isUrl(presetArg)) {
const url = new URL(presetArg)
if (options.rtl) {
url.searchParams.set("rtl", "true")
} else if (options.rtl === false) {
url.searchParams.delete("rtl")
}
applyInitUrlOptions(url, options)
if (url.pathname === "/init" && presetArg.startsWith(SHADCN_URL)) {
url.searchParams.set("track", "1")
}
@@ -406,7 +425,11 @@ export const init = new Command()
base: "radix",
rtl: options.rtl ?? false,
},
{ template: options.template, preset: presetArg }
{
template: options.template,
preset: presetArg,
pointer: options.pointer,
}
)
presetBase = undefined
} else {
@@ -420,7 +443,7 @@ export const init = new Command()
base: options.base ?? "radix",
rtl: options.rtl ?? preset.rtl,
},
{ template: options.template }
{ template: options.template, pointer: options.pointer }
)
presetBase = undefined
}
@@ -459,7 +482,7 @@ export const init = new Command()
base: resolvedBase,
rtl: options.rtl ?? false,
},
{ template: options.template }
{ template: options.template, pointer: options.pointer }
)
components = [initUrl, ...components]
}

View File

@@ -31,9 +31,14 @@ describe("createPresetUrl", () => {
})
it("should append search params when provided", () => {
const url = resolveCreateUrl({ rtl: true, template: "next" })
const url = resolveCreateUrl({
rtl: true,
pointer: true,
template: "next",
})
const parsed = new URL(url)
expect(parsed.searchParams.get("rtl")).toBe("true")
expect(parsed.searchParams.get("pointer")).toBe("true")
expect(parsed.searchParams.get("template")).toBe("next")
})
})
@@ -117,4 +122,23 @@ describe("buildInitUrl", () => {
const parsed = new URL(url)
expect(parsed.searchParams.has("preset")).toBe(false)
})
it("should include pointer when enabled", () => {
const url = resolveInitUrl(mockPreset, { pointer: true })
const parsed = new URL(url)
expect(parsed.searchParams.get("pointer")).toBe("true")
})
it("should not include pointer when disabled", () => {
const url = resolveInitUrl(mockPreset, { pointer: false })
const parsed = new URL(url)
expect(parsed.searchParams.has("pointer")).toBe(false)
})
it("should include pointer with preset codes", () => {
const url = resolveInitUrl(mockPreset, { preset: "a0", pointer: true })
const parsed = new URL(url)
expect(parsed.searchParams.get("preset")).toBe("a0")
expect(parsed.searchParams.get("pointer")).toBe("true")
})
})

View File

@@ -132,11 +132,12 @@ export function resolveCreateUrl(
command: "create" | "init"
template: string
rtl: boolean
pointer: boolean
base: string
}>
) {
const url = new URL(`${SHADCN_URL}/create`)
const { rtl, ...params } = searchParams ?? {}
const { rtl, pointer, ...params } = searchParams ?? {}
for (const [key, value] of Object.entries(params)) {
if (value !== undefined) {
@@ -149,6 +150,10 @@ export function resolveCreateUrl(
url.searchParams.set("rtl", "true")
}
if (pointer) {
url.searchParams.set("pointer", "true")
}
return url.toString()
}
@@ -195,7 +200,12 @@ export function resolveInitUrl(
menuColor: string
radius: string
},
options?: { template?: string; preset?: string; only?: string }
options?: {
template?: string
preset?: string
only?: string
pointer?: boolean
}
) {
const params = new URLSearchParams({
base: preset.base,
@@ -232,6 +242,10 @@ export function resolveInitUrl(
params.set("only", options.only)
}
if (options?.pointer) {
params.set("pointer", "true")
}
// Signal the server to record this init run.
params.set("track", "1")
@@ -256,6 +270,7 @@ export async function promptForPreset(options: {
rtl: boolean
base: string
template?: string
pointer?: boolean
}) {
const presets = Object.entries(DEFAULT_PRESETS)
@@ -285,6 +300,7 @@ export async function promptForPreset(options: {
const createUrl = resolveCreateUrl({
command: "init",
rtl: options.rtl,
pointer: options.pointer,
base: options.base,
...(options.template && { template: options.template }),
})
@@ -308,6 +324,7 @@ export async function promptForPreset(options: {
{ ...preset, base: options.base, rtl: options.rtl },
{
template: options.template,
pointer: options.pointer,
}
),
base: options.base,