shapes-ui 0.4.2 → 0.6.0

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.
Files changed (180) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +47 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +1 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.yml +31 -0
  4. package/.github/pull_request_template.md +14 -0
  5. package/.github/workflows/pr-preview.yml +75 -0
  6. package/.github/workflows/release.yml +8 -0
  7. package/CHANGELOG.md +30 -0
  8. package/CODE_OF_CONDUCT.md +41 -0
  9. package/CONTRIBUTING.md +52 -0
  10. package/README.md +18 -0
  11. package/SECURITY.md +0 -0
  12. package/content/components/accordion.mdx +13 -0
  13. package/content/components/alert-dialog.mdx +34 -0
  14. package/content/components/autocomplete.mdx +62 -0
  15. package/content/components/avatar.mdx +11 -0
  16. package/content/components/button.mdx +8 -0
  17. package/content/components/checkbox.mdx +11 -0
  18. package/content/components/collapsible.mdx +11 -0
  19. package/content/components/combobox.mdx +33 -0
  20. package/content/components/context-menu.mdx +29 -0
  21. package/content/components/dialog.mdx +33 -0
  22. package/content/components/drawer.mdx +38 -0
  23. package/content/components/field.mdx +23 -2
  24. package/content/components/fieldset.mdx +11 -1
  25. package/content/components/form.mdx +8 -0
  26. package/content/components/input.mdx +4 -0
  27. package/content/components/menu.mdx +27 -0
  28. package/content/components/menubar.mdx +31 -0
  29. package/content/components/meter.mdx +14 -0
  30. package/content/components/navigation-menu.mdx +28 -0
  31. package/content/components/number-field.mdx +25 -0
  32. package/content/components/popover.mdx +22 -0
  33. package/content/components/preview-card.mdx +14 -2
  34. package/content/components/progress.mdx +15 -1
  35. package/content/components/radio.mdx +31 -0
  36. package/content/components/scroll-area.mdx +23 -0
  37. package/content/components/select.mdx +57 -0
  38. package/content/components/separator.mdx +29 -0
  39. package/content/components/slider.mdx +52 -0
  40. package/content/components/switch.mdx +30 -0
  41. package/content/components/tabs.mdx +47 -0
  42. package/content/components/toast.mdx +70 -0
  43. package/content/components/toggle-group.mdx +37 -0
  44. package/content/components/toggle.mdx +46 -2
  45. package/content/components/toolbar.mdx +48 -0
  46. package/content/components/tooltip.mdx +38 -0
  47. package/content/docs/installation.mdx +30 -0
  48. package/content-collections.ts +65 -1
  49. package/dist/cli.js +947 -101
  50. package/examples/__index.tsx +320 -66
  51. package/examples/autocomplete-align.tsx +39 -0
  52. package/examples/autocomplete-controlled.tsx +44 -0
  53. package/examples/autocomplete-groups.tsx +65 -0
  54. package/examples/autocomplete-no-clear.tsx +40 -0
  55. package/examples/avatar-demo.tsx +3 -3
  56. package/examples/checkbox-demo.tsx +1 -1
  57. package/examples/checkbox-form.tsx +3 -3
  58. package/examples/field-custom-control.tsx +33 -9
  59. package/examples/form-demo.tsx +5 -10
  60. package/examples/input-group-with-button.tsx +1 -1
  61. package/examples/menu-advanced.tsx +1 -3
  62. package/examples/menu-align.tsx +19 -16
  63. package/examples/menu-checkbox.tsx +2 -3
  64. package/examples/menu-demo.tsx +1 -3
  65. package/examples/menu-group.tsx +1 -3
  66. package/examples/menu-radio.tsx +1 -3
  67. package/examples/menu-submenu.tsx +2 -3
  68. package/examples/meter-demo.tsx +10 -2
  69. package/examples/meter-flip.tsx +8 -8
  70. package/examples/meter-no-label.tsx +9 -2
  71. package/examples/meter-no-value.tsx +7 -8
  72. package/examples/radio-card.tsx +28 -0
  73. package/examples/radio-demo.tsx +19 -1
  74. package/examples/radio-description.tsx +26 -0
  75. package/examples/radio-orientation.tsx +21 -0
  76. package/examples/select-alignment.tsx +51 -0
  77. package/examples/select-demo.tsx +36 -1
  78. package/examples/select-disabled.tsx +38 -0
  79. package/examples/select-groups.tsx +54 -0
  80. package/examples/select-invalid.tsx +41 -0
  81. package/examples/select-scrollable.tsx +112 -0
  82. package/examples/separator-demo.tsx +13 -0
  83. package/examples/separator-horizontal.tsx +18 -0
  84. package/examples/slider-controlled.tsx +28 -0
  85. package/examples/slider-demo.tsx +3 -1
  86. package/examples/slider-disabled.tsx +7 -0
  87. package/examples/slider-edge.tsx +13 -0
  88. package/examples/slider-multiple.tsx +7 -0
  89. package/examples/slider-range.tsx +5 -0
  90. package/examples/slider-vertical.tsx +10 -0
  91. package/examples/switch-demo.tsx +19 -1
  92. package/examples/switch-disabled.tsx +20 -0
  93. package/examples/switch-sizes.tsx +24 -0
  94. package/examples/switch-with-label.tsx +16 -0
  95. package/examples/tabs-demo.tsx +14 -1
  96. package/examples/tabs-disabled.tsx +21 -0
  97. package/examples/tabs-line.tsx +18 -0
  98. package/examples/tabs-vertical.tsx +13 -0
  99. package/examples/toast-action.tsx +39 -0
  100. package/examples/toast-anchored.tsx +36 -0
  101. package/examples/toast-demo.tsx +27 -1
  102. package/examples/toast-positions.tsx +54 -0
  103. package/examples/toast-promise.tsx +51 -0
  104. package/examples/toast-stacked.tsx +30 -0
  105. package/examples/toast-timeout.tsx +43 -0
  106. package/examples/toast-update.tsx +38 -0
  107. package/examples/toast-variants.tsx +54 -0
  108. package/examples/toggle-controlled.tsx +20 -0
  109. package/examples/toggle-demo.tsx +7 -51
  110. package/examples/toggle-group-demo.tsx +19 -0
  111. package/examples/toggle-group-multiple.tsx +19 -0
  112. package/examples/toggle-icon-fill.tsx +12 -0
  113. package/examples/toolbar-demo.tsx +45 -21
  114. package/examples/toolbar-input-link.tsx +35 -0
  115. package/examples/toolbar-menu.tsx +53 -0
  116. package/examples/tooltip-demo.tsx +48 -0
  117. package/examples/tooltip-positions.tsx +60 -0
  118. package/package.json +19 -18
  119. package/public/base-ui.svg +1 -0
  120. package/public/r/drawer.json +1 -1
  121. package/public/r/field.json +1 -1
  122. package/public/r/meter.json +1 -1
  123. package/public/r/number-field.json +1 -1
  124. package/public/r/progress.json +1 -1
  125. package/public/r/radio.json +1 -1
  126. package/public/r/select.json +1 -1
  127. package/public/r/slider.json +1 -1
  128. package/public/r/switch.json +1 -1
  129. package/public/r/tabs.json +1 -1
  130. package/public/r/toast.json +2 -1
  131. package/public/r/toggle.json +1 -1
  132. package/public/r/toolbar.json +1 -1
  133. package/public/r/tooltip.json +15 -0
  134. package/src/assets/base-ui.svg +1 -0
  135. package/src/commands/add.ts +79 -38
  136. package/src/commands/cli.ts +50 -3
  137. package/src/commands/create.ts +262 -0
  138. package/src/commands/init.ts +45 -12
  139. package/src/commands/palette.ts +55 -0
  140. package/src/components/docs/layout/footer.tsx +2 -2
  141. package/src/components/docs/layout/header.tsx +7 -19
  142. package/src/components/docs/layout/mobile-menu.tsx +26 -78
  143. package/src/components/docs/layout/nav-list.tsx +27 -21
  144. package/src/components/docs/layout/page-header.tsx +52 -7
  145. package/src/components/docs/layout/split-layout.tsx +11 -9
  146. package/src/components/docs/layout/table-of-content.tsx +145 -0
  147. package/src/components/docs/markdown/components.tsx +142 -29
  148. package/src/components/docs/markdown/copy-button.tsx +41 -0
  149. package/src/components/docs/markdown/installation-block.tsx +5 -24
  150. package/src/components/docs/markdown/render-preview.tsx +1 -1
  151. package/src/components/ui/badge.tsx +1 -1
  152. package/src/components/ui/button-group.tsx +1 -1
  153. package/src/components/ui/checkbox.tsx +1 -1
  154. package/src/components/ui/drawer.tsx +1 -1
  155. package/src/components/ui/field.tsx +9 -28
  156. package/src/components/ui/form.tsx +1 -1
  157. package/src/components/ui/meter.tsx +12 -26
  158. package/src/components/ui/number-field.tsx +1 -1
  159. package/src/components/ui/radio.tsx +32 -19
  160. package/src/components/ui/scroll-area.tsx +11 -2
  161. package/src/components/ui/select.tsx +6 -6
  162. package/src/components/ui/slider.tsx +8 -5
  163. package/src/components/ui/switch.tsx +13 -17
  164. package/src/components/ui/tabs.tsx +23 -10
  165. package/src/components/ui/toast.tsx +190 -29
  166. package/src/components/ui/toggle.tsx +1 -1
  167. package/src/components/ui/toolbar.tsx +17 -4
  168. package/src/components/ui/tooltip.tsx +54 -0
  169. package/src/lib/docs-headings.ts +72 -0
  170. package/src/routeTree.gen.ts +60 -3
  171. package/src/routes/__root.tsx +3 -5
  172. package/src/routes/components.$slug.tsx +20 -4
  173. package/src/routes/docs.$slug.tsx +78 -0
  174. package/src/routes/docs.tsx +15 -0
  175. package/src/styles/styles.css +1 -1
  176. package/src/utils/cli-utils.ts +8 -8
  177. package/src/utils/dependency-installer.ts +30 -0
  178. package/src/utils/package-manager.ts +4 -4
  179. package/src/utils/palette.ts +666 -0
  180. package/src/utils/schema.ts +6 -0
@@ -0,0 +1,72 @@
1
+ export type TocHeading = {
2
+ id: string;
3
+ title: string;
4
+ level: number;
5
+ };
6
+
7
+ export function slugifyHeading(value: string) {
8
+ return value
9
+ .trim()
10
+ .toLowerCase()
11
+ .replace(/`/g, "")
12
+ .replace(/\[(.*?)\]\((.*?)\)/g, "$1")
13
+ .replace(/<[^>]+>/g, "")
14
+ .replace(/&[a-z0-9#]+;/gi, "")
15
+ .replace(/[^a-z0-9\s-]/g, "")
16
+ .replace(/\s+/g, "-")
17
+ .replace(/-+/g, "-")
18
+ .replace(/^-|-$/g, "");
19
+ }
20
+
21
+ function sanitizeHeadingTitle(value: string) {
22
+ return value
23
+ .replace(/\s+#+\s*$/, "")
24
+ .replace(/`/g, "")
25
+ .replace(/\[(.*?)\]\((.*?)\)/g, "$1")
26
+ .replace(/[*_~]/g, "")
27
+ .replace(/<[^>]+>/g, "")
28
+ .replace(/&[a-z0-9#]+;/gi, "")
29
+ .trim();
30
+ }
31
+
32
+ export function extractHeadingsFromMarkdown(content: string): TocHeading[] {
33
+ const headings: TocHeading[] = [];
34
+ const lines = content.split("\n");
35
+ let inCodeFence = false;
36
+
37
+ for (const rawLine of lines) {
38
+ const line = rawLine.trim();
39
+
40
+ if (line.startsWith("```")) {
41
+ inCodeFence = !inCodeFence;
42
+ continue;
43
+ }
44
+
45
+ if (inCodeFence) {
46
+ continue;
47
+ }
48
+
49
+ const match = line.match(/^(#{1,6})\s+(.+)$/);
50
+
51
+ if (!match) {
52
+ continue;
53
+ }
54
+
55
+ const level = match[1]?.length ?? 0;
56
+ const title = sanitizeHeadingTitle(match[2] ?? "");
57
+
58
+ if (!title) {
59
+ continue;
60
+ }
61
+
62
+ const id = slugifyHeading(title);
63
+
64
+ if (!id) {
65
+ continue;
66
+ }
67
+
68
+ headings.push({ id, title, level });
69
+ }
70
+
71
+ return headings;
72
+ }
@@ -9,11 +9,18 @@
9
9
  // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10
10
 
11
11
  import { Route as rootRouteImport } from './routes/__root'
12
+ import { Route as DocsRouteImport } from './routes/docs'
12
13
  import { Route as ComponentsRouteImport } from './routes/components'
13
14
  import { Route as IndexRouteImport } from './routes/index'
14
15
  import { Route as ComponentsIndexRouteImport } from './routes/components.index'
16
+ import { Route as DocsSlugRouteImport } from './routes/docs.$slug'
15
17
  import { Route as ComponentsSlugRouteImport } from './routes/components.$slug'
16
18
 
19
+ const DocsRoute = DocsRouteImport.update({
20
+ id: '/docs',
21
+ path: '/docs',
22
+ getParentRoute: () => rootRouteImport,
23
+ } as any)
17
24
  const ComponentsRoute = ComponentsRouteImport.update({
18
25
  id: '/components',
19
26
  path: '/components',
@@ -29,6 +36,11 @@ const ComponentsIndexRoute = ComponentsIndexRouteImport.update({
29
36
  path: '/',
30
37
  getParentRoute: () => ComponentsRoute,
31
38
  } as any)
39
+ const DocsSlugRoute = DocsSlugRouteImport.update({
40
+ id: '/$slug',
41
+ path: '/$slug',
42
+ getParentRoute: () => DocsRoute,
43
+ } as any)
32
44
  const ComponentsSlugRoute = ComponentsSlugRouteImport.update({
33
45
  id: '/$slug',
34
46
  path: '/$slug',
@@ -38,36 +50,63 @@ const ComponentsSlugRoute = ComponentsSlugRouteImport.update({
38
50
  export interface FileRoutesByFullPath {
39
51
  '/': typeof IndexRoute
40
52
  '/components': typeof ComponentsRouteWithChildren
53
+ '/docs': typeof DocsRouteWithChildren
41
54
  '/components/$slug': typeof ComponentsSlugRoute
55
+ '/docs/$slug': typeof DocsSlugRoute
42
56
  '/components/': typeof ComponentsIndexRoute
43
57
  }
44
58
  export interface FileRoutesByTo {
45
59
  '/': typeof IndexRoute
60
+ '/docs': typeof DocsRouteWithChildren
46
61
  '/components/$slug': typeof ComponentsSlugRoute
62
+ '/docs/$slug': typeof DocsSlugRoute
47
63
  '/components': typeof ComponentsIndexRoute
48
64
  }
49
65
  export interface FileRoutesById {
50
66
  __root__: typeof rootRouteImport
51
67
  '/': typeof IndexRoute
52
68
  '/components': typeof ComponentsRouteWithChildren
69
+ '/docs': typeof DocsRouteWithChildren
53
70
  '/components/$slug': typeof ComponentsSlugRoute
71
+ '/docs/$slug': typeof DocsSlugRoute
54
72
  '/components/': typeof ComponentsIndexRoute
55
73
  }
56
74
  export interface FileRouteTypes {
57
75
  fileRoutesByFullPath: FileRoutesByFullPath
58
- fullPaths: '/' | '/components' | '/components/$slug' | '/components/'
76
+ fullPaths:
77
+ | '/'
78
+ | '/components'
79
+ | '/docs'
80
+ | '/components/$slug'
81
+ | '/docs/$slug'
82
+ | '/components/'
59
83
  fileRoutesByTo: FileRoutesByTo
60
- to: '/' | '/components/$slug' | '/components'
61
- id: '__root__' | '/' | '/components' | '/components/$slug' | '/components/'
84
+ to: '/' | '/docs' | '/components/$slug' | '/docs/$slug' | '/components'
85
+ id:
86
+ | '__root__'
87
+ | '/'
88
+ | '/components'
89
+ | '/docs'
90
+ | '/components/$slug'
91
+ | '/docs/$slug'
92
+ | '/components/'
62
93
  fileRoutesById: FileRoutesById
63
94
  }
64
95
  export interface RootRouteChildren {
65
96
  IndexRoute: typeof IndexRoute
66
97
  ComponentsRoute: typeof ComponentsRouteWithChildren
98
+ DocsRoute: typeof DocsRouteWithChildren
67
99
  }
68
100
 
69
101
  declare module '@tanstack/react-router' {
70
102
  interface FileRoutesByPath {
103
+ '/docs': {
104
+ id: '/docs'
105
+ path: '/docs'
106
+ fullPath: '/docs'
107
+ preLoaderRoute: typeof DocsRouteImport
108
+ parentRoute: typeof rootRouteImport
109
+ }
71
110
  '/components': {
72
111
  id: '/components'
73
112
  path: '/components'
@@ -89,6 +128,13 @@ declare module '@tanstack/react-router' {
89
128
  preLoaderRoute: typeof ComponentsIndexRouteImport
90
129
  parentRoute: typeof ComponentsRoute
91
130
  }
131
+ '/docs/$slug': {
132
+ id: '/docs/$slug'
133
+ path: '/$slug'
134
+ fullPath: '/docs/$slug'
135
+ preLoaderRoute: typeof DocsSlugRouteImport
136
+ parentRoute: typeof DocsRoute
137
+ }
92
138
  '/components/$slug': {
93
139
  id: '/components/$slug'
94
140
  path: '/$slug'
@@ -113,9 +159,20 @@ const ComponentsRouteWithChildren = ComponentsRoute._addFileChildren(
113
159
  ComponentsRouteChildren,
114
160
  )
115
161
 
162
+ interface DocsRouteChildren {
163
+ DocsSlugRoute: typeof DocsSlugRoute
164
+ }
165
+
166
+ const DocsRouteChildren: DocsRouteChildren = {
167
+ DocsSlugRoute: DocsSlugRoute,
168
+ }
169
+
170
+ const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren)
171
+
116
172
  const rootRouteChildren: RootRouteChildren = {
117
173
  IndexRoute: IndexRoute,
118
174
  ComponentsRoute: ComponentsRouteWithChildren,
175
+ DocsRoute: DocsRouteWithChildren,
119
176
  }
120
177
  export const routeTree = rootRouteImport
121
178
  ._addFileChildren(rootRouteChildren)
@@ -40,17 +40,15 @@ export const Route = createRootRoute({
40
40
 
41
41
  function RootDocument({ children }: { children: React.ReactNode }) {
42
42
  return (
43
- <Suspense fallback={<SuspenseFallback className=" h-dvh" />}>
43
+ <Suspense fallback={<SuspenseFallback className="" />}>
44
44
  <ThemeProvider>
45
45
  <html lang="en">
46
46
  <head>
47
47
  <HeadContent />
48
48
  </head>
49
- <body className="flex h-dvh flex-col">
49
+ <body className="flex h-dvh flex-col overflow-hidden bg-muted">
50
50
  <Header />
51
- <main className=" container mx-auto flex flex-1 overflow-hidden border-x">
52
- {children}
53
- </main>
51
+ <main className=" container mx-auto flex min-h-0 xl:bg-card flex-1 px-4 xl:p-0 ">{children}</main>
54
52
  <Footer />
55
53
  <TanStackDevtools
56
54
  config={{
@@ -7,16 +7,27 @@ import { PageHeader } from "@/components/docs/layout/page-header";
7
7
  import { mdxComponents } from "@/components/docs/markdown/components";
8
8
  import { RenderPreview } from "@/components/docs/markdown/render-preview";
9
9
 
10
+ const markdownFiles = import.meta.glob("../../content/components/*.mdx", {
11
+ query: "?url",
12
+ import: "default",
13
+ eager: true,
14
+ }) as Record<string, string>;
15
+
10
16
  export const Route = createFileRoute("/components/$slug")({
11
17
  loader: ({ params }) => {
12
18
  const { slug } = params;
13
- const component = (allComponents as any[]).find((c) => c.slug === slug);
19
+ const component = allComponents?.find((c) => c.slug === slug);
14
20
 
15
21
  if (!component) {
16
22
  throw notFound();
17
23
  }
18
24
 
19
- return component;
25
+ const markdownLink = markdownFiles[`../../content/components/${component.slug}.mdx`];
26
+
27
+ return {
28
+ ...component,
29
+ markdownLink,
30
+ };
20
31
  },
21
32
  head: ({ loaderData }) => ({
22
33
  meta: [
@@ -51,8 +62,13 @@ function RouteComponent() {
51
62
 
52
63
  return (
53
64
  <div className=" flex flex-col gap-6 ">
54
- <PageHeader title={component?.title} subtitle={component?.description} />
55
- <div className="container mx-auto max-w-4xl min-w-0 p-6">
65
+ <PageHeader
66
+ title={component?.title}
67
+ subtitle={component?.description}
68
+ baseUILink={component?.referenceLink}
69
+ markdownLink={component?.markdownLink}
70
+ />
71
+ <div data-docs-content className="container mx-auto max-w-4xl min-w-0 p-6">
56
72
  {isMounted ? (
57
73
  <MDXContent code={component.mdx} components={{ ...mdxComponents, RenderPreview }} />
58
74
  ) : null}
@@ -0,0 +1,78 @@
1
+ import { MDXContent } from "@content-collections/mdx/react";
2
+ import { createFileRoute, notFound } from "@tanstack/react-router";
3
+ import { allDocs } from "content-collections";
4
+ import { useEffect, useState } from "react";
5
+
6
+ import { PageHeader } from "@/components/docs/layout/page-header";
7
+ import { mdxComponents } from "@/components/docs/markdown/components";
8
+ import { RenderPreview } from "@/components/docs/markdown/render-preview";
9
+
10
+ const markdownFiles = import.meta.glob("../../content/docs/*.mdx", {
11
+ query: "?url",
12
+ import: "default",
13
+ eager: true,
14
+ }) as Record<string, string>;
15
+
16
+ export const Route = createFileRoute("/docs/$slug")({
17
+ loader: ({ params }) => {
18
+ const { slug } = params;
19
+ const doc = allDocs?.find((d) => d.slug === slug);
20
+
21
+ if (!doc) {
22
+ throw notFound();
23
+ }
24
+
25
+ const markdownLink = markdownFiles[`../../content/docs/${doc.slug}.mdx`];
26
+
27
+ return {
28
+ ...doc,
29
+ markdownLink,
30
+ };
31
+ },
32
+ head: ({ loaderData }) => ({
33
+ meta: [
34
+ {
35
+ charSet: "utf-8",
36
+ },
37
+ {
38
+ name: "viewport",
39
+ content: "width=device-width, initial-scale=1",
40
+ },
41
+ {
42
+ title: `${loaderData?.title} - Shapes UI`,
43
+ },
44
+ ],
45
+ links: [
46
+ {
47
+ rel: "icon",
48
+ href: "/favicon.ico",
49
+ },
50
+ ],
51
+ }),
52
+ component: RouteComponent,
53
+ });
54
+
55
+ function RouteComponent() {
56
+ const doc = Route.useLoaderData();
57
+ const [isMounted, setIsMounted] = useState(false);
58
+
59
+ useEffect(() => {
60
+ setIsMounted(true);
61
+ }, []);
62
+
63
+ return (
64
+ <div className=" flex flex-col gap-6 ">
65
+ <PageHeader
66
+ title={doc?.title}
67
+ subtitle={doc?.description}
68
+ baseUILink={doc?.referenceLink}
69
+ markdownLink={doc?.markdownLink}
70
+ />
71
+ <div data-docs-content className="container mx-auto max-w-4xl min-w-0 p-6">
72
+ {isMounted ? (
73
+ <MDXContent code={doc.mdx} components={{ ...mdxComponents, RenderPreview }} />
74
+ ) : null}
75
+ </div>
76
+ </div>
77
+ );
78
+ }
@@ -0,0 +1,15 @@
1
+ import { createFileRoute, Outlet } from "@tanstack/react-router";
2
+
3
+ import { SplitLayout } from "@/components/docs/layout/split-layout";
4
+
5
+ export const Route = createFileRoute("/docs")({
6
+ component: RouteComponent,
7
+ });
8
+
9
+ function RouteComponent() {
10
+ return (
11
+ <SplitLayout>
12
+ <Outlet />
13
+ </SplitLayout>
14
+ );
15
+ }
@@ -15,7 +15,7 @@
15
15
  --secondary: oklch(0.97 0 0);
16
16
  --secondary-foreground: oklch(0.205 0 0);
17
17
  --muted: oklch(0.97 0 0);
18
- --muted-foreground: oklch(0.556 0 0);
18
+ --muted-foreground: oklch(55.553% 0.00006 271.152);
19
19
  --accent: oklch(0.97 0 0);
20
20
  --accent-foreground: oklch(0.205 0 0);
21
21
  --destructive: oklch(57.7% 0.245 27.325);
@@ -13,14 +13,14 @@ export function exitIfCancelled<T>(value: T): Exclude<T, symbol> {
13
13
  return value as Exclude<T, symbol>;
14
14
  }
15
15
 
16
- export async function getConfig(): Promise<Config | null> {
17
- const configPath = path.join(process.cwd(), "shapes.json");
16
+ export async function getConfig(cwd = process.cwd()): Promise<Config | null> {
17
+ const configPath = path.join(cwd, "shapes.json");
18
18
  if (!fs.existsSync(configPath)) return null;
19
19
  return configSchema.parse(await fs.readJSON(configPath));
20
20
  }
21
21
 
22
- export async function readPackageJson() {
23
- const pkgPath = path.join(process.cwd(), "package.json");
22
+ export async function readPackageJson(cwd = process.cwd()) {
23
+ const pkgPath = path.join(cwd, "package.json");
24
24
  if (!(await fs.pathExists(pkgPath))) return null;
25
25
  return fs.readJSON(pkgPath);
26
26
  }
@@ -45,10 +45,10 @@ export function getMissingDeps(pkg: Record<string, any> | null, deps: string[])
45
45
  return deps.filter((dep) => !pkg.dependencies?.[dep] && !pkg.devDependencies?.[dep]);
46
46
  }
47
47
 
48
- export async function isViteProject() {
49
- const viteConfigTs = path.join(process.cwd(), "vite.config.ts");
50
- const viteConfigJs = path.join(process.cwd(), "vite.config.js");
51
- const viteConfigMjs = path.join(process.cwd(), "vite.config.mjs");
48
+ export async function isViteProject(cwd = process.cwd()) {
49
+ const viteConfigTs = path.join(cwd, "vite.config.ts");
50
+ const viteConfigJs = path.join(cwd, "vite.config.js");
51
+ const viteConfigMjs = path.join(cwd, "vite.config.mjs");
52
52
 
53
53
  return (
54
54
  (await fs.pathExists(viteConfigTs)) ||
@@ -0,0 +1,30 @@
1
+ import { spinner } from "@clack/prompts";
2
+ import { execa } from "execa";
3
+
4
+ import { getInstallCommand } from "@/utils/package-manager";
5
+
6
+ type InstallDependenciesOptions = {
7
+ dev?: boolean;
8
+ label?: string;
9
+ successMessage?: string;
10
+ cwd?: string;
11
+ };
12
+
13
+ export async function installDependencies(
14
+ deps: string[],
15
+ options: InstallDependenciesOptions = {},
16
+ ) {
17
+ if (!deps.length) return;
18
+
19
+ const spin = spinner();
20
+ const label =
21
+ options.label ?? (options.dev ? "Installing dev dependencies" : "Installing dependencies");
22
+
23
+ spin.start(label);
24
+
25
+ const cwd = options.cwd ?? process.cwd();
26
+ const [command, ...args] = await getInstallCommand(deps, options.dev ?? false, cwd);
27
+ await execa(command, args, { cwd });
28
+
29
+ spin.stop(options.successMessage ?? "Dependencies installed");
30
+ }
@@ -1,15 +1,15 @@
1
1
  import { detect } from "detect-package-manager";
2
2
 
3
- export async function getPackageManager() {
3
+ export async function getPackageManager(cwd = process.cwd()) {
4
4
  try {
5
- return await detect();
5
+ return await detect({ cwd });
6
6
  } catch {
7
7
  return "npm";
8
8
  }
9
9
  }
10
10
 
11
- export async function getInstallCommand(deps: string[], dev = false) {
12
- const pm = await getPackageManager();
11
+ export async function getInstallCommand(deps: string[], dev = false, cwd = process.cwd()) {
12
+ const pm = await getPackageManager(cwd);
13
13
 
14
14
  if (pm === "yarn") {
15
15
  return ["yarn", "add", ...(dev ? ["-D"] : []), ...deps];