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.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +47 -0
- package/.github/ISSUE_TEMPLATE/config.yml +1 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +31 -0
- package/.github/pull_request_template.md +14 -0
- package/.github/workflows/pr-preview.yml +75 -0
- package/.github/workflows/release.yml +8 -0
- package/CHANGELOG.md +30 -0
- package/CODE_OF_CONDUCT.md +41 -0
- package/CONTRIBUTING.md +52 -0
- package/README.md +18 -0
- package/SECURITY.md +0 -0
- package/content/components/accordion.mdx +13 -0
- package/content/components/alert-dialog.mdx +34 -0
- package/content/components/autocomplete.mdx +62 -0
- package/content/components/avatar.mdx +11 -0
- package/content/components/button.mdx +8 -0
- package/content/components/checkbox.mdx +11 -0
- package/content/components/collapsible.mdx +11 -0
- package/content/components/combobox.mdx +33 -0
- package/content/components/context-menu.mdx +29 -0
- package/content/components/dialog.mdx +33 -0
- package/content/components/drawer.mdx +38 -0
- package/content/components/field.mdx +23 -2
- package/content/components/fieldset.mdx +11 -1
- package/content/components/form.mdx +8 -0
- package/content/components/input.mdx +4 -0
- package/content/components/menu.mdx +27 -0
- package/content/components/menubar.mdx +31 -0
- package/content/components/meter.mdx +14 -0
- package/content/components/navigation-menu.mdx +28 -0
- package/content/components/number-field.mdx +25 -0
- package/content/components/popover.mdx +22 -0
- package/content/components/preview-card.mdx +14 -2
- package/content/components/progress.mdx +15 -1
- package/content/components/radio.mdx +31 -0
- package/content/components/scroll-area.mdx +23 -0
- package/content/components/select.mdx +57 -0
- package/content/components/separator.mdx +29 -0
- package/content/components/slider.mdx +52 -0
- package/content/components/switch.mdx +30 -0
- package/content/components/tabs.mdx +47 -0
- package/content/components/toast.mdx +70 -0
- package/content/components/toggle-group.mdx +37 -0
- package/content/components/toggle.mdx +46 -2
- package/content/components/toolbar.mdx +48 -0
- package/content/components/tooltip.mdx +38 -0
- package/content/docs/installation.mdx +30 -0
- package/content-collections.ts +65 -1
- package/dist/cli.js +947 -101
- package/examples/__index.tsx +320 -66
- package/examples/autocomplete-align.tsx +39 -0
- package/examples/autocomplete-controlled.tsx +44 -0
- package/examples/autocomplete-groups.tsx +65 -0
- package/examples/autocomplete-no-clear.tsx +40 -0
- package/examples/avatar-demo.tsx +3 -3
- package/examples/checkbox-demo.tsx +1 -1
- package/examples/checkbox-form.tsx +3 -3
- package/examples/field-custom-control.tsx +33 -9
- package/examples/form-demo.tsx +5 -10
- package/examples/input-group-with-button.tsx +1 -1
- package/examples/menu-advanced.tsx +1 -3
- package/examples/menu-align.tsx +19 -16
- package/examples/menu-checkbox.tsx +2 -3
- package/examples/menu-demo.tsx +1 -3
- package/examples/menu-group.tsx +1 -3
- package/examples/menu-radio.tsx +1 -3
- package/examples/menu-submenu.tsx +2 -3
- package/examples/meter-demo.tsx +10 -2
- package/examples/meter-flip.tsx +8 -8
- package/examples/meter-no-label.tsx +9 -2
- package/examples/meter-no-value.tsx +7 -8
- package/examples/radio-card.tsx +28 -0
- package/examples/radio-demo.tsx +19 -1
- package/examples/radio-description.tsx +26 -0
- package/examples/radio-orientation.tsx +21 -0
- package/examples/select-alignment.tsx +51 -0
- package/examples/select-demo.tsx +36 -1
- package/examples/select-disabled.tsx +38 -0
- package/examples/select-groups.tsx +54 -0
- package/examples/select-invalid.tsx +41 -0
- package/examples/select-scrollable.tsx +112 -0
- package/examples/separator-demo.tsx +13 -0
- package/examples/separator-horizontal.tsx +18 -0
- package/examples/slider-controlled.tsx +28 -0
- package/examples/slider-demo.tsx +3 -1
- package/examples/slider-disabled.tsx +7 -0
- package/examples/slider-edge.tsx +13 -0
- package/examples/slider-multiple.tsx +7 -0
- package/examples/slider-range.tsx +5 -0
- package/examples/slider-vertical.tsx +10 -0
- package/examples/switch-demo.tsx +19 -1
- package/examples/switch-disabled.tsx +20 -0
- package/examples/switch-sizes.tsx +24 -0
- package/examples/switch-with-label.tsx +16 -0
- package/examples/tabs-demo.tsx +14 -1
- package/examples/tabs-disabled.tsx +21 -0
- package/examples/tabs-line.tsx +18 -0
- package/examples/tabs-vertical.tsx +13 -0
- package/examples/toast-action.tsx +39 -0
- package/examples/toast-anchored.tsx +36 -0
- package/examples/toast-demo.tsx +27 -1
- package/examples/toast-positions.tsx +54 -0
- package/examples/toast-promise.tsx +51 -0
- package/examples/toast-stacked.tsx +30 -0
- package/examples/toast-timeout.tsx +43 -0
- package/examples/toast-update.tsx +38 -0
- package/examples/toast-variants.tsx +54 -0
- package/examples/toggle-controlled.tsx +20 -0
- package/examples/toggle-demo.tsx +7 -51
- package/examples/toggle-group-demo.tsx +19 -0
- package/examples/toggle-group-multiple.tsx +19 -0
- package/examples/toggle-icon-fill.tsx +12 -0
- package/examples/toolbar-demo.tsx +45 -21
- package/examples/toolbar-input-link.tsx +35 -0
- package/examples/toolbar-menu.tsx +53 -0
- package/examples/tooltip-demo.tsx +48 -0
- package/examples/tooltip-positions.tsx +60 -0
- package/package.json +19 -18
- package/public/base-ui.svg +1 -0
- package/public/r/drawer.json +1 -1
- package/public/r/field.json +1 -1
- package/public/r/meter.json +1 -1
- package/public/r/number-field.json +1 -1
- package/public/r/progress.json +1 -1
- package/public/r/radio.json +1 -1
- package/public/r/select.json +1 -1
- package/public/r/slider.json +1 -1
- package/public/r/switch.json +1 -1
- package/public/r/tabs.json +1 -1
- package/public/r/toast.json +2 -1
- package/public/r/toggle.json +1 -1
- package/public/r/toolbar.json +1 -1
- package/public/r/tooltip.json +15 -0
- package/src/assets/base-ui.svg +1 -0
- package/src/commands/add.ts +79 -38
- package/src/commands/cli.ts +50 -3
- package/src/commands/create.ts +262 -0
- package/src/commands/init.ts +45 -12
- package/src/commands/palette.ts +55 -0
- package/src/components/docs/layout/footer.tsx +2 -2
- package/src/components/docs/layout/header.tsx +7 -19
- package/src/components/docs/layout/mobile-menu.tsx +26 -78
- package/src/components/docs/layout/nav-list.tsx +27 -21
- package/src/components/docs/layout/page-header.tsx +52 -7
- package/src/components/docs/layout/split-layout.tsx +11 -9
- package/src/components/docs/layout/table-of-content.tsx +145 -0
- package/src/components/docs/markdown/components.tsx +142 -29
- package/src/components/docs/markdown/copy-button.tsx +41 -0
- package/src/components/docs/markdown/installation-block.tsx +5 -24
- package/src/components/docs/markdown/render-preview.tsx +1 -1
- package/src/components/ui/badge.tsx +1 -1
- package/src/components/ui/button-group.tsx +1 -1
- package/src/components/ui/checkbox.tsx +1 -1
- package/src/components/ui/drawer.tsx +1 -1
- package/src/components/ui/field.tsx +9 -28
- package/src/components/ui/form.tsx +1 -1
- package/src/components/ui/meter.tsx +12 -26
- package/src/components/ui/number-field.tsx +1 -1
- package/src/components/ui/radio.tsx +32 -19
- package/src/components/ui/scroll-area.tsx +11 -2
- package/src/components/ui/select.tsx +6 -6
- package/src/components/ui/slider.tsx +8 -5
- package/src/components/ui/switch.tsx +13 -17
- package/src/components/ui/tabs.tsx +23 -10
- package/src/components/ui/toast.tsx +190 -29
- package/src/components/ui/toggle.tsx +1 -1
- package/src/components/ui/toolbar.tsx +17 -4
- package/src/components/ui/tooltip.tsx +54 -0
- package/src/lib/docs-headings.ts +72 -0
- package/src/routeTree.gen.ts +60 -3
- package/src/routes/__root.tsx +3 -5
- package/src/routes/components.$slug.tsx +20 -4
- package/src/routes/docs.$slug.tsx +78 -0
- package/src/routes/docs.tsx +15 -0
- package/src/styles/styles.css +1 -1
- package/src/utils/cli-utils.ts +8 -8
- package/src/utils/dependency-installer.ts +30 -0
- package/src/utils/package-manager.ts +4 -4
- package/src/utils/palette.ts +666 -0
- package/src/utils/schema.ts +6 -0
package/src/commands/add.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
3
|
import { cancel, multiselect, note, spinner } from "@clack/prompts";
|
|
4
|
-
import { execa } from "execa";
|
|
5
4
|
import fs from "fs-extra";
|
|
6
5
|
|
|
7
6
|
import type { RegistryItem } from "@/types/registry-item";
|
|
8
7
|
import { exitIfCancelled } from "@/utils/cli-utils";
|
|
9
|
-
import {
|
|
8
|
+
import { installDependencies } from "@/utils/dependency-installer";
|
|
10
9
|
import type { Config } from "@/utils/schema";
|
|
11
10
|
|
|
12
11
|
const REGISTRY_URL = "https://shapes-ui.com/r";
|
|
13
12
|
|
|
14
|
-
export async function loadRegistryIndex() {
|
|
15
|
-
const localRegistryDir = path.resolve(
|
|
13
|
+
export async function loadRegistryIndex(cwd = process.cwd()) {
|
|
14
|
+
const localRegistryDir = path.resolve(cwd, "public/r");
|
|
16
15
|
if (await fs.pathExists(localRegistryDir)) {
|
|
17
16
|
const files = await fs.readdir(localRegistryDir);
|
|
18
17
|
const entries: RegistryItem[] = [];
|
|
@@ -47,7 +46,7 @@ export async function loadRegistryIndex() {
|
|
|
47
46
|
export async function pickComponents() {
|
|
48
47
|
let components: string[] = [];
|
|
49
48
|
try {
|
|
50
|
-
components = await loadRegistryIndex();
|
|
49
|
+
components = await loadRegistryIndex(process.cwd());
|
|
51
50
|
} catch (error) {
|
|
52
51
|
cancel(error instanceof Error ? error.message : "Failed to load registry.");
|
|
53
52
|
process.exit(1);
|
|
@@ -68,49 +67,90 @@ export async function pickComponents() {
|
|
|
68
67
|
return selected as string[];
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
type InstallContext = {
|
|
71
|
+
installing: Set<string>;
|
|
72
|
+
installed: Set<string>;
|
|
73
|
+
};
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
function createInstallContext(): InstallContext {
|
|
76
|
+
return {
|
|
77
|
+
installing: new Set<string>(),
|
|
78
|
+
installed: new Set<string>(),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
76
81
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
82
|
+
export async function installComponent(
|
|
83
|
+
name: string,
|
|
84
|
+
config: Config,
|
|
85
|
+
context = createInstallContext(),
|
|
86
|
+
cwd = process.cwd(),
|
|
87
|
+
) {
|
|
88
|
+
if (context.installed.has(name)) return;
|
|
89
|
+
if (context.installing.has(name)) {
|
|
90
|
+
note(`Skipped circular dependency while resolving ${name}`);
|
|
91
|
+
return;
|
|
87
92
|
}
|
|
88
93
|
|
|
89
|
-
|
|
94
|
+
context.installing.add(name);
|
|
95
|
+
try {
|
|
96
|
+
const spin = spinner();
|
|
97
|
+
spin.start(`Fetching ${name}`);
|
|
98
|
+
|
|
99
|
+
let data: RegistryItem;
|
|
100
|
+
|
|
101
|
+
const localFile = path.resolve(cwd, `public/r/${name}.json`);
|
|
102
|
+
if (await fs.pathExists(localFile)) {
|
|
103
|
+
data = await fs.readJSON(localFile);
|
|
104
|
+
} else {
|
|
105
|
+
const res = await fetch(`${REGISTRY_URL}/${name}.json`);
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
spin.stop("Fetch failed");
|
|
108
|
+
throw new Error(`Component ${name} not found in registry.`);
|
|
109
|
+
}
|
|
110
|
+
data = await res.json();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
spin.stop(`Fetched ${name}`);
|
|
90
114
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
// 1. Recursive Install of Registry Dependencies
|
|
116
|
+
if (data.registryDependencies) {
|
|
117
|
+
for (const dep of data.registryDependencies) {
|
|
118
|
+
const depPath = path.join(cwd, config.paths.ui, `${dep}.tsx`);
|
|
119
|
+
if (!fs.existsSync(depPath)) {
|
|
120
|
+
await installComponent(dep, config, context, cwd);
|
|
121
|
+
}
|
|
97
122
|
}
|
|
98
123
|
}
|
|
99
|
-
}
|
|
100
124
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
// 2. Install NPM Dependencies
|
|
126
|
+
if (data.dependencies?.length) {
|
|
127
|
+
await installDependencies(data.dependencies, {
|
|
128
|
+
label: `Installing dependencies for ${name}`,
|
|
129
|
+
successMessage: `Dependencies installed for ${name}`,
|
|
130
|
+
cwd,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 3. Write Files
|
|
135
|
+
for (const file of data.files) {
|
|
136
|
+
const target = path.join(cwd, config.paths.ui, file.path);
|
|
137
|
+
await fs.ensureDir(path.dirname(target));
|
|
138
|
+
await fs.writeFile(target, file.content);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
context.installed.add(name);
|
|
142
|
+
note(`Added ${name}`);
|
|
143
|
+
} finally {
|
|
144
|
+
context.installing.delete(name);
|
|
105
145
|
}
|
|
146
|
+
}
|
|
106
147
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
await
|
|
148
|
+
export async function addAllComponents(config: Config, cwd = process.cwd()) {
|
|
149
|
+
const all = await loadRegistryIndex(cwd);
|
|
150
|
+
const context = createInstallContext();
|
|
151
|
+
for (const name of all) {
|
|
152
|
+
await installComponent(name, config, context, cwd);
|
|
112
153
|
}
|
|
113
|
-
note(`Added ${name}`);
|
|
114
154
|
}
|
|
115
155
|
|
|
116
156
|
export async function addCommand(components: string[], config: Config) {
|
|
@@ -119,7 +159,8 @@ export async function addCommand(components: string[], config: Config) {
|
|
|
119
159
|
selections = await pickComponents();
|
|
120
160
|
}
|
|
121
161
|
|
|
162
|
+
const context = createInstallContext();
|
|
122
163
|
for (const name of selections) {
|
|
123
|
-
await installComponent(name, config);
|
|
164
|
+
await installComponent(name, config, context, process.cwd());
|
|
124
165
|
}
|
|
125
166
|
}
|
package/src/commands/cli.ts
CHANGED
|
@@ -3,25 +3,72 @@ import { Command } from "commander";
|
|
|
3
3
|
|
|
4
4
|
import { getConfig } from "@/utils/cli-utils";
|
|
5
5
|
|
|
6
|
-
import { addCommand } from "./add";
|
|
6
|
+
import { addAllComponents, addCommand } from "./add";
|
|
7
|
+
import { createCommand } from "./create";
|
|
7
8
|
import { initCommand } from "./init";
|
|
9
|
+
import { paletteSetCommand } from "./palette";
|
|
8
10
|
|
|
9
11
|
const program = new Command();
|
|
10
12
|
|
|
11
|
-
program.name("shapes").description("Shapes UI CLI").version("0.0.1");
|
|
13
|
+
program.name("shapes-ui").description("Shapes UI CLI").version("0.0.1");
|
|
12
14
|
|
|
13
15
|
program.command("init").description("Configure Shapes UI for your project").action(initCommand);
|
|
14
16
|
|
|
17
|
+
program
|
|
18
|
+
.command("create [project-name]")
|
|
19
|
+
.description("Create a new Vite React TypeScript app ready for Shapes UI")
|
|
20
|
+
.option("-i, --install", "Install project dependencies")
|
|
21
|
+
.option("--full", "Configure Shapes UI and install all components")
|
|
22
|
+
.option("--style <style>", "Shapes style for --full (default|brutalist|minimal)")
|
|
23
|
+
.option("--palette <name>", "Brand palette for --full (e.g. blue, emerald, rose)")
|
|
24
|
+
.option("--contrast-mode <mode>", "Foreground contrast mode for --full (deterministic|dynamic)")
|
|
25
|
+
.option("--ui-path <path>", "UI components path for --full (e.g. ./src/components/ui)")
|
|
26
|
+
.option("--css-path <path>", "Tailwind CSS file path for --full (e.g. src/index.css)")
|
|
27
|
+
.option("-f, --force", "Overwrite target directory if it already exists")
|
|
28
|
+
.action(async (projectName, options) => {
|
|
29
|
+
await createCommand(projectName, {
|
|
30
|
+
install: options.install,
|
|
31
|
+
full: options.full,
|
|
32
|
+
style: options.style,
|
|
33
|
+
palette: options.palette,
|
|
34
|
+
contrastMode: options.contrastMode,
|
|
35
|
+
uiPath: options.uiPath,
|
|
36
|
+
cssPath: options.cssPath,
|
|
37
|
+
force: options.force,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
15
41
|
program
|
|
16
42
|
.command("add [components...]")
|
|
17
43
|
.description("Add components to your project")
|
|
18
|
-
.
|
|
44
|
+
.option("-a, --all", "Add all available components")
|
|
45
|
+
.action(async (components, options) => {
|
|
19
46
|
const config = await getConfig();
|
|
20
47
|
if (!config) {
|
|
21
48
|
console.error("Please run 'init' first.");
|
|
22
49
|
return;
|
|
23
50
|
}
|
|
51
|
+
if (options.all) {
|
|
52
|
+
await addAllComponents(config);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
24
55
|
await addCommand(components, config);
|
|
25
56
|
});
|
|
26
57
|
|
|
58
|
+
program
|
|
59
|
+
.command("palette")
|
|
60
|
+
.description("Manage color palettes")
|
|
61
|
+
.command("set <name>")
|
|
62
|
+
.description("Set brand palette and regenerate semantic tokens")
|
|
63
|
+
.option("--css-path <path>", "Override Tailwind CSS file path")
|
|
64
|
+
.option("--contrast-mode <mode>", "Foreground contrast mode (deterministic|dynamic)")
|
|
65
|
+
.option("--refresh", "Refresh Tailwind palette data from docs")
|
|
66
|
+
.action(async (name, options) => {
|
|
67
|
+
await paletteSetCommand(name, {
|
|
68
|
+
cssPath: options.cssPath,
|
|
69
|
+
contrastMode: options.contrastMode,
|
|
70
|
+
refresh: options.refresh,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
27
74
|
program.parse();
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { confirm, intro, note, outro, spinner, text } from "@clack/prompts";
|
|
4
|
+
import { execa } from "execa";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
|
|
8
|
+
import { addAllComponents } from "@/commands/add";
|
|
9
|
+
import {
|
|
10
|
+
exitIfCancelled,
|
|
11
|
+
getMissingDeps,
|
|
12
|
+
isTailwindV4Installed,
|
|
13
|
+
isViteProject,
|
|
14
|
+
readPackageJson,
|
|
15
|
+
} from "@/utils/cli-utils";
|
|
16
|
+
import { installDependencies } from "@/utils/dependency-installer";
|
|
17
|
+
import { getPackageManager } from "@/utils/package-manager";
|
|
18
|
+
import {
|
|
19
|
+
type ContrastMode,
|
|
20
|
+
getDefaultBrandPaletteOptions,
|
|
21
|
+
normalizeContrastMode,
|
|
22
|
+
writePaletteTokens,
|
|
23
|
+
} from "@/utils/palette";
|
|
24
|
+
import type { Config } from "@/utils/schema";
|
|
25
|
+
|
|
26
|
+
type CreateOptions = {
|
|
27
|
+
install?: boolean;
|
|
28
|
+
force?: boolean;
|
|
29
|
+
full?: boolean;
|
|
30
|
+
style?: Config["style"];
|
|
31
|
+
palette?: string;
|
|
32
|
+
contrastMode?: ContrastMode | string;
|
|
33
|
+
uiPath?: string;
|
|
34
|
+
cssPath?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const BASE_DEPS = [
|
|
38
|
+
"@base-ui/react",
|
|
39
|
+
"class-variance-authority",
|
|
40
|
+
"clsx",
|
|
41
|
+
"lucide-react",
|
|
42
|
+
"tailwind-merge",
|
|
43
|
+
"tw-animate-css",
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
async function ensureTailwindStyles(cwd: string, cssPath: string) {
|
|
47
|
+
const template = '@import "tailwindcss";\n';
|
|
48
|
+
const absPath = path.join(cwd, cssPath);
|
|
49
|
+
await fs.ensureDir(path.dirname(absPath));
|
|
50
|
+
|
|
51
|
+
if (await fs.pathExists(absPath)) {
|
|
52
|
+
const current = await fs.readFile(absPath, "utf-8");
|
|
53
|
+
if (current.includes("tailwindcss") || current.includes("@tailwind")) return;
|
|
54
|
+
await fs.writeFile(absPath, `${current.trimEnd()}\n\n${template}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await fs.writeFile(absPath, template);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function setupShapesFull(projectDir: string, options: CreateOptions) {
|
|
62
|
+
const setupSpin = spinner();
|
|
63
|
+
setupSpin.start("Configuring Shapes UI");
|
|
64
|
+
|
|
65
|
+
const cssPath = options.cssPath ?? "src/index.css";
|
|
66
|
+
const uiPath = options.uiPath ?? "./src/components/ui";
|
|
67
|
+
const style = options.style ?? "default";
|
|
68
|
+
const palette = options.palette ?? "blue";
|
|
69
|
+
const contrastMode = options.contrastMode ?? "deterministic";
|
|
70
|
+
const pkg = await readPackageJson(projectDir);
|
|
71
|
+
const hasTailwindV4 = isTailwindV4Installed(pkg);
|
|
72
|
+
const isVite = await isViteProject(projectDir);
|
|
73
|
+
|
|
74
|
+
if (!hasTailwindV4) {
|
|
75
|
+
const tailwindDeps = ["tailwindcss"];
|
|
76
|
+
if (isVite) {
|
|
77
|
+
tailwindDeps.push("@tailwindcss/vite");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await installDependencies(tailwindDeps, {
|
|
81
|
+
dev: true,
|
|
82
|
+
label: "Installing Tailwind dependencies",
|
|
83
|
+
successMessage: "Tailwind dependencies installed",
|
|
84
|
+
cwd: projectDir,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const latestPkg = await readPackageJson(projectDir);
|
|
89
|
+
const missingBaseDeps = getMissingDeps(latestPkg, BASE_DEPS);
|
|
90
|
+
await installDependencies(missingBaseDeps, {
|
|
91
|
+
label: "Installing Shapes base dependencies",
|
|
92
|
+
successMessage: "Shapes base dependencies installed",
|
|
93
|
+
cwd: projectDir,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await ensureTailwindStyles(projectDir, cssPath);
|
|
97
|
+
|
|
98
|
+
const paletteResult = await writePaletteTokens({
|
|
99
|
+
cwd: projectDir,
|
|
100
|
+
cssPath,
|
|
101
|
+
paletteName: palette,
|
|
102
|
+
neutralPalette: "zinc",
|
|
103
|
+
contrastMode,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const config: Config = {
|
|
107
|
+
$schema: "https://shapes-ui.com/schema.json",
|
|
108
|
+
style,
|
|
109
|
+
palette: {
|
|
110
|
+
name: paletteResult.paletteName,
|
|
111
|
+
contrastMode: paletteResult.contrastMode,
|
|
112
|
+
},
|
|
113
|
+
tailwind: { css: cssPath, baseColor: "zinc" },
|
|
114
|
+
paths: { ui: uiPath, lib: "./src/lib" },
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
await fs.writeJSON(path.join(projectDir, "shapes.json"), config, { spaces: 2 });
|
|
118
|
+
await addAllComponents(config, projectDir);
|
|
119
|
+
|
|
120
|
+
setupSpin.stop("Shapes UI configured with all components");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getCreateCommand(packageManager: string, projectName: string) {
|
|
124
|
+
if (packageManager === "pnpm") {
|
|
125
|
+
return ["pnpm", ["create", "vite", projectName, "--template", "react-ts"]] as const;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (packageManager === "yarn") {
|
|
129
|
+
return ["yarn", ["create", "vite", projectName, "--template", "react-ts"]] as const;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (packageManager === "bun") {
|
|
133
|
+
return ["bun", ["create", "vite", projectName, "--template", "react-ts"]] as const;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return ["npm", ["create", "vite@latest", projectName, "--", "--template", "react-ts"]] as const;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getProjectInstallCommand(packageManager: string) {
|
|
140
|
+
if (packageManager === "pnpm") {
|
|
141
|
+
return ["pnpm", ["install"]] as const;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (packageManager === "yarn") {
|
|
145
|
+
return ["yarn", ["install"]] as const;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (packageManager === "bun") {
|
|
149
|
+
return ["bun", ["install"]] as const;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return ["npm", ["install"]] as const;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function createCommand(projectNameArg?: string, options: CreateOptions = {}) {
|
|
156
|
+
intro(pc.bgCyan(pc.black(" Create a Shapes UI app ")));
|
|
157
|
+
|
|
158
|
+
const allowedStyles = new Set<Config["style"]>(["default", "brutalist", "minimal"]);
|
|
159
|
+
if (options.style && !allowedStyles.has(options.style)) {
|
|
160
|
+
throw new Error(`Invalid style "${options.style}". Use one of: default, brutalist, minimal.`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (options.palette) {
|
|
164
|
+
const normalizedPalette = options.palette.toLowerCase();
|
|
165
|
+
const allowedPalettes = new Set<string>(getDefaultBrandPaletteOptions());
|
|
166
|
+
if (!allowedPalettes.has(normalizedPalette)) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Invalid palette "${options.palette}". Use one of: ${getDefaultBrandPaletteOptions().join(", ")}.`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
options.palette = normalizedPalette;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (options.contrastMode) {
|
|
175
|
+
options.contrastMode = normalizeContrastMode(options.contrastMode);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (options.uiPath !== undefined && !options.uiPath.trim()) {
|
|
179
|
+
throw new Error("Invalid ui path. Please provide a non-empty value.");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (options.cssPath !== undefined && !options.cssPath.trim()) {
|
|
183
|
+
throw new Error("Invalid css path. Please provide a non-empty value.");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const projectName =
|
|
187
|
+
projectNameArg && projectNameArg.trim().length > 0
|
|
188
|
+
? projectNameArg.trim()
|
|
189
|
+
: (
|
|
190
|
+
exitIfCancelled(
|
|
191
|
+
await text({
|
|
192
|
+
message: "Project name",
|
|
193
|
+
initialValue: "my-shapes-app",
|
|
194
|
+
validate(value) {
|
|
195
|
+
if (!value || !value.trim()) return "Project name is required.";
|
|
196
|
+
return undefined;
|
|
197
|
+
},
|
|
198
|
+
}),
|
|
199
|
+
) as string
|
|
200
|
+
).trim();
|
|
201
|
+
|
|
202
|
+
const projectDir = path.resolve(process.cwd(), projectName);
|
|
203
|
+
const exists = await fs.pathExists(projectDir);
|
|
204
|
+
|
|
205
|
+
if (exists) {
|
|
206
|
+
const overwrite =
|
|
207
|
+
options.force ??
|
|
208
|
+
exitIfCancelled(
|
|
209
|
+
await confirm({
|
|
210
|
+
message: `${projectName} already exists. Overwrite it?`,
|
|
211
|
+
initialValue: false,
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (!overwrite) {
|
|
216
|
+
outro(pc.yellow("Create cancelled."));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
await fs.remove(projectDir);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const packageManager = await getPackageManager();
|
|
224
|
+
const [createCommandName, createArgs] = getCreateCommand(packageManager, projectName);
|
|
225
|
+
|
|
226
|
+
const createSpin = spinner();
|
|
227
|
+
createSpin.start(`Scaffolding ${projectName}`);
|
|
228
|
+
|
|
229
|
+
await execa(createCommandName, createArgs, { stdio: "ignore" });
|
|
230
|
+
createSpin.stop(`Created ${projectName}`);
|
|
231
|
+
|
|
232
|
+
note(`Project scaffolded with ${packageManager} + Vite (React + TypeScript).`, "Create");
|
|
233
|
+
|
|
234
|
+
const shouldInstallProjectDeps = options.install || options.full;
|
|
235
|
+
|
|
236
|
+
if (shouldInstallProjectDeps) {
|
|
237
|
+
const [installCommand, installArgs] = getProjectInstallCommand(packageManager);
|
|
238
|
+
const installSpin = spinner();
|
|
239
|
+
installSpin.start("Installing project dependencies");
|
|
240
|
+
|
|
241
|
+
await execa(installCommand, installArgs, {
|
|
242
|
+
cwd: projectDir,
|
|
243
|
+
stdio: "ignore",
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
installSpin.stop("Dependencies installed");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (options.full) {
|
|
250
|
+
await setupShapesFull(projectDir, options);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const runShapesInit = `${packageManager === "npm" ? "npx" : packageManager === "pnpm" ? "pnpm dlx" : packageManager === "yarn" ? "yarn dlx" : "bunx"} shapes-ui init`;
|
|
254
|
+
|
|
255
|
+
outro(
|
|
256
|
+
`${pc.green(options.full ? "Project ready with Shapes UI." : "Project ready.")}\n\n` +
|
|
257
|
+
`${pc.bold("Next steps:")}\n` +
|
|
258
|
+
` cd ${projectName}\n` +
|
|
259
|
+
` ${shouldInstallProjectDeps ? "" : `${packageManager} install\n `}${options.full ? "" : `${runShapesInit}\n `}` +
|
|
260
|
+
` ${packageManager} run dev`,
|
|
261
|
+
);
|
|
262
|
+
}
|
package/src/commands/init.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
-
import { confirm, intro, note, outro, select, text
|
|
4
|
-
import { execa } from "execa";
|
|
3
|
+
import { confirm, intro, note, outro, select, text } from "@clack/prompts";
|
|
5
4
|
import figlet from "figlet";
|
|
6
5
|
import fs from "fs-extra";
|
|
7
6
|
import gradient from "gradient-string";
|
|
@@ -15,7 +14,12 @@ import {
|
|
|
15
14
|
isViteProject,
|
|
16
15
|
readPackageJson,
|
|
17
16
|
} from "@/utils/cli-utils";
|
|
18
|
-
import {
|
|
17
|
+
import { installDependencies } from "@/utils/dependency-installer";
|
|
18
|
+
import {
|
|
19
|
+
type ContrastMode,
|
|
20
|
+
getDefaultBrandPaletteOptions,
|
|
21
|
+
writePaletteTokens,
|
|
22
|
+
} from "@/utils/palette";
|
|
19
23
|
import { type Config } from "@/utils/schema";
|
|
20
24
|
|
|
21
25
|
const BASE_DEPS = [
|
|
@@ -28,15 +32,10 @@ const BASE_DEPS = [
|
|
|
28
32
|
];
|
|
29
33
|
|
|
30
34
|
async function installDeps(deps: string[], dev = false) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const [command, ...args] = await getInstallCommand(deps, dev);
|
|
37
|
-
await execa(command, args);
|
|
38
|
-
|
|
39
|
-
spin.stop(pc.green("Dependencies installed"));
|
|
35
|
+
await installDependencies(deps, {
|
|
36
|
+
dev,
|
|
37
|
+
successMessage: pc.green("Dependencies installed"),
|
|
38
|
+
});
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
async function ensureTailwindStyles(cssPath: string) {
|
|
@@ -81,7 +80,30 @@ export async function initCommand() {
|
|
|
81
80
|
options: [
|
|
82
81
|
{ label: "Default", value: "default" },
|
|
83
82
|
{ label: "Brutalist", value: "brutalist" },
|
|
83
|
+
{ label: "Minimal", value: "minimal" },
|
|
84
|
+
],
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const palette = exitIfCancelled(
|
|
89
|
+
await select({
|
|
90
|
+
message: "Pick a brand palette",
|
|
91
|
+
options: getDefaultBrandPaletteOptions().map((name) => ({
|
|
92
|
+
label: name[0].toUpperCase() + name.slice(1),
|
|
93
|
+
value: name,
|
|
94
|
+
})),
|
|
95
|
+
initialValue: "blue",
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const contrastMode = exitIfCancelled(
|
|
100
|
+
await select({
|
|
101
|
+
message: "Choose foreground contrast strategy",
|
|
102
|
+
options: [
|
|
103
|
+
{ label: "Deterministic shades", value: "deterministic" },
|
|
104
|
+
{ label: "Dynamic contrast", value: "dynamic" },
|
|
84
105
|
],
|
|
106
|
+
initialValue: "deterministic",
|
|
85
107
|
}),
|
|
86
108
|
);
|
|
87
109
|
|
|
@@ -121,9 +143,20 @@ export async function initCommand() {
|
|
|
121
143
|
await installDeps(missingBaseDeps, false);
|
|
122
144
|
await ensureTailwindStyles(cssPath);
|
|
123
145
|
|
|
146
|
+
const paletteResult = await writePaletteTokens({
|
|
147
|
+
cssPath,
|
|
148
|
+
paletteName: palette as string,
|
|
149
|
+
neutralPalette: "zinc",
|
|
150
|
+
contrastMode: contrastMode as ContrastMode,
|
|
151
|
+
});
|
|
152
|
+
|
|
124
153
|
const config: Config = {
|
|
125
154
|
$schema: "https://shapes-ui.com/schema.json",
|
|
126
155
|
style: style as Config["style"],
|
|
156
|
+
palette: {
|
|
157
|
+
name: paletteResult.paletteName,
|
|
158
|
+
contrastMode: paletteResult.contrastMode,
|
|
159
|
+
},
|
|
127
160
|
tailwind: { css: cssPath as string, baseColor: "zinc" },
|
|
128
161
|
paths: { ui: uiPath as string, lib: "./src/lib" },
|
|
129
162
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { intro, note, outro } from "@clack/prompts";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
|
|
7
|
+
import { getConfig } from "@/utils/cli-utils";
|
|
8
|
+
import { normalizeContrastMode, writePaletteTokens } from "@/utils/palette";
|
|
9
|
+
|
|
10
|
+
type PaletteSetOptions = {
|
|
11
|
+
cssPath?: string;
|
|
12
|
+
contrastMode?: string;
|
|
13
|
+
refresh?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function paletteSetCommand(name: string, options: PaletteSetOptions = {}) {
|
|
17
|
+
intro(pc.bgCyan(pc.black(" Update Shapes palette ")));
|
|
18
|
+
|
|
19
|
+
const config = await getConfig();
|
|
20
|
+
if (!config) {
|
|
21
|
+
throw new Error("Could not find shapes.json. Run `shapes-ui init` first.");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const contrastMode = normalizeContrastMode(options.contrastMode ?? config.palette?.contrastMode);
|
|
25
|
+
const cssPath = options.cssPath ?? config.tailwind.css;
|
|
26
|
+
const neutralPalette = config.tailwind.baseColor;
|
|
27
|
+
|
|
28
|
+
const result = await writePaletteTokens({
|
|
29
|
+
cssPath,
|
|
30
|
+
paletteName: name,
|
|
31
|
+
neutralPalette,
|
|
32
|
+
contrastMode,
|
|
33
|
+
refresh: options.refresh,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const updatedConfig = {
|
|
37
|
+
...config,
|
|
38
|
+
palette: {
|
|
39
|
+
name: result.paletteName,
|
|
40
|
+
contrastMode: result.contrastMode,
|
|
41
|
+
},
|
|
42
|
+
tailwind: {
|
|
43
|
+
...config.tailwind,
|
|
44
|
+
css: cssPath,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
await fs.writeJSON(path.join(process.cwd(), "shapes.json"), updatedConfig, { spaces: 2 });
|
|
49
|
+
|
|
50
|
+
note(
|
|
51
|
+
`palette=${result.paletteName} neutral=${result.neutralPalette} contrast=${result.contrastMode} source=${result.source}`,
|
|
52
|
+
"Palette updated",
|
|
53
|
+
);
|
|
54
|
+
outro(pc.green("Shapes UI palette updated."));
|
|
55
|
+
}
|
|
@@ -2,8 +2,8 @@ import { Badge } from "@/components/ui/badge";
|
|
|
2
2
|
|
|
3
3
|
export function Footer() {
|
|
4
4
|
return (
|
|
5
|
-
<footer className=" h-12
|
|
6
|
-
<div className=" container mx-auto flex h-full items-center
|
|
5
|
+
<footer className=" z-10 h-12">
|
|
6
|
+
<div className=" container mx-auto flex h-full items-center px-4">
|
|
7
7
|
<Badge variant={"info"}>Alpha</Badge>
|
|
8
8
|
</div>
|
|
9
9
|
</footer>
|