@dinachi/cli 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DinachiUI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import path3 from "path";
11
11
  import { fileURLToPath as fileURLToPath2 } from "url";
12
12
  import ora from "ora";
13
13
  import chalk from "chalk";
14
+ import prompts from "prompts";
14
15
 
15
16
  // src/utils/registry.ts
16
17
  import fs from "fs-extra";
@@ -374,25 +375,14 @@ var LOCK_FILE_MAP = [
374
375
  { file: "yarn.lock", manager: "yarn" },
375
376
  { file: "package-lock.json", manager: "npm" }
376
377
  ];
377
- function walkUpDirectories(startDir) {
378
- const dirs = [];
379
- let current = path2.resolve(startDir);
380
- while (true) {
381
- dirs.push(current);
382
- const parent = path2.dirname(current);
383
- if (parent === current) {
384
- break;
378
+ function detectPackageManager(startDir = process.cwd()) {
379
+ for (const entry of LOCK_FILE_MAP) {
380
+ if (fs2.existsSync(path2.join(startDir, entry.file))) {
381
+ return entry.manager;
385
382
  }
386
- current = parent;
387
383
  }
388
- return dirs;
389
- }
390
- function detectManagerFromPackageJson(startDir) {
391
- for (const dir of walkUpDirectories(startDir)) {
392
- const packageJsonPath = path2.join(dir, "package.json");
393
- if (!fs2.existsSync(packageJsonPath)) {
394
- continue;
395
- }
384
+ const packageJsonPath = path2.join(startDir, "package.json");
385
+ if (fs2.existsSync(packageJsonPath)) {
396
386
  try {
397
387
  const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
398
388
  const value = packageJson.packageManager ?? "";
@@ -401,21 +391,6 @@ function detectManagerFromPackageJson(startDir) {
401
391
  if (value.startsWith("bun@")) return "bun";
402
392
  if (value.startsWith("npm@")) return "npm";
403
393
  } catch {
404
- continue;
405
- }
406
- }
407
- return null;
408
- }
409
- function detectPackageManager(startDir = process.cwd()) {
410
- const packageJsonManager = detectManagerFromPackageJson(startDir);
411
- if (packageJsonManager) {
412
- return packageJsonManager;
413
- }
414
- for (const dir of walkUpDirectories(startDir)) {
415
- for (const entry of LOCK_FILE_MAP) {
416
- if (fs2.existsSync(path2.join(dir, entry.file))) {
417
- return entry.manager;
418
- }
419
394
  }
420
395
  }
421
396
  return "npm";
@@ -699,7 +674,8 @@ async function ensureTW4Plugin(deps, cssFilePath) {
699
674
  let updated;
700
675
  if (importMatch) {
701
676
  updated = content.replace(importMatch[0], `${importMatch[0].trimEnd()}
702
- ${pluginLine}`);
677
+ ${pluginLine}
678
+ `);
703
679
  } else {
704
680
  updated = `${pluginLine}
705
681
  ${content}`;
@@ -769,6 +745,19 @@ ${updatedContent}`;
769
745
  function escapeRegex(value) {
770
746
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
771
747
  }
748
+ function extractExportBlocks(content) {
749
+ const results = [];
750
+ const regex = /export\s+(?:type\s+)?{[^}]*}\s*from\s+['"]\.\/([^'"]+)['"]\s*;?/g;
751
+ let match;
752
+ while ((match = regex.exec(content)) !== null) {
753
+ results.push({ block: match[0].trimEnd(), modulePath: match[1] });
754
+ }
755
+ const starRegex = /export\s+\*\s+from\s+['"]\.\/([^'"]+)['"]\s*;?/g;
756
+ while ((match = starRegex.exec(content)) !== null) {
757
+ results.push({ block: match[0].trimEnd(), modulePath: match[1] });
758
+ }
759
+ return results;
760
+ }
772
761
  async function handleIndexFile(sourcePath, targetPath, allFilesAdded, targetDir) {
773
762
  const templateContent = stripTemplateDirective(await fs3.readFile(sourcePath, "utf-8"));
774
763
  if (!fs3.existsSync(targetPath)) {
@@ -777,28 +766,27 @@ async function handleIndexFile(sourcePath, targetPath, allFilesAdded, targetDir)
777
766
  return;
778
767
  }
779
768
  const existingContent = await fs3.readFile(targetPath, "utf-8");
780
- const exportLines = templateContent.split("\n").map((line) => line.trim()).filter((line) => /^export\s+/.test(line) && /from\s+['"]\.\/[^'"]+['"]/.test(line));
781
- const linesToAppend = [];
782
- for (const line of exportLines) {
783
- const match = line.match(/from\s+['"]\.\/([^'"]+)['"]/);
784
- if (!match) {
785
- continue;
786
- }
787
- const modulePath = match[1];
769
+ const exportBlocks = extractExportBlocks(templateContent);
770
+ const blocksToAppend = [];
771
+ for (const { block, modulePath } of exportBlocks) {
788
772
  const modulePathPattern = new RegExp(`from\\s+['"]\\./${escapeRegex(modulePath)}['"]`);
789
773
  if (modulePathPattern.test(existingContent)) {
790
774
  continue;
791
775
  }
792
- linesToAppend.push(line.endsWith(";") ? line : `${line};`);
776
+ const normalized = block.endsWith(";") ? block : `${block};`;
777
+ blocksToAppend.push(normalized);
793
778
  }
794
- if (linesToAppend.length === 0) {
779
+ if (blocksToAppend.length === 0) {
795
780
  return;
796
781
  }
797
782
  const updatedContent = `${existingContent.trimEnd()}
798
- ${linesToAppend.join("\n")}
783
+ ${blocksToAppend.join("\n")}
799
784
  `;
800
785
  await fs3.writeFile(targetPath, updatedContent);
801
- allFilesAdded.push({ name: "index.ts", path: path3.join(targetDir, "index.ts") });
786
+ const indexPath = path3.join(targetDir, "index.ts");
787
+ if (!allFilesAdded.some((f) => f.path === indexPath)) {
788
+ allFilesAdded.push({ name: "index.ts", path: indexPath });
789
+ }
802
790
  }
803
791
  var addCommand = new Command("add").description("Add a component to your project").argument("[components...]", "Names of the components to add (optional when using --all)").option("-y, --yes", "Skip confirmation prompts").option("-o, --overwrite", "Overwrite existing files").option("-a, --all", "Install all available components").option("--skip-install", "Skip package installation").action(
804
792
  async (componentNames, options) => {
@@ -901,8 +889,19 @@ var addCommand = new Command("add").description("Add a component to your project
901
889
  continue;
902
890
  }
903
891
  if (fs3.existsSync(targetPath) && !options.overwrite) {
904
- spinner.warn(`\u26A0\uFE0F ${file.name} already exists. Use --overwrite to replace it.`);
905
- continue;
892
+ spinner.stop();
893
+ const { overwrite } = await prompts({
894
+ type: "confirm",
895
+ name: "overwrite",
896
+ message: `${file.name} already exists. Overwrite?`,
897
+ initial: false
898
+ });
899
+ if (!overwrite) {
900
+ console.log(` ${chalk.yellow("\u2298")} Skipped ${file.name}`);
901
+ spinner.start();
902
+ continue;
903
+ }
904
+ spinner.start();
906
905
  }
907
906
  const templateContent = stripTemplateDirective(await fs3.readFile(sourcePath, "utf-8"));
908
907
  const rewrittenContent = rewriteTemplateImports(templateContent, targetPath, utilsFilePath, libDir);
@@ -996,7 +995,7 @@ import { Command as Command2 } from "commander";
996
995
  import { execSync as execSync2 } from "child_process";
997
996
  import fs4 from "fs-extra";
998
997
  import path4 from "path";
999
- import prompts from "prompts";
998
+ import prompts2 from "prompts";
1000
999
  import chalk2 from "chalk";
1001
1000
  import ora2 from "ora";
1002
1001
  function normalizeProjectPath(inputPath, projectRoot) {
@@ -1032,7 +1031,16 @@ function detectTailwindMajorVersion2(projectRoot) {
1032
1031
  return 4;
1033
1032
  }
1034
1033
  }
1035
- function getThemeCSS(tailwindMajor, mode) {
1034
+ var DINACHI_THEME_PREFIXES = ["--color-", "--radius-"];
1035
+ function extractPreservedThemeVars(css) {
1036
+ const themeMatch = css.match(/@theme\s+inline\s*\{([^}]*)\}/);
1037
+ if (!themeMatch) return [];
1038
+ return themeMatch[1].split("\n").map((line) => line.trim()).filter((line) => {
1039
+ if (!line || line.startsWith("//") || line.startsWith("/*")) return false;
1040
+ return line.startsWith("--") && !DINACHI_THEME_PREFIXES.some((p) => line.startsWith(p));
1041
+ });
1042
+ }
1043
+ function getThemeCSS(tailwindMajor, mode, preservedThemeVars = []) {
1036
1044
  const lightVars = `:root {
1037
1045
  --background: oklch(0.986 0.0034 145.5499);
1038
1046
  --foreground: oklch(0.1459 0.0497 142.4953);
@@ -1082,6 +1090,7 @@ function getThemeCSS(tailwindMajor, mode) {
1082
1090
  parts.push('@import "tailwindcss";');
1083
1091
  }
1084
1092
  parts.push(lightVars, darkVars);
1093
+ const preservedLines = preservedThemeVars.length > 0 ? "\n" + preservedThemeVars.map((v) => ` ${v}`).join("\n") : "";
1085
1094
  parts.push(`@theme inline {
1086
1095
  --color-background: var(--background);
1087
1096
  --color-foreground: var(--foreground);
@@ -1105,7 +1114,7 @@ function getThemeCSS(tailwindMajor, mode) {
1105
1114
  --radius-sm: calc(var(--radius) - 4px);
1106
1115
  --radius-md: calc(var(--radius) - 2px);
1107
1116
  --radius-lg: var(--radius);
1108
- --radius-xl: calc(var(--radius) + 4px);
1117
+ --radius-xl: calc(var(--radius) + 4px);${preservedLines}
1109
1118
  }`);
1110
1119
  parts.push(`@layer base {
1111
1120
  * {
@@ -1148,9 +1157,10 @@ async function injectThemeCSS(cssFilePath, tailwindMajor) {
1148
1157
  if (existing.includes("--primary:")) {
1149
1158
  return { path: cssFilePath, skipped: true };
1150
1159
  }
1160
+ const preservedVars = tailwindMajor >= 4 ? extractPreservedThemeVars(existing) : [];
1151
1161
  const cleaned = stripConflictingCSS(existing);
1152
- const theme2 = getThemeCSS(tailwindMajor, "append");
1153
- const result = cleaned.length > 0 ? cleaned + "\n\n" + theme2 : getThemeCSS(tailwindMajor, "full");
1162
+ const theme2 = getThemeCSS(tailwindMajor, "append", preservedVars);
1163
+ const result = cleaned.length > 0 ? cleaned + "\n\n" + theme2 : getThemeCSS(tailwindMajor, "full", preservedVars);
1154
1164
  await fs4.writeFile(cssFilePath, result);
1155
1165
  return { path: cssFilePath, updated: true };
1156
1166
  }
@@ -1329,7 +1339,9 @@ async function ensureAtAlias(projectRoot, srcDir, isTypeScript) {
1329
1339
  }
1330
1340
  }
1331
1341
  const aliasTarget = srcDir === "." ? "*" : `${srcDir}/*`;
1332
- const alreadyConfigured = compilerOptions.baseUrl === "." && Array.isArray(paths["@/*"]) && paths["@/*"][0] === aliasTarget;
1342
+ const aliasTargetAlt = srcDir === "." ? "./*" : `./${srcDir}/*`;
1343
+ const existingAlias = paths["@/*"]?.[0];
1344
+ const alreadyConfigured = compilerOptions.baseUrl === "." && existingAlias === aliasTarget || existingAlias === aliasTargetAlt;
1333
1345
  if (alreadyConfigured) {
1334
1346
  return { path: configPath };
1335
1347
  }
@@ -1364,7 +1376,7 @@ var initCommand = new Command2("init").description("Initialize Dinachi UI in you
1364
1376
  const projectConfig = detectProjectType();
1365
1377
  console.log(chalk2.gray(`Detected ${projectConfig.framework} project`));
1366
1378
  console.log();
1367
- const response = await prompts([
1379
+ const response = await prompts2([
1368
1380
  {
1369
1381
  type: "text",
1370
1382
  name: "componentsPath",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dinachi/cli",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "CLI for adding Dinachi UI components to your project",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -15,7 +15,7 @@
15
15
  "license": "MIT",
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "git+https://github.com/dinachi/ui.git"
18
+ "url": "git+https://github.com/raymond-UI/dinachiUI.git"
19
19
  },
20
20
  "homepage": "https://dinachi.dev",
21
21
  "bin": {
@@ -33,7 +33,7 @@ const AlertDialogBackdrop = React.forwardRef<
33
33
  ref={ref}
34
34
  className={cn(
35
35
  "fixed inset-0 z-50 bg-black/80",
36
- "data-[starting-style]:opacity-0 data-[ending-style]:opacity-0",
36
+ "data-starting-style:opacity-0 data-ending-style:opacity-0",
37
37
  "transition-all duration-150",
38
38
  className
39
39
  )}
@@ -50,8 +50,8 @@ const AlertDialogPopup = React.forwardRef<
50
50
  ref={ref}
51
51
  className={cn(
52
52
  "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg sm:rounded-lg",
53
- "data-[starting-style]:scale-95 data-[starting-style]:opacity-0",
54
- "data-[ending-style]:scale-95 data-[ending-style]:opacity-0",
53
+ "data-starting-style:scale-95 data-starting-style:opacity-0",
54
+ "data-ending-style:scale-95 data-ending-style:opacity-0",
55
55
  "transition-all duration-150",
56
56
  className
57
57
  )}
@@ -71,24 +71,30 @@ AutocompleteClear.displayName = "AutocompleteClear"
71
71
 
72
72
  const AutocompleteContent = React.forwardRef<
73
73
  React.ComponentRef<typeof AutocompletePrimitive.Popup>,
74
- React.ComponentPropsWithoutRef<typeof AutocompletePrimitive.Popup>
75
- >(({ className, ...props }, ref) => (
76
- <AutocompletePortal>
74
+ React.ComponentPropsWithoutRef<typeof AutocompletePrimitive.Popup> & {
75
+ readonly portal?: boolean
76
+ }
77
+ >(({ className, portal = true, ...props }, ref) => {
78
+ const content = (
77
79
  <AutocompletePrimitive.Positioner sideOffset={4}>
78
80
  <AutocompletePrimitive.Popup
79
81
  ref={ref}
80
82
  className={cn(
81
- "relative z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
82
- "origin-[var(--transform-origin)] outline-none",
83
- "data-[starting-style]:animate-in data-[starting-style]:fade-in-0 data-[starting-style]:zoom-in-95",
84
- "data-[ending-style]:animate-out data-[ending-style]:fade-out-0 data-[ending-style]:zoom-out-95",
83
+ "relative z-50 min-w-48 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
84
+ "origin-(--transform-origin) outline-none",
85
+ "data-starting-style:animate-in data-starting-style:fade-in-0 data-starting-style:zoom-in-95",
86
+ "data-ending-style:animate-out data-ending-style:fade-out-0 data-ending-style:zoom-out-95",
85
87
  className
86
88
  )}
87
89
  {...props}
88
90
  />
89
91
  </AutocompletePrimitive.Positioner>
90
- </AutocompletePortal>
91
- ))
92
+ )
93
+
94
+ if (!portal) return content
95
+
96
+ return <AutocompletePortal>{content}</AutocompletePortal>
97
+ })
92
98
  AutocompleteContent.displayName = "AutocompleteContent"
93
99
 
94
100
  const AutocompleteList = React.forwardRef<
@@ -113,8 +119,8 @@ const AutocompleteItem = React.forwardRef<
113
119
  ref={ref}
114
120
  className={cn(
115
121
  "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
116
- "data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
117
- "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground",
123
+ "data-disabled:pointer-events-none data-disabled:opacity-50",
118
124
  inset && "pl-8",
119
125
  className
120
126
  )}
@@ -151,7 +157,7 @@ const AutocompleteEmpty = React.forwardRef<
151
157
  >(({ className, ...props }, ref) => (
152
158
  <AutocompletePrimitive.Empty
153
159
  ref={ref}
154
- className={cn("px-2 py-6 text-center text-sm text-muted-foreground", className)}
160
+ className={cn("p-2 text-center text-sm text-muted-foreground", className)}
155
161
  {...props}
156
162
  />
157
163
  ))
@@ -39,11 +39,13 @@ export interface ButtonProps
39
39
  }
40
40
 
41
41
  const Button = React.forwardRef<HTMLElement, ButtonProps>(
42
- ({ className, variant, size, ...props }, ref) => {
42
+ ({ className, variant, size, render, nativeButton, ...props }, ref) => {
43
43
  return (
44
44
  <BaseButton
45
45
  className={cn(buttonVariants({ variant, size, className }))}
46
46
  ref={ref}
47
+ render={render}
48
+ nativeButton={render ? (nativeButton ?? false) : nativeButton}
47
49
  {...props}
48
50
  />
49
51
  )
@@ -71,24 +71,30 @@ ComboboxClear.displayName = "ComboboxClear"
71
71
 
72
72
  const ComboboxContent = React.forwardRef<
73
73
  React.ComponentRef<typeof ComboboxPrimitive.Popup>,
74
- React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.Popup>
75
- >(({ className, ...props }, ref) => (
76
- <ComboboxPortal>
74
+ React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.Popup> & {
75
+ readonly portal?: boolean
76
+ }
77
+ >(({ className, portal = true, ...props }, ref) => {
78
+ const content = (
77
79
  <ComboboxPrimitive.Positioner sideOffset={4}>
78
80
  <ComboboxPrimitive.Popup
79
81
  ref={ref}
80
82
  className={cn(
81
- "relative z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
82
- "origin-[var(--transform-origin)] outline-none",
83
- "data-[starting-style]:animate-in data-[starting-style]:fade-in-0 data-[starting-style]:zoom-in-95",
84
- "data-[ending-style]:animate-out data-[ending-style]:fade-out-0 data-[ending-style]:zoom-out-95",
83
+ "relative z-50 min-w-48 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
84
+ "origin-(--transform-origin) outline-none",
85
+ "data-starting-style:animate-in data-starting-style:fade-in-0 data-starting-style:zoom-in-95",
86
+ "data-ending-style:animate-out data-ending-style:fade-out-0 data-ending-style:zoom-out-95",
85
87
  className
86
88
  )}
87
89
  {...props}
88
90
  />
89
91
  </ComboboxPrimitive.Positioner>
90
- </ComboboxPortal>
91
- ))
92
+ )
93
+
94
+ if (!portal) return content
95
+
96
+ return <ComboboxPortal>{content}</ComboboxPortal>
97
+ })
92
98
  ComboboxContent.displayName = "ComboboxContent"
93
99
 
94
100
  const ComboboxList = React.forwardRef<
@@ -113,8 +119,8 @@ const ComboboxItem = React.forwardRef<
113
119
  ref={ref}
114
120
  className={cn(
115
121
  "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none",
116
- "data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
117
- "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground",
123
+ "data-disabled:pointer-events-none data-disabled:opacity-50",
118
124
  inset && "pl-10",
119
125
  className
120
126
  )}
@@ -156,7 +162,7 @@ const ComboboxEmpty = React.forwardRef<
156
162
  >(({ className, ...props }, ref) => (
157
163
  <ComboboxPrimitive.Empty
158
164
  ref={ref}
159
- className={cn("px-2 py-6 text-center text-sm text-muted-foreground", className)}
165
+ className={cn("p-2 text-center text-sm text-muted-foreground", className)}
160
166
  {...props}
161
167
  />
162
168
  ))
@@ -30,8 +30,8 @@ const MenuTrigger = React.forwardRef<
30
30
  "inline-flex h-10 items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium",
31
31
  "hover:bg-accent hover:text-accent-foreground",
32
32
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
33
- "data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground",
34
- "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
33
+ "data-popup-open:bg-accent data-popup-open:text-accent-foreground",
34
+ "data-disabled:pointer-events-none data-disabled:opacity-50",
35
35
  className
36
36
  )}
37
37
  {...props}
@@ -75,10 +75,10 @@ const MenuContent = React.forwardRef<
75
75
  <MenuPrimitive.Popup
76
76
  ref={ref}
77
77
  className={cn(
78
- "z-50 min-w-[10rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
79
- "origin-[var(--transform-origin)] outline-none",
80
- "data-[starting-style]:animate-in data-[starting-style]:fade-in-0 data-[starting-style]:zoom-in-95",
81
- "data-[ending-style]:animate-out data-[ending-style]:fade-out-0 data-[ending-style]:zoom-out-95",
78
+ "z-50 min-w-40 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
79
+ "origin-(--transform-origin) outline-none",
80
+ "data-starting-style:animate-in data-starting-style:fade-in-0 data-starting-style:zoom-in-95",
81
+ "data-ending-style:animate-out data-ending-style:fade-out-0 data-ending-style:zoom-out-95",
82
82
  className
83
83
  )}
84
84
  {...props}
@@ -99,8 +99,8 @@ const MenuItem = React.forwardRef<
99
99
  className={cn(
100
100
  "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
101
101
  "focus:bg-accent focus:text-accent-foreground",
102
- "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
103
- "data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
102
+ "data-disabled:pointer-events-none data-disabled:opacity-50",
103
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground",
104
104
  inset && "pl-8",
105
105
  className
106
106
  )}
@@ -118,8 +118,8 @@ const MenuCheckboxItem = React.forwardRef<
118
118
  className={cn(
119
119
  "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none",
120
120
  "focus:bg-accent focus:text-accent-foreground",
121
- "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122
- "data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
121
+ "data-disabled:pointer-events-none data-disabled:opacity-50",
122
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground",
123
123
  className
124
124
  )}
125
125
  checked={checked}
@@ -150,14 +150,14 @@ const MenuRadioItem = React.forwardRef<
150
150
  className={cn(
151
151
  "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none",
152
152
  "focus:bg-accent focus:text-accent-foreground",
153
- "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
154
- "data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
153
+ "data-disabled:pointer-events-none data-disabled:opacity-50",
154
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground",
155
155
  className
156
156
  )}
157
157
  {...props}
158
158
  >
159
159
  <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
160
- <Circle className="h-2 w-2 fill-current data-[checked]:block data-[unchecked]:hidden" />
160
+ <Circle className="h-2 w-2 fill-current data-checked:block data-unchecked:hidden" />
161
161
  </span>
162
162
  {children}
163
163
  </MenuPrimitive.RadioItem>
@@ -226,9 +226,9 @@ const MenuSubTrigger = React.forwardRef<
226
226
  className={cn(
227
227
  "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
228
228
  "focus:bg-accent focus:text-accent-foreground",
229
- "data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
230
- "data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground",
231
- "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
229
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground",
230
+ "data-popup-open:bg-accent data-popup-open:text-accent-foreground",
231
+ "data-disabled:pointer-events-none data-disabled:opacity-50",
232
232
  inset && "pl-8",
233
233
  className
234
234
  )}
@@ -249,10 +249,10 @@ const MenuSubContent = React.forwardRef<
249
249
  <MenuPrimitive.Popup
250
250
  ref={ref}
251
251
  className={cn(
252
- "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
253
- "origin-[var(--transform-origin)] outline-none",
254
- "data-[starting-style]:animate-in data-[starting-style]:fade-in-0 data-[starting-style]:zoom-in-95",
255
- "data-[ending-style]:animate-out data-[ending-style]:fade-out-0 data-[ending-style]:zoom-out-95",
252
+ "z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
253
+ "origin-(--transform-origin) outline-none",
254
+ "data-starting-style:animate-in data-starting-style:fade-in-0 data-starting-style:zoom-in-95",
255
+ "data-ending-style:animate-out data-ending-style:fade-out-0 data-ending-style:zoom-out-95",
256
256
  className
257
257
  )}
258
258
  {...props}
@@ -9,6 +9,7 @@ export {
9
9
  MenubarCheckboxItem,
10
10
  MenubarRadioGroup,
11
11
  MenubarRadioItem,
12
+ MenubarGroup,
12
13
  MenubarLabel,
13
14
  MenubarSeparator,
14
15
  MenubarShortcut,
@@ -175,6 +175,14 @@ const MenubarRadioItem = React.forwardRef<
175
175
  ))
176
176
  MenubarRadioItem.displayName = "MenubarRadioItem"
177
177
 
178
+ const MenubarGroup = React.forwardRef<
179
+ React.ComponentRef<typeof Menu.Group>,
180
+ React.ComponentProps<typeof Menu.Group>
181
+ >(({ className, ...props }, ref) => (
182
+ <Menu.Group ref={ref} className={cn(className)} {...props} />
183
+ ))
184
+ MenubarGroup.displayName = "MenubarGroup"
185
+
178
186
  const MenubarLabel = React.forwardRef<
179
187
  React.ComponentRef<typeof Menu.GroupLabel>,
180
188
  React.ComponentProps<typeof Menu.GroupLabel> & {
@@ -293,6 +301,7 @@ export {
293
301
  MenubarCheckboxItem,
294
302
  MenubarRadioGroup,
295
303
  MenubarRadioItem,
304
+ MenubarGroup,
296
305
  MenubarLabel,
297
306
  MenubarSeparator,
298
307
  MenubarShortcut,
@@ -17,10 +17,11 @@ const PopoverContent = React.forwardRef<
17
17
  readonly sideOffset?: number
18
18
  readonly align?: "start" | "center" | "end"
19
19
  readonly side?: "top" | "bottom" | "left" | "right"
20
+ readonly portal?: boolean
20
21
  }
21
- >(({ className, align = "center", side = "bottom", sideOffset = 8, ...props }, ref) => (
22
- <PopoverPrimitive.Portal>
23
- <PopoverPrimitive.Positioner
22
+ >(({ className, align = "center", side = "bottom", sideOffset = 8, portal = true, ...props }, ref) => {
23
+ const content = (
24
+ <PopoverPrimitive.Positioner
24
25
  align={align}
25
26
  side={side}
26
27
  sideOffset={sideOffset}
@@ -29,17 +30,21 @@ const PopoverContent = React.forwardRef<
29
30
  ref={ref}
30
31
  className={cn(
31
32
  "z-50 rounded-lg border bg-popover px-6 py-4 text-popover-foreground shadow-lg outline-none",
32
- "origin-[var(--transform-origin)]",
33
- "data-[starting-style]:scale-90 data-[starting-style]:opacity-0",
34
- "data-[ending-style]:scale-90 data-[ending-style]:opacity-0",
33
+ "origin-(--transform-origin)",
34
+ "data-starting-style:scale-90 data-starting-style:opacity-0",
35
+ "data-ending-style:scale-90 data-ending-style:opacity-0",
35
36
  "transition-[transform,opacity] duration-150",
36
37
  className
37
38
  )}
38
39
  {...props}
39
40
  />
40
41
  </PopoverPrimitive.Positioner>
41
- </PopoverPrimitive.Portal>
42
- ))
42
+ )
43
+
44
+ if (!portal) return content
45
+
46
+ return <PopoverPrimitive.Portal>{content}</PopoverPrimitive.Portal>
47
+ })
43
48
  PopoverContent.displayName = "PopoverContent"
44
49
 
45
50
  // Optimized PopoverArrow
@@ -113,7 +118,7 @@ const PopoverBackdrop = React.forwardRef<
113
118
  ref={ref}
114
119
  className={cn(
115
120
  "fixed inset-0 z-40 bg-black/50",
116
- "data-[starting-style]:opacity-0 data-[ending-style]:opacity-0",
121
+ "data-starting-style:opacity-0 data-ending-style:opacity-0",
117
122
  "transition-opacity duration-150",
118
123
  className
119
124
  )}
@@ -21,7 +21,7 @@ const Radio = React.forwardRef<
21
21
  <RadioPrimitive.Root
22
22
  ref={ref}
23
23
  className={cn(
24
- "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background",
24
+ "flex items-center justify-center aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background",
25
25
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
26
26
  "disabled:cursor-not-allowed disabled:opacity-50",
27
27
  className
@@ -39,9 +39,10 @@ const SelectContent = React.forwardRef<
39
39
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Popup> & {
40
40
  readonly position?: "item-aligned" | "popper"
41
41
  readonly sideOffset?: number
42
+ readonly portal?: boolean
42
43
  }
43
- >(({ className, children, position = "popper", sideOffset = 4, ...props }, ref) => (
44
- <SelectPrimitive.Portal>
44
+ >(({ className, children, position = "popper", sideOffset = 4, portal = true, ...props }, ref) => {
45
+ const content = (
45
46
  <SelectPrimitive.Positioner
46
47
  sideOffset={sideOffset}
47
48
  alignItemWithTrigger={position === "item-aligned"}
@@ -49,7 +50,7 @@ const SelectContent = React.forwardRef<
49
50
  <SelectPrimitive.Popup
50
51
  ref={ref}
51
52
  className={cn(
52
- "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
53
+ "relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
53
54
  "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
54
55
  "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
55
56
  position === "popper" &&
@@ -65,8 +66,12 @@ const SelectContent = React.forwardRef<
65
66
  <SelectScrollDownArrow />
66
67
  </SelectPrimitive.Popup>
67
68
  </SelectPrimitive.Positioner>
68
- </SelectPrimitive.Portal>
69
- ))
69
+ )
70
+
71
+ if (!portal) return content
72
+
73
+ return <SelectPrimitive.Portal>{content}</SelectPrimitive.Portal>
74
+ })
70
75
  SelectContent.displayName = "SelectContent"
71
76
 
72
77
  // Add scroll arrows for better UX with large lists
@@ -143,7 +148,7 @@ const SelectItem = React.forwardRef<
143
148
  ref={ref}
144
149
  className={cn(
145
150
  "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-2 text-sm outline-none",
146
- "focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
151
+ "focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
147
152
  // Dynamic padding based on indicator presence and position
148
153
  showIndicator && isLeftIndicator && "pl-8",
149
154
  showIndicator && isRightIndicator && "pr-8",