reend-components 1.0.0 → 1.2.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/dist/bin/cli.cjs CHANGED
@@ -8813,201 +8813,10 @@ var chalk = createChalk();
8813
8813
  var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
8814
8814
  var source_default = chalk;
8815
8815
 
8816
- // src/cli/commands/init.ts
8817
- var import_node_fs2 = require("node:fs");
8818
- var import_node_path2 = require("node:path");
8819
- var import_prompts = __toESM(require_prompts3(), 1);
8820
-
8821
- // src/cli/utils.ts
8822
- var import_node_fs = require("node:fs");
8823
- var import_node_path = require("node:path");
8824
- var CONFIG_FILE = "reend-ui.config.json";
8825
- function readConfig(cwd2) {
8826
- const configPath = (0, import_node_path.join)(cwd2, CONFIG_FILE);
8827
- if (!(0, import_node_fs.existsSync)(configPath)) return null;
8828
- try {
8829
- return JSON.parse((0, import_node_fs.readFileSync)(configPath, "utf-8"));
8830
- } catch {
8831
- return null;
8832
- }
8833
- }
8834
- function writeConfig(cwd2, config) {
8835
- const configPath = (0, import_node_path.join)(cwd2, CONFIG_FILE);
8836
- (0, import_node_fs.writeFileSync)(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
8837
- }
8838
- function detectFramework(cwd2) {
8839
- const pkgPath = (0, import_node_path.join)(cwd2, "package.json");
8840
- if (!(0, import_node_fs.existsSync)(pkgPath)) return "vite";
8841
- try {
8842
- const pkg = JSON.parse((0, import_node_fs.readFileSync)(pkgPath, "utf-8"));
8843
- const allDeps = {
8844
- ...pkg.dependencies,
8845
- ...pkg.devDependencies
8846
- };
8847
- if (allDeps["next"]) return "next";
8848
- if (allDeps["@remix-run/react"]) return "remix";
8849
- if (allDeps["gatsby"]) return "gatsby";
8850
- return "vite";
8851
- } catch {
8852
- return "vite";
8853
- }
8854
- }
8855
- function detectTypeScript(cwd2) {
8856
- return (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd2, "tsconfig.json")) || (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd2, "tsconfig.app.json"));
8857
- }
8858
- function detectTailwindConfig(cwd2) {
8859
- const candidates = [
8860
- "tailwind.config.ts",
8861
- "tailwind.config.js",
8862
- "tailwind.config.mjs"
8863
- ];
8864
- for (const f of candidates) {
8865
- if ((0, import_node_fs.existsSync)((0, import_node_path.join)(cwd2, f))) return f;
8866
- }
8867
- return void 0;
8868
- }
8869
- async function fetchText(url) {
8870
- const res = await fetch(url);
8871
- if (!res.ok) {
8872
- throw new Error(`HTTP ${res.status} ${res.statusText} \u2014 ${url}`);
8873
- }
8874
- return res.text();
8875
- }
8876
- var log = {
8877
- info: (msg) => console.log(` ${source_default.cyan("\u25C6")} ${msg}`),
8878
- success: (msg) => console.log(` ${source_default.green("\u25C6")} ${source_default.green(msg)}`),
8879
- warn: (msg) => console.log(` ${source_default.yellow("\u25C7")} ${source_default.yellow(msg)}`),
8880
- error: (msg) => console.error(` ${source_default.red("\u2715")} ${source_default.red(msg)}`),
8881
- step: (msg) => console.log(` ${source_default.dim("\u203A")} ${msg}`),
8882
- blank: () => console.log(),
8883
- header: (msg) => console.log(
8884
- `
8885
- ${source_default.bold.white("\u25C6")} ${source_default.bold.white(msg.toUpperCase())}
8886
- `
8887
- )
8888
- };
8889
-
8890
- // src/cli/commands/init.ts
8891
- async function runInit(cwd2) {
8892
- log.header("ReEnd UI \u2014 Initialize");
8893
- if ((0, import_node_fs2.existsSync)((0, import_node_path2.join)(cwd2, "reend-ui.config.json"))) {
8894
- log.warn("reend-ui.config.json already exists.");
8895
- const { overwrite } = await (0, import_prompts.default)({
8896
- type: "confirm",
8897
- name: "overwrite",
8898
- message: "Overwrite existing config?",
8899
- initial: false
8900
- });
8901
- if (!overwrite) {
8902
- log.info("Init cancelled.");
8903
- return;
8904
- }
8905
- }
8906
- const framework = detectFramework(cwd2);
8907
- const isTypescript = detectTypeScript(cwd2);
8908
- const tailwindCfg = detectTailwindConfig(cwd2);
8909
- log.step(`Detected framework: ${source_default.cyan(framework)}`);
8910
- log.step(
8911
- `TypeScript: ${isTypescript ? source_default.green("yes") : source_default.yellow("no")}`
8912
- );
8913
- if (tailwindCfg) log.step(`Tailwind config: ${source_default.cyan(tailwindCfg)}`);
8914
- log.blank();
8915
- const response = await (0, import_prompts.default)([
8916
- {
8917
- type: "text",
8918
- name: "outputDir",
8919
- message: "Component output directory:",
8920
- initial: "components/ui"
8921
- },
8922
- {
8923
- type: "confirm",
8924
- name: "cssVariables",
8925
- message: "Copy CSS design tokens (variables.css)?",
8926
- initial: true
8927
- }
8928
- ]);
8929
- if (!response.outputDir) {
8930
- log.error("Init cancelled.");
8931
- return;
8932
- }
8933
- writeConfig(cwd2, {
8934
- version: "1.0",
8935
- framework,
8936
- typescript: isTypescript,
8937
- outputDir: response.outputDir,
8938
- cssVariables: response.cssVariables,
8939
- tailwindConfig: tailwindCfg
8940
- });
8941
- log.success("Created reend-ui.config.json");
8942
- const outDir = (0, import_node_path2.join)(cwd2, response.outputDir);
8943
- if (!(0, import_node_fs2.existsSync)(outDir)) {
8944
- (0, import_node_fs2.mkdirSync)(outDir, { recursive: true });
8945
- log.success(`Created directory: ${response.outputDir}`);
8946
- }
8947
- if (response.cssVariables) {
8948
- await copyVariablesCss(cwd2, response.outputDir);
8949
- }
8950
- log.blank();
8951
- console.log(source_default.bold.white(" \u25C6 NEXT STEPS\n"));
8952
- console.log(
8953
- source_default.dim(" Add the ReEnd Tailwind preset to your tailwind config:\n")
8954
- );
8955
- console.log(
8956
- source_default.cyan(` // ${tailwindCfg ?? "tailwind.config.ts"}`) + "\n" + source_default.white(" import reendPreset from 'reend-components/tailwind'\n") + source_default.white(" export default {\n") + source_default.white(" presets: [reendPreset],\n") + source_default.white(" // ...your config\n") + source_default.white(" }") + "\n"
8957
- );
8958
- console.log(source_default.dim(" Import CSS variables in your global CSS:\n"));
8959
- console.log(source_default.white(` @import 'reend-components/styles.css';`) + "\n");
8960
- console.log(source_default.dim(" Add components:\n"));
8961
- console.log(source_default.cyan(" npx reend-ui add button") + "\n");
8962
- }
8963
- async function copyVariablesCss(cwd2, outputDir) {
8964
- const stylesDir = (0, import_node_path2.join)(cwd2, "styles");
8965
- if (!(0, import_node_fs2.existsSync)(stylesDir)) {
8966
- (0, import_node_fs2.mkdirSync)(stylesDir, { recursive: true });
8967
- }
8968
- const destPath = (0, import_node_path2.join)(stylesDir, "reend-variables.css");
8969
- try {
8970
- const candidates = [
8971
- (0, import_node_path2.join)(
8972
- cwd2,
8973
- "node_modules",
8974
- "reend-components",
8975
- "src",
8976
- "styles",
8977
- "variables.css"
8978
- )
8979
- ];
8980
- for (const src of candidates) {
8981
- if ((0, import_node_fs2.existsSync)(src)) {
8982
- const content = (0, import_node_fs2.readFileSync)(src, "utf-8");
8983
- (0, import_node_fs2.writeFileSync)(destPath, content, "utf-8");
8984
- log.success("Copied variables.css \u2192 styles/reend-variables.css");
8985
- return;
8986
- }
8987
- }
8988
- const url = "https://raw.githubusercontent.com/VBeatDead/ReEnd-Components/main/src/styles/variables.css";
8989
- const res = await fetch(url);
8990
- if (res.ok) {
8991
- const content = await res.text();
8992
- (0, import_node_fs2.writeFileSync)(destPath, content, "utf-8");
8993
- log.success("Copied variables.css \u2192 styles/reend-variables.css");
8994
- } else {
8995
- log.warn(
8996
- "Could not copy variables.css \u2014 add manually from reend-components/styles.css"
8997
- );
8998
- }
8999
- } catch {
9000
- log.warn(
9001
- "Could not copy variables.css \u2014 add manually from reend-components/styles.css"
9002
- );
9003
- }
9004
- }
9005
-
9006
- // src/cli/commands/add.ts
9007
- var import_node_fs3 = require("node:fs");
9008
- var import_node_path3 = require("node:path");
9009
-
9010
8816
  // src/cli/registry.ts
8817
+ var CLI_VERSION = "1.2.0";
8818
+ var GITHUB_RAW_ROOT = "https://raw.githubusercontent.com/VBeatDead/ReEnd-Components";
8819
+ var BASE_DEPS = ["clsx", "tailwind-merge", "class-variance-authority"];
9011
8820
  var REGISTRY = {
9012
8821
  // ── Core (Tier 1) ───────────────────────────────────────────────────────
9013
8822
  button: {
@@ -9034,6 +8843,14 @@ var REGISTRY = {
9034
8843
  deps: [],
9035
8844
  description: "Corner bracket decoration with 6 sub-components"
9036
8845
  },
8846
+ "link-card": {
8847
+ name: "link-card",
8848
+ displayName: "LinkCard",
8849
+ type: "core",
8850
+ files: ["card.tsx"],
8851
+ deps: [],
8852
+ description: "Navigation card with icon, title, and animated arrow (\u2192). Uses clip-corner hover effect."
8853
+ },
9037
8854
  input: {
9038
8855
  name: "input",
9039
8856
  displayName: "Input",
@@ -9148,6 +8965,14 @@ var REGISTRY = {
9148
8965
  deps: ["@radix-ui/react-tooltip"],
9149
8966
  description: "Tooltip with Radix primitives"
9150
8967
  },
8968
+ sonner: {
8969
+ name: "sonner",
8970
+ displayName: "Sonner",
8971
+ type: "core",
8972
+ files: ["sonner.tsx"],
8973
+ deps: ["sonner"],
8974
+ description: "Toast notifications with notify helper, 5 types, auto-dismiss"
8975
+ },
9151
8976
  // ── Core (Tier 6 — Extended) ────────────────────────────────────────────
9152
8977
  skeleton: {
9153
8978
  name: "skeleton",
@@ -9155,7 +8980,7 @@ var REGISTRY = {
9155
8980
  type: "core",
9156
8981
  files: ["skeleton.tsx"],
9157
8982
  deps: [],
9158
- description: "SkeletonLine/Text/Avatar/Card with shimmer animation"
8983
+ description: "SkeletonLine/Text/Avatar/Card/Image/TableRow with shimmer animation"
9159
8984
  },
9160
8985
  "empty-state": {
9161
8986
  name: "empty-state",
@@ -9163,7 +8988,7 @@ var REGISTRY = {
9163
8988
  type: "core",
9164
8989
  files: ["empty-state.tsx"],
9165
8990
  deps: [],
9166
- description: "5 preset variants: search/error/permission/empty/default"
8991
+ description: "8 context variants: search/empty-list/no-notifications/error-loading/no-permission/empty-filter/inbox-zero/no-connection"
9167
8992
  },
9168
8993
  alert: {
9169
8994
  name: "alert",
@@ -9171,7 +8996,7 @@ var REGISTRY = {
9171
8996
  type: "core",
9172
8997
  files: ["alert.tsx"],
9173
8998
  deps: [],
9174
- description: "4 variants: info/success/warning/error, dismissible, ARIA"
8999
+ description: "4 variants: info/success/warning/error, dismissible, ARIA, TopBanner"
9175
9000
  },
9176
9001
  timeline: {
9177
9002
  name: "timeline",
@@ -9213,6 +9038,185 @@ var REGISTRY = {
9213
9038
  deps: [],
9214
9039
  description: "+/\u2212 stepper input with min/max/step, keyboard support"
9215
9040
  },
9041
+ // ── Core (P1 New — v1.1.0) ──────────────────────────────────────────────
9042
+ "view-toggle": {
9043
+ name: "view-toggle",
9044
+ displayName: "ViewToggle",
9045
+ type: "core",
9046
+ files: ["view-toggle.tsx"],
9047
+ deps: [],
9048
+ description: "Grid/List toggle with localStorage persistence"
9049
+ },
9050
+ "filter-bar": {
9051
+ name: "filter-bar",
9052
+ displayName: "FilterBar",
9053
+ type: "core",
9054
+ files: ["filter-bar.tsx"],
9055
+ deps: [],
9056
+ description: "Filter bar with chips and clear-all"
9057
+ },
9058
+ "otp-input": {
9059
+ name: "otp-input",
9060
+ displayName: "OTPInput",
9061
+ type: "core",
9062
+ files: ["otp-input.tsx"],
9063
+ deps: [],
9064
+ description: "OTP/PIN input with auto-advance, paste, shake-on-error"
9065
+ },
9066
+ "date-picker": {
9067
+ name: "date-picker",
9068
+ displayName: "DatePicker",
9069
+ type: "core",
9070
+ files: ["date-picker.tsx"],
9071
+ deps: ["@radix-ui/react-popover"],
9072
+ description: "Date picker with Endfield styling"
9073
+ },
9074
+ rating: {
9075
+ name: "rating",
9076
+ displayName: "Rating",
9077
+ type: "core",
9078
+ files: ["rating.tsx"],
9079
+ deps: [],
9080
+ description: "Star/diamond rating with \u25C6/\u25C7 indicators"
9081
+ },
9082
+ "session-timeout-modal": {
9083
+ name: "session-timeout-modal",
9084
+ displayName: "SessionTimeoutModal",
9085
+ type: "core",
9086
+ files: ["session-timeout-modal.tsx", "dialog.tsx", "button.tsx"],
9087
+ deps: ["@radix-ui/react-dialog", "@radix-ui/react-slot"],
9088
+ description: "Session expiry modal with CountdownTimer"
9089
+ },
9090
+ "file-upload": {
9091
+ name: "file-upload",
9092
+ displayName: "FileUpload",
9093
+ type: "core",
9094
+ files: ["file-upload.tsx"],
9095
+ deps: [],
9096
+ description: "Drag-and-drop file upload with 8 states"
9097
+ },
9098
+ "bottom-sheet": {
9099
+ name: "bottom-sheet",
9100
+ displayName: "BottomSheet",
9101
+ type: "core",
9102
+ files: ["bottom-sheet.tsx"],
9103
+ deps: [],
9104
+ description: "Mobile bottom sheet with slide-up animation"
9105
+ },
9106
+ carousel: {
9107
+ name: "carousel",
9108
+ displayName: "Carousel",
9109
+ type: "core",
9110
+ files: ["carousel.tsx"],
9111
+ deps: [],
9112
+ description: "CSS scroll-snap carousel with \u25C6/\u25C7 dots"
9113
+ },
9114
+ resizable: {
9115
+ name: "resizable",
9116
+ displayName: "ResizablePanels",
9117
+ type: "core",
9118
+ files: ["resizable.tsx"],
9119
+ deps: ["react-resizable-panels"],
9120
+ description: "Resizable panel layout with Endfield handle"
9121
+ },
9122
+ chart: {
9123
+ name: "chart",
9124
+ displayName: "Chart",
9125
+ type: "core",
9126
+ files: ["chart.tsx"],
9127
+ deps: ["recharts"],
9128
+ description: "Recharts wrapper with Endfield palette (--chart-1 to --chart-8)"
9129
+ },
9130
+ footer: {
9131
+ name: "footer",
9132
+ displayName: "Footer",
9133
+ type: "core",
9134
+ files: ["footer.tsx"],
9135
+ deps: [],
9136
+ description: "Footer with FooterColumn and FooterLink, 3-column grid, brand bar"
9137
+ },
9138
+ // ── Data Display ───────────────────────────────────────────────────────
9139
+ table: {
9140
+ name: "table",
9141
+ displayName: "Table",
9142
+ type: "core",
9143
+ files: ["table.tsx"],
9144
+ deps: [],
9145
+ description: "Data table with sortable columns, sticky header, hover/selected states, empty slot"
9146
+ },
9147
+ list: {
9148
+ name: "list",
9149
+ displayName: "List",
9150
+ type: "core",
9151
+ files: ["list.tsx"],
9152
+ deps: [],
9153
+ description: "Diamond bullet, numbered (01/02), description, and link list variants"
9154
+ },
9155
+ stat: {
9156
+ name: "stat",
9157
+ displayName: "Stat",
9158
+ type: "core",
9159
+ files: ["stat.tsx"],
9160
+ deps: [],
9161
+ description: "Stat/metric card with Orbitron value, trend indicators, StatGrid layout"
9162
+ },
9163
+ // ── Overlay & Utility (Part 7) ─────────────────────────────────────────
9164
+ "copy-clipboard": {
9165
+ name: "copy-clipboard",
9166
+ displayName: "CopyClipboard",
9167
+ type: "core",
9168
+ files: ["copy-clipboard.tsx"],
9169
+ deps: [],
9170
+ description: "Copy-to-clipboard button with \u2713 feedback and configurable reset delay"
9171
+ },
9172
+ "back-to-top": {
9173
+ name: "back-to-top",
9174
+ displayName: "BackToTop",
9175
+ type: "core",
9176
+ files: ["back-to-top.tsx"],
9177
+ deps: [],
9178
+ description: "Fixed back-to-top button with scroll threshold and smooth behavior"
9179
+ },
9180
+ "scroll-progress": {
9181
+ name: "scroll-progress",
9182
+ displayName: "ScrollProgress",
9183
+ type: "core",
9184
+ files: ["scroll-progress.tsx"],
9185
+ deps: [],
9186
+ description: "Fixed top progress bar tracking page scroll position (progressbar ARIA)"
9187
+ },
9188
+ "cookie-consent": {
9189
+ name: "cookie-consent",
9190
+ displayName: "CookieConsent",
9191
+ type: "core",
9192
+ files: ["cookie-consent.tsx"],
9193
+ deps: [],
9194
+ description: "Fixed bottom cookie consent banner with onAccept / onCustomize callbacks"
9195
+ },
9196
+ "command-palette": {
9197
+ name: "command-palette",
9198
+ displayName: "CommandPalette",
9199
+ type: "core",
9200
+ files: ["command-palette.tsx"],
9201
+ deps: [],
9202
+ description: "\u2318K command palette with search, grouped results, arrow-key navigation"
9203
+ },
9204
+ dropdown: {
9205
+ name: "dropdown",
9206
+ displayName: "Dropdown",
9207
+ type: "core",
9208
+ files: ["dropdown.tsx"],
9209
+ deps: ["@radix-ui/react-popover"],
9210
+ description: "Dropdown menu with grouped items and danger variant (Radix Popover base)"
9211
+ },
9212
+ "context-menu": {
9213
+ name: "context-menu",
9214
+ displayName: "ContextMenu",
9215
+ type: "core",
9216
+ files: ["context-menu.tsx"],
9217
+ deps: [],
9218
+ description: "Right-click context menu with keyboard shortcuts and portal rendering"
9219
+ },
9216
9220
  // ── Signature (Phase 0) ─────────────────────────────────────────────────
9217
9221
  "glitch-text": {
9218
9222
  name: "glitch-text",
@@ -9235,7 +9239,7 @@ var REGISTRY = {
9235
9239
  displayName: "TacticalPanel",
9236
9240
  type: "signature",
9237
9241
  files: ["signature/tactical-panel.tsx"],
9238
- deps: [],
9242
+ deps: ["lucide-react"],
9239
9243
  description: "HUD-style panel with status, headerAction, collapsible"
9240
9244
  },
9241
9245
  "holo-card": {
@@ -9243,7 +9247,7 @@ var REGISTRY = {
9243
9247
  displayName: "HoloCard",
9244
9248
  type: "signature",
9245
9249
  files: ["signature/holo-card.tsx"],
9246
- deps: ["framer-motion"],
9250
+ deps: [],
9247
9251
  description: "Holographic stat card with tilt hover effect"
9248
9252
  },
9249
9253
  "data-stream": {
@@ -9251,7 +9255,7 @@ var REGISTRY = {
9251
9255
  displayName: "DataStream",
9252
9256
  type: "signature",
9253
9257
  files: ["signature/data-stream.tsx"],
9254
- deps: [],
9258
+ deps: ["framer-motion", "lucide-react"],
9255
9259
  description: "Scrolling data feed terminal with speed/messageType"
9256
9260
  },
9257
9261
  "tactical-badge": {
@@ -9267,7 +9271,7 @@ var REGISTRY = {
9267
9271
  displayName: "WarningBanner",
9268
9272
  type: "signature",
9269
9273
  files: ["signature/warning-banner.tsx"],
9270
- deps: [],
9274
+ deps: ["lucide-react"],
9271
9275
  description: "Alert banner with caution/alert/critical severity"
9272
9276
  },
9273
9277
  "scan-divider": {
@@ -9366,11 +9370,298 @@ var REGISTRY = {
9366
9370
  files: ["signature/countdown-timer.tsx"],
9367
9371
  deps: [],
9368
9372
  description: "Tactical mission countdown, onComplete, 3 sizes"
9373
+ },
9374
+ "rich-text-editor": {
9375
+ name: "rich-text-editor",
9376
+ displayName: "RichTextEditor",
9377
+ type: "core",
9378
+ files: ["rich-text-editor.tsx"],
9379
+ deps: [],
9380
+ description: "WYSIWYG markdown editor with toolbar (B/I/U/S/H1-H3/quote/list/link/divider), character count, and MARKDOWN/PREVIEW toggle"
9381
+ },
9382
+ "sort-control": {
9383
+ name: "sort-control",
9384
+ displayName: "SortControl",
9385
+ type: "core",
9386
+ files: ["sort-control.tsx"],
9387
+ deps: [],
9388
+ description: "Sort options row with tri-state direction cycling (asc/desc/none) and \u25B2/\u25BC indicators"
9389
+ },
9390
+ "pull-to-refresh": {
9391
+ name: "pull-to-refresh",
9392
+ displayName: "PullToRefresh",
9393
+ type: "core",
9394
+ files: ["pull-to-refresh.tsx"],
9395
+ deps: [],
9396
+ description: "Mobile pull-to-refresh with diamond spinner, phase machine (idle\u2192pulling\u2192threshold\u2192refreshing\u2192complete), haptic feedback"
9397
+ },
9398
+ "swipeable-item": {
9399
+ name: "swipeable-item",
9400
+ displayName: "SwipeableItem",
9401
+ type: "core",
9402
+ files: ["swipeable-item.tsx"],
9403
+ deps: [],
9404
+ description: "Touch-swipeable list item with left/right action reveal, 80/160px thresholds, haptic feedback"
9405
+ },
9406
+ "spoiler-block": {
9407
+ name: "spoiler-block",
9408
+ displayName: "SpoilerBlock",
9409
+ type: "core",
9410
+ files: ["spoiler-block.tsx"],
9411
+ deps: [],
9412
+ description: "Spoiler content block with blur overlay and reveal toggle"
9413
+ },
9414
+ "theme-switcher": {
9415
+ name: "theme-switcher",
9416
+ displayName: "ThemeSwitcher",
9417
+ type: "core",
9418
+ files: ["theme-switcher.tsx"],
9419
+ deps: ["lucide-react"],
9420
+ description: "Light/dark theme toggle with localStorage persistence"
9421
+ },
9422
+ toast: {
9423
+ name: "toast",
9424
+ displayName: "Toast",
9425
+ type: "core",
9426
+ files: ["toast.tsx", "toaster.tsx"],
9427
+ hooks: ["use-toast.ts"],
9428
+ deps: ["@radix-ui/react-toast", "lucide-react"],
9429
+ description: "Radix toast primitives + Toaster portal + useToast hook (imperative API)"
9369
9430
  }
9370
9431
  };
9432
+ function getFileUrl(filePath, ref = "main") {
9433
+ return `${GITHUB_RAW_ROOT}/${ref}/src/components/ui/${filePath}`;
9434
+ }
9435
+ function getHookUrl(filePath, ref = "main") {
9436
+ return `${GITHUB_RAW_ROOT}/${ref}/src/hooks/${filePath}`;
9437
+ }
9438
+
9439
+ // src/cli/commands/init.ts
9440
+ var import_node_fs2 = require("node:fs");
9441
+ var import_node_path2 = require("node:path");
9442
+ var import_prompts = __toESM(require_prompts3(), 1);
9443
+
9444
+ // src/cli/utils.ts
9445
+ var import_node_fs = require("node:fs");
9446
+ var import_node_path = require("node:path");
9447
+ var CONFIG_FILE = "reend-ui.config.json";
9448
+ function readConfig(cwd2) {
9449
+ const configPath = (0, import_node_path.join)(cwd2, CONFIG_FILE);
9450
+ if (!(0, import_node_fs.existsSync)(configPath)) return null;
9451
+ try {
9452
+ return JSON.parse((0, import_node_fs.readFileSync)(configPath, "utf-8"));
9453
+ } catch {
9454
+ return null;
9455
+ }
9456
+ }
9457
+ function writeConfig(cwd2, config) {
9458
+ const configPath = (0, import_node_path.join)(cwd2, CONFIG_FILE);
9459
+ (0, import_node_fs.writeFileSync)(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
9460
+ }
9461
+ function detectFramework(cwd2) {
9462
+ const pkgPath = (0, import_node_path.join)(cwd2, "package.json");
9463
+ if (!(0, import_node_fs.existsSync)(pkgPath)) return "vite";
9464
+ try {
9465
+ const pkg = JSON.parse((0, import_node_fs.readFileSync)(pkgPath, "utf-8"));
9466
+ const allDeps = {
9467
+ ...pkg.dependencies,
9468
+ ...pkg.devDependencies
9469
+ };
9470
+ if (allDeps["next"]) return "next";
9471
+ if (allDeps["@remix-run/react"]) return "remix";
9472
+ if (allDeps["gatsby"]) return "gatsby";
9473
+ return "vite";
9474
+ } catch {
9475
+ return "vite";
9476
+ }
9477
+ }
9478
+ function detectTypeScript(cwd2) {
9479
+ return (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd2, "tsconfig.json")) || (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd2, "tsconfig.app.json"));
9480
+ }
9481
+ function detectTailwindConfig(cwd2) {
9482
+ const candidates = [
9483
+ "tailwind.config.ts",
9484
+ "tailwind.config.js",
9485
+ "tailwind.config.mjs"
9486
+ ];
9487
+ for (const f of candidates) {
9488
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(cwd2, f))) return f;
9489
+ }
9490
+ return void 0;
9491
+ }
9492
+ async function fetchText(url) {
9493
+ const res = await fetch(url);
9494
+ if (!res.ok) {
9495
+ throw new Error(`HTTP ${res.status} ${res.statusText} \u2014 ${url}`);
9496
+ }
9497
+ return res.text();
9498
+ }
9499
+ var log = {
9500
+ info: (msg) => console.log(` ${source_default.cyan("\u25C6")} ${msg}`),
9501
+ success: (msg) => console.log(` ${source_default.green("\u25C6")} ${source_default.green(msg)}`),
9502
+ warn: (msg) => console.log(` ${source_default.yellow("\u25C7")} ${source_default.yellow(msg)}`),
9503
+ error: (msg) => console.error(` ${source_default.red("\u2715")} ${source_default.red(msg)}`),
9504
+ step: (msg) => console.log(` ${source_default.dim("\u203A")} ${msg}`),
9505
+ blank: () => console.log(),
9506
+ header: (msg) => console.log(
9507
+ `
9508
+ ${source_default.bold.white("\u25C6")} ${source_default.bold.white(msg.toUpperCase())}
9509
+ `
9510
+ )
9511
+ };
9512
+
9513
+ // src/cli/commands/init.ts
9514
+ async function runInit(cwd2) {
9515
+ log.header("ReEnd UI \u2014 Initialize");
9516
+ if ((0, import_node_fs2.existsSync)((0, import_node_path2.join)(cwd2, "reend-ui.config.json"))) {
9517
+ log.warn("reend-ui.config.json already exists.");
9518
+ const { overwrite } = await (0, import_prompts.default)({
9519
+ type: "confirm",
9520
+ name: "overwrite",
9521
+ message: "Overwrite existing config?",
9522
+ initial: false
9523
+ });
9524
+ if (!overwrite) {
9525
+ log.info("Init cancelled.");
9526
+ return;
9527
+ }
9528
+ }
9529
+ const framework = detectFramework(cwd2);
9530
+ const isTypescript = detectTypeScript(cwd2);
9531
+ const tailwindCfg = detectTailwindConfig(cwd2);
9532
+ log.step(`Detected framework: ${source_default.cyan(framework)}`);
9533
+ log.step(
9534
+ `TypeScript: ${isTypescript ? source_default.green("yes") : source_default.yellow("no")}`
9535
+ );
9536
+ if (tailwindCfg) log.step(`Tailwind config: ${source_default.cyan(tailwindCfg)}`);
9537
+ log.blank();
9538
+ const response = await (0, import_prompts.default)([
9539
+ {
9540
+ type: "text",
9541
+ name: "outputDir",
9542
+ message: "Component output directory:",
9543
+ initial: "components/ui"
9544
+ },
9545
+ {
9546
+ type: "confirm",
9547
+ name: "cssVariables",
9548
+ message: "Copy CSS design tokens (variables.css)?",
9549
+ initial: true
9550
+ }
9551
+ ]);
9552
+ if (!response.outputDir) {
9553
+ log.error("Init cancelled.");
9554
+ return;
9555
+ }
9556
+ writeConfig(cwd2, {
9557
+ version: "1.0",
9558
+ framework,
9559
+ typescript: isTypescript,
9560
+ outputDir: response.outputDir,
9561
+ cssVariables: response.cssVariables,
9562
+ tailwindConfig: tailwindCfg
9563
+ });
9564
+ log.success("Created reend-ui.config.json");
9565
+ const outDir = (0, import_node_path2.join)(cwd2, response.outputDir);
9566
+ if (!(0, import_node_fs2.existsSync)(outDir)) {
9567
+ (0, import_node_fs2.mkdirSync)(outDir, { recursive: true });
9568
+ log.success(`Created directory: ${response.outputDir}`);
9569
+ }
9570
+ const projectBase = (0, import_node_path2.join)(outDir, "..", "..");
9571
+ createCnUtility(projectBase);
9572
+ if (response.cssVariables) {
9573
+ await copyVariablesCss(cwd2, projectBase);
9574
+ }
9575
+ log.blank();
9576
+ console.log(source_default.bold.white(" \u25C6 NEXT STEPS\n"));
9577
+ console.log(source_default.dim(" Install the base dependencies used by every component:\n"));
9578
+ console.log(
9579
+ source_default.cyan(" npm install clsx tailwind-merge class-variance-authority") + "\n"
9580
+ );
9581
+ console.log(
9582
+ source_default.dim(" Add the ReEnd Tailwind preset to your tailwind config:\n")
9583
+ );
9584
+ console.log(
9585
+ source_default.cyan(` // ${tailwindCfg ?? "tailwind.config.ts"}`) + "\n" + source_default.white(" import reendPreset from 'reend-components/tailwind'\n") + source_default.white(" export default {\n") + source_default.white(" presets: [reendPreset],\n") + source_default.white(" // ...your config\n") + source_default.white(" }") + "\n"
9586
+ );
9587
+ console.log(source_default.dim(" Import CSS variables in your global CSS:\n"));
9588
+ console.log(source_default.white(` @import 'reend-components/styles.css';`) + "\n");
9589
+ console.log(source_default.dim(" Add components:\n"));
9590
+ console.log(source_default.cyan(" npx reend-ui add button") + "\n");
9591
+ }
9592
+ var CN_UTILITY = `import { clsx, type ClassValue } from "clsx";
9593
+ import { twMerge } from "tailwind-merge";
9594
+
9595
+ export function cn(...inputs: ClassValue[]) {
9596
+ return twMerge(clsx(inputs));
9597
+ }
9598
+ `;
9599
+ function createCnUtility(projectBase) {
9600
+ const libDir = (0, import_node_path2.join)(projectBase, "lib");
9601
+ const destPath = (0, import_node_path2.join)(libDir, "utils.ts");
9602
+ if ((0, import_node_fs2.existsSync)(destPath)) {
9603
+ log.step("lib/utils.ts already exists \u2014 keeping yours");
9604
+ return;
9605
+ }
9606
+ if (!(0, import_node_fs2.existsSync)(libDir)) {
9607
+ (0, import_node_fs2.mkdirSync)(libDir, { recursive: true });
9608
+ }
9609
+ (0, import_node_fs2.writeFileSync)(destPath, CN_UTILITY, "utf-8");
9610
+ log.success("Created lib/utils.ts (cn helper)");
9611
+ }
9612
+ async function copyVariablesCss(cwd2, projectBase) {
9613
+ const stylesDir = (0, import_node_path2.join)(projectBase, "styles");
9614
+ if (!(0, import_node_fs2.existsSync)(stylesDir)) {
9615
+ (0, import_node_fs2.mkdirSync)(stylesDir, { recursive: true });
9616
+ }
9617
+ const destPath = (0, import_node_path2.join)(stylesDir, "reend-variables.css");
9618
+ try {
9619
+ const candidates = [
9620
+ (0, import_node_path2.join)(
9621
+ cwd2,
9622
+ "node_modules",
9623
+ "reend-components",
9624
+ "src",
9625
+ "styles",
9626
+ "variables.css"
9627
+ )
9628
+ ];
9629
+ for (const src of candidates) {
9630
+ if ((0, import_node_fs2.existsSync)(src)) {
9631
+ const content = (0, import_node_fs2.readFileSync)(src, "utf-8");
9632
+ (0, import_node_fs2.writeFileSync)(destPath, content, "utf-8");
9633
+ log.success("Copied variables.css \u2192 styles/reend-variables.css");
9634
+ return;
9635
+ }
9636
+ }
9637
+ const url = "https://raw.githubusercontent.com/VBeatDead/ReEnd-Components/main/src/styles/variables.css";
9638
+ const res = await fetch(url);
9639
+ if (res.ok) {
9640
+ const content = await res.text();
9641
+ (0, import_node_fs2.writeFileSync)(destPath, content, "utf-8");
9642
+ log.success("Copied variables.css \u2192 styles/reend-variables.css");
9643
+ } else {
9644
+ log.warn(
9645
+ "Could not copy variables.css \u2014 add manually from reend-components/styles.css"
9646
+ );
9647
+ }
9648
+ } catch {
9649
+ log.warn(
9650
+ "Could not copy variables.css \u2014 add manually from reend-components/styles.css"
9651
+ );
9652
+ }
9653
+ }
9371
9654
 
9372
9655
  // src/cli/commands/add.ts
9373
- var GITHUB_RAW_BASE = "https://raw.githubusercontent.com/VBeatDead/ReEnd-Components/main/src/components/ui";
9656
+ var import_node_fs3 = require("node:fs");
9657
+ var import_node_path3 = require("node:path");
9658
+ async function fetchRegistryFile(urlFor) {
9659
+ try {
9660
+ return await fetchText(urlFor(`v${CLI_VERSION}`));
9661
+ } catch {
9662
+ return fetchText(urlFor("main"));
9663
+ }
9664
+ }
9374
9665
  async function runAdd(components, cwd2, opts) {
9375
9666
  log.header("ReEnd UI \u2014 Add Components");
9376
9667
  const config = readConfig(cwd2);
@@ -9379,6 +9670,8 @@ async function runAdd(components, cwd2, opts) {
9379
9670
  process.exit(1);
9380
9671
  }
9381
9672
  const outputDir = (0, import_node_path3.join)(cwd2, config.outputDir);
9673
+ const projectBase = (0, import_node_path3.join)(outputDir, "..", "..");
9674
+ const hooksDir = (0, import_node_path3.join)(projectBase, "hooks");
9382
9675
  const depsToInstall = /* @__PURE__ */ new Set();
9383
9676
  const added = [];
9384
9677
  const skipped = [];
@@ -9391,28 +9684,41 @@ async function runAdd(components, cwd2, opts) {
9391
9684
  continue;
9392
9685
  }
9393
9686
  log.info(`Adding ${source_default.cyan(entry.displayName)}...`);
9687
+ const targets = [
9688
+ ...entry.files.map((file) => ({
9689
+ file,
9690
+ dest: (0, import_node_path3.join)(outputDir, file),
9691
+ hook: false
9692
+ })),
9693
+ ...(entry.hooks ?? []).map((file) => ({
9694
+ file,
9695
+ dest: (0, import_node_path3.join)(hooksDir, file),
9696
+ hook: true
9697
+ }))
9698
+ ];
9394
9699
  let componentSuccess = true;
9395
- for (const file of entry.files) {
9396
- const destPath = (0, import_node_path3.join)(outputDir, file);
9397
- const destDir = (0, import_node_path3.dirname)(destPath);
9398
- if ((0, import_node_fs3.existsSync)(destPath) && !opts.overwrite) {
9399
- log.warn(` ${file} already exists \u2014 use --overwrite to replace`);
9700
+ for (const { file, dest, hook } of targets) {
9701
+ const destDir = (0, import_node_path3.dirname)(dest);
9702
+ const label = hook ? `hooks/${file}` : file;
9703
+ if ((0, import_node_fs3.existsSync)(dest) && !opts.overwrite) {
9704
+ log.warn(` ${label} already exists \u2014 use --overwrite to replace`);
9400
9705
  skipped.push(entry.displayName);
9401
9706
  componentSuccess = false;
9402
9707
  continue;
9403
9708
  }
9404
9709
  try {
9405
- const url = `${GITHUB_RAW_BASE}/${file}`;
9406
- log.step(`Fetching ${file}...`);
9407
- const content = await fetchText(url);
9710
+ log.step(`Fetching ${label}...`);
9711
+ const content = await fetchRegistryFile(
9712
+ (ref) => hook ? getHookUrl(file, ref) : getFileUrl(file, ref)
9713
+ );
9408
9714
  if (!(0, import_node_fs3.existsSync)(destDir)) {
9409
9715
  (0, import_node_fs3.mkdirSync)(destDir, { recursive: true });
9410
9716
  }
9411
- (0, import_node_fs3.writeFileSync)(destPath, content, "utf-8");
9412
- log.success(` Added \u2192 ${config.outputDir}/${file}`);
9717
+ (0, import_node_fs3.writeFileSync)(dest, content, "utf-8");
9718
+ log.success(` Added \u2192 ${label}`);
9413
9719
  } catch (err) {
9414
9720
  const message = err instanceof Error ? err.message : String(err);
9415
- log.error(` Failed to fetch ${file}: ${message}`);
9721
+ log.error(` Failed to fetch ${label}: ${message}`);
9416
9722
  componentSuccess = false;
9417
9723
  failed.push(entry.displayName);
9418
9724
  }
@@ -9434,22 +9740,21 @@ async function runAdd(components, cwd2, opts) {
9434
9740
  if (failed.length > 0) {
9435
9741
  log.error(`Failed: ${failed.join(", ")}`);
9436
9742
  }
9743
+ if (added.length > 0) {
9744
+ for (const dep of BASE_DEPS) {
9745
+ depsToInstall.add(dep);
9746
+ }
9747
+ }
9437
9748
  if (depsToInstall.size > 0) {
9438
9749
  const deps = Array.from(depsToInstall).join(" ");
9439
9750
  log.blank();
9440
- console.log(source_default.bold.yellow(" \u26A1 Install peer dependencies:\n"));
9751
+ console.log(source_default.bold.yellow(" \u26A1 Install dependencies:\n"));
9441
9752
  console.log(source_default.cyan(` npm install ${deps}`) + "\n");
9442
9753
  }
9443
- if (added.length > 0) {
9754
+ if (added.length > 0 && !(0, import_node_fs3.existsSync)((0, import_node_path3.join)(projectBase, "lib", "utils.ts"))) {
9444
9755
  log.blank();
9445
- console.log(
9446
- source_default.dim(
9447
- " Components require the cn() utility. Add to your project:\n"
9448
- )
9449
- );
9450
- console.log(source_default.white(" import { cn } from 'reend-components'") + "\n");
9451
- console.log(
9452
- source_default.dim(" Or copy the cn utility from: lib/utils.ts") + "\n"
9756
+ log.warn(
9757
+ "lib/utils.ts (cn helper) not found \u2014 run `npx reend-ui init` to create it."
9453
9758
  );
9454
9759
  }
9455
9760
  }
@@ -9503,10 +9808,9 @@ async function runUpdate(components, cwd2) {
9503
9808
  const outputDir = (0, import_node_path4.join)(cwd2, config.outputDir);
9504
9809
  let targets = components;
9505
9810
  if (targets.length === 0) {
9506
- targets = Object.keys(REGISTRY).filter((name) => {
9507
- const entry = REGISTRY[name];
9508
- return entry.files.some((f) => (0, import_node_fs4.existsSync)((0, import_node_path4.join)(outputDir, f)));
9509
- });
9811
+ targets = Object.keys(REGISTRY).filter(
9812
+ (name) => (0, import_node_fs4.existsSync)((0, import_node_path4.join)(outputDir, REGISTRY[name].files[0]))
9813
+ );
9510
9814
  if (targets.length === 0) {
9511
9815
  log.warn("No installed components found. Use: npx reend-ui add <name>");
9512
9816
  return;
@@ -9523,7 +9827,7 @@ async function runUpdate(components, cwd2) {
9523
9827
  log.error(`Unknown component: "${name}"`);
9524
9828
  continue;
9525
9829
  }
9526
- const isInstalled = entry.files.some((f) => (0, import_node_fs4.existsSync)((0, import_node_path4.join)(outputDir, f)));
9830
+ const isInstalled = (0, import_node_fs4.existsSync)((0, import_node_path4.join)(outputDir, entry.files[0]));
9527
9831
  if (isInstalled) {
9528
9832
  existing.push(name);
9529
9833
  } else {
@@ -9547,7 +9851,7 @@ program2.name("reend-ui").description(
9547
9851
  source_default.bold.white(
9548
9852
  "\u25C6 ReEnd UI \u2014 Arknights: Endfield React Component Library\n"
9549
9853
  ) + source_default.dim(" Shadcn-style copy-paste workflow for ReEnd components.\n")
9550
- ).version("1.0.0");
9854
+ ).version(CLI_VERSION);
9551
9855
  program2.command("init").description("Initialize ReEnd UI in your project").action(async () => {
9552
9856
  await runInit(cwd);
9553
9857
  });