@salesforce/webapp-template-feature-react-file-upload-experimental 1.60.0 → 1.60.2

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 (35) hide show
  1. package/README.md +97 -11
  2. package/dist/CHANGELOG.md +16 -0
  3. package/dist/force-app/main/default/webapplications/feature-react-file-upload/package-lock.json +15 -15
  4. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/api/fileUpload.ts +4 -12
  5. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUpload.tsx +20 -3
  6. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadDialog.tsx +2 -2
  7. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadDropZone.tsx +48 -40
  8. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadFileItem.tsx +1 -1
  9. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadIcons.tsx +2 -2
  10. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/hooks/useFileUpload.ts +36 -23
  11. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts +18 -0
  12. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/pages/Home.tsx +10 -23
  13. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/pages/UploadTest.tsx +56 -0
  14. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/routes.tsx +8 -3
  15. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/types/fileUpload.ts +2 -0
  16. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/utils/fileUploadUtils.ts +12 -2
  17. package/dist/force-app/main/default/webapplications/feature-react-file-upload/src/utils/labels.ts +2 -0
  18. package/dist/package.json +1 -1
  19. package/package.json +3 -3
  20. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/api/fileUpload.ts +4 -12
  21. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUpload.tsx +20 -3
  22. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadDialog.tsx +2 -2
  23. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadDropZone.tsx +48 -40
  24. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadFileItem.tsx +1 -1
  25. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/FileUploadIcons.tsx +2 -2
  26. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/__inherit__button.tsx +45 -0
  27. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/components/ui/__inherit__dialog.tsx +102 -0
  28. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/hooks/useFileUpload.ts +36 -23
  29. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/index.ts +18 -0
  30. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/pages/UploadTest.tsx +56 -0
  31. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/routes.tsx +8 -3
  32. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/types/fileUpload.ts +2 -0
  33. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/utils/fileUploadUtils.ts +12 -2
  34. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/utils/labels.ts +2 -0
  35. package/src/force-app/main/default/webapplications/feature-react-file-upload/src/pages/Home.tsx +0 -25
@@ -1,25 +1,12 @@
1
- import { FileUpload } from "../components/FileUpload";
2
-
3
- /**
4
- * Home page for testing the file-upload feature standalone.
5
- * Renders FileUpload for dialog-based file upload with progress.
6
- */
7
1
  export default function Home() {
8
- return (
9
- <div className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-12 space-y-16">
10
- <section>
11
- <h2 className="text-2xl font-bold text-gray-900 mb-2">File Upload (Dialog + Progress)</h2>
12
- <p className="text-gray-600 mb-8">
13
- Choose files to open a dialog showing upload status with progress bar for each file.
14
- </p>
15
- <FileUpload
16
- multiple
17
- onUploadComplete={(files) => {
18
- // eslint-disable-next-line no-console
19
- console.log("Uploaded files:", files);
20
- }}
21
- />
22
- </section>
23
- </div>
24
- );
2
+ return (
3
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
4
+ <div className="text-center">
5
+ <h1 className="text-4xl font-bold text-gray-900 mb-4">Home</h1>
6
+ <p className="text-lg text-gray-600 mb-8">
7
+ Welcome to your React application.
8
+ </p>
9
+ </div>
10
+ </div>
11
+ );
25
12
  }
@@ -0,0 +1,56 @@
1
+ import { FileUpload } from "../components/FileUpload";
2
+ import { createContentVersion, getCurrentUserId } from "../api/fileUpload";
3
+
4
+ /**
5
+ * Upload test page for testing the file-upload feature standalone.
6
+ * Renders FileUpload for dialog-based file upload with progress.
7
+ * Demonstrates manual ContentVersion creation after file upload.
8
+ */
9
+ export default function UploadTest() {
10
+ const handleUploadComplete = async (
11
+ files: { name: string; size: number; contentBodyId: string; contentVersionId?: string }[],
12
+ ) => {
13
+ // eslint-disable-next-line no-console
14
+ console.log("Uploaded files:", JSON.stringify(files, null, 2));
15
+
16
+ try {
17
+ const userId = await getCurrentUserId();
18
+ // eslint-disable-next-line no-console
19
+ console.log("Current user ID:", userId);
20
+
21
+ for (const uploadedFile of files) {
22
+ // Create a File object from the uploaded file info (for API signature)
23
+ const file = new File([""], uploadedFile.name);
24
+ const contentVersionId = await createContentVersion(
25
+ file,
26
+ uploadedFile.contentBodyId,
27
+ userId,
28
+ );
29
+ // eslint-disable-next-line no-console
30
+ console.log(`Created ContentVersion for ${uploadedFile.name}:`, contentVersionId);
31
+ }
32
+ } catch (error) {
33
+ // eslint-disable-next-line no-console
34
+ console.error("Error creating ContentVersion:", error);
35
+ }
36
+ };
37
+
38
+ return (
39
+ <div className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-12 space-y-16">
40
+ <section>
41
+ <h2 className="text-2xl font-bold text-gray-900 mb-2">File Upload (Dialog + Progress)</h2>
42
+ <p className="text-gray-600 mb-8">
43
+ Choose files to open a dialog showing upload status with progress bar for each file.
44
+ ContentVersion is created manually after upload using current user ID.
45
+ </p>
46
+ <FileUpload
47
+ multiple
48
+ accept="image/jpeg,.jpg,.jpeg,application/pdf,.pdf"
49
+ maxFileSize={50}
50
+ formatHint="JPEG and PDF formats, up to 50MB"
51
+ onUploadComplete={handleUploadComplete}
52
+ />
53
+ </section>
54
+ </div>
55
+ );
56
+ }
@@ -1,7 +1,8 @@
1
1
  import type { RouteObject } from 'react-router';
2
2
  import AppLayout from './appLayout';
3
- import Home from './pages/Home';
4
3
  import NotFound from './pages/NotFound';
4
+ import { Navigate } from "react-router";
5
+ import UploadTest from "./pages/UploadTest";
5
6
 
6
7
  export const routes: RouteObject[] = [
7
8
  {
@@ -10,12 +11,16 @@ export const routes: RouteObject[] = [
10
11
  children: [
11
12
  {
12
13
  index: true,
13
- element: <Home />,
14
- handle: { showInNavigation: true, label: "Home" }
14
+ element: <Navigate to="/upload-test" replace />
15
15
  },
16
16
  {
17
17
  path: '*',
18
18
  element: <NotFound />
19
+ },
20
+ {
21
+ path: "upload-test",
22
+ element: <UploadTest />,
23
+ handle: { showInNavigation: true, label: "Upload Test" }
19
24
  }
20
25
  ]
21
26
  }
@@ -16,11 +16,13 @@ export interface FileUploadItem {
16
16
  state: UploadState;
17
17
  progress: number;
18
18
  error?: string;
19
+ contentBodyId?: string;
19
20
  contentVersionId?: string;
20
21
  }
21
22
 
22
23
  export interface UploadedFile {
23
24
  name: string;
24
25
  size: number;
26
+ contentBodyId: string;
25
27
  contentVersionId?: string;
26
28
  }
@@ -7,8 +7,18 @@ import type { UploadState } from "../types/fileUpload";
7
7
  /** Maximum allowed file size: 2 GB */
8
8
  export const MAX_FILE_SIZE_BYTES = 2 * 1024 * 1024 * 1024;
9
9
 
10
- export function isFileTooLarge(file: File): boolean {
11
- return file.size > MAX_FILE_SIZE_BYTES;
10
+ /** Convert megabytes to bytes */
11
+ export function bytesFromMB(mb: number): number {
12
+ return mb * 1024 * 1024;
13
+ }
14
+
15
+ /**
16
+ * Check if file exceeds size limit.
17
+ * @param file - File to check
18
+ * @param maxBytes - Max size in bytes. Defaults to MAX_FILE_SIZE_BYTES when omitted.
19
+ */
20
+ export function isFileTooLarge(file: File, maxBytes: number = MAX_FILE_SIZE_BYTES): boolean {
21
+ return file.size > maxBytes;
12
22
  }
13
23
 
14
24
  const UPLOADING_STATES: UploadState[] = ["loading_config", "uploading", "creating_record"];
@@ -4,6 +4,8 @@ export const LABELS = {
4
4
  uploadFiles: "Upload Files",
5
5
  dropFilesHere: "Drop files here",
6
6
  orDropFiles: "Or drop files",
7
+ chooseFileOrDrop: "Choose a file or drag & drop for reference here.",
8
+ formatHint: "JPEG, PNG, PDF, and MP4 formats, up to 50MB",
7
9
  uploadFilesDialogTitle: "Upload Files",
8
10
  done: "Done",
9
11
  cancelled: "Cancelled",
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
3
- "version": "1.60.0",
3
+ "version": "1.60.2",
4
4
  "description": "Base SFDX project template",
5
5
  "private": true,
6
6
  "files": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-feature-react-file-upload-experimental",
3
- "version": "1.60.0",
3
+ "version": "1.60.2",
4
4
  "description": "File upload feature with a component to upload files to core",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",
@@ -41,7 +41,7 @@
41
41
  }
42
42
  },
43
43
  "devDependencies": {
44
- "@salesforce/webapp-experimental": "^1.60.0",
44
+ "@salesforce/webapp-experimental": "^1.60.2",
45
45
  "@types/react": "^19.2.7",
46
46
  "@types/react-dom": "^19.2.3",
47
47
  "nodemon": "^3.1.0",
@@ -49,5 +49,5 @@
49
49
  "react-router": "^7.10.1",
50
50
  "vite": "^7.3.1"
51
51
  },
52
- "gitHead": "c8b46bfc786c8d2c3e04bf8a179fc201a8e6a3c5"
52
+ "gitHead": "abc41ea9a3be79240ce212be95208320d197d14a"
53
53
  }
@@ -3,16 +3,8 @@
3
3
  */
4
4
 
5
5
  import { createRecord, getCurrentUser } from "@salesforce/webapp-experimental/api";
6
- import { createDataSDK, type DataSDK } from "@salesforce/sdk-data";
7
6
 
8
- // Lazy-initialized SDK instance
9
- let sdkInstance: DataSDK | null = null;
10
- async function getSDK() {
11
- if (!sdkInstance) {
12
- sdkInstance = await createDataSDK();
13
- }
14
- return sdkInstance;
15
- }
7
+ import { getDataSDK } from "@salesforce/sdk-data";
16
8
 
17
9
  declare const __SF_API_VERSION__: string;
18
10
 
@@ -25,7 +17,7 @@ export interface UploadConfig {
25
17
  * Get upload config (token, uploadUrl) from /connect/file/upload/config.
26
18
  */
27
19
  export async function getUploadConfig(): Promise<UploadConfig> {
28
- const sdk = await getSDK();
20
+ const sdk = await getDataSDK();
29
21
  if (!sdk?.fetch) {
30
22
  throw new Error("Failed to initialize SDK");
31
23
  }
@@ -120,10 +112,10 @@ function parseJsonResponse(text: string): Record<string, unknown> {
120
112
  export async function createContentVersion(
121
113
  file: File,
122
114
  contentBodyId: string,
123
- userId: string,
115
+ recordId: string,
124
116
  ): Promise<string | undefined> {
125
117
  const fields = {
126
- FirstPublishLocationId: userId,
118
+ FirstPublishLocationId: recordId,
127
119
  Title: fileNameWithoutExtension(file.name),
128
120
  PathOnClient: file.name,
129
121
  ContentBodyId: contentBodyId,
@@ -9,13 +9,21 @@ export interface FileUploadProps {
9
9
  accept?: string;
10
10
  /** Whether to allow multiple file selection. Default: false */
11
11
  multiple?: boolean;
12
- /** Record Id for FirstPublishLocationId (e.g. Account, Opportunity). When provided, files are linked to this record. Otherwise, current user Id is used. */
12
+ /** Record Id for FirstPublishLocationId (e.g. Account, Opportunity). When provided, files are linked to this record and ContentVersion is created. When null/undefined, only uploads file and returns contentBodyId without creating ContentVersion. */
13
13
  recordId?: string;
14
- /** Called when uploads complete. Receives array of successfully uploaded files with name, size, and contentVersionId. */
15
- onUploadComplete?: (files: { name: string; size: number; contentVersionId?: string }[]) => void;
14
+ /** Called when uploads complete. Receives array of successfully uploaded files with name, size, contentBodyId, and contentVersionId (if ContentVersion was created). */
15
+ onUploadComplete?: (
16
+ files: { name: string; size: number; contentBodyId: string; contentVersionId?: string }[],
17
+ ) => void;
16
18
  onUploadError?: (file: File, error: string) => void;
17
19
  /** Optional CSS class for the wrapper div */
18
20
  className?: string;
21
+ /** Optional CSS class for the drop zone (e.g. "h-full" for flex layouts) */
22
+ dropZoneClassName?: string;
23
+ /** Optional format hint for drop zone (e.g. "JPEG, PNG, PDF, and MP4 formats, up to 50MB") */
24
+ formatHint?: string;
25
+ /** Maximum file size in MB. Files exceeding this limit are rejected with an error. Omit for default (2 GB). */
26
+ maxFileSize?: number;
19
27
  }
20
28
 
21
29
  /**
@@ -30,6 +38,9 @@ export function FileUpload({
30
38
  onUploadComplete,
31
39
  onUploadError,
32
40
  className = "",
41
+ dropZoneClassName = "",
42
+ formatHint,
43
+ maxFileSize,
33
44
  }: FileUploadProps) {
34
45
  const { fileItems, getInputProps, getDropZoneProps, isDragging, reset, cancelFile, allDone } =
35
46
  useFileUpload({
@@ -38,6 +49,7 @@ export function FileUpload({
38
49
  recordId,
39
50
  onUploadComplete,
40
51
  onUploadError,
52
+ maxFileSize,
41
53
  });
42
54
 
43
55
  const { dialogOpen, uploadedFileNames, handleOpenChange } = useFileUploadDialog({
@@ -45,6 +57,9 @@ export function FileUpload({
45
57
  reset,
46
58
  });
47
59
 
60
+ const effectiveFormatHint =
61
+ formatHint ?? (maxFileSize != null ? `Up to ${maxFileSize}MB` : undefined);
62
+
48
63
  const inputProps = getInputProps();
49
64
  const dropZoneProps = getDropZoneProps();
50
65
 
@@ -55,6 +70,8 @@ export function FileUpload({
55
70
  inputProps={inputProps}
56
71
  dropZoneProps={dropZoneProps}
57
72
  isDragging={isDragging}
73
+ formatHint={effectiveFormatHint}
74
+ className={dropZoneClassName}
58
75
  />
59
76
 
60
77
  <FileUploadDialog
@@ -1,12 +1,12 @@
1
+ import { Button } from "./ui/__inherit__button";
1
2
  import {
2
- Button,
3
3
  Dialog,
4
4
  DialogClose,
5
5
  DialogContent,
6
6
  DialogFooter,
7
7
  DialogHeader,
8
8
  DialogTitle,
9
- } from "@/components/ui";
9
+ } from "./ui/__inherit__dialog";
10
10
  import { FileUploadFileItem } from "./FileUploadFileItem";
11
11
  import { formatUploadSummary } from "../utils/fileUploadUtils";
12
12
  import { LABELS } from "../utils/labels";
@@ -1,6 +1,7 @@
1
- import { UtilityIcon } from "./FileUploadIcons";
2
1
  import { LABELS } from "../utils/labels";
3
2
 
3
+ import { UtilityIcon } from "./FileUploadIcons";
4
+
4
5
  export interface FileUploadDropZoneProps {
5
6
  /** Props for the hidden file input (ref, type, accept, multiple, onChange) */
6
7
  inputProps: {
@@ -20,63 +21,70 @@ export interface FileUploadDropZoneProps {
20
21
  };
21
22
  /** Whether the user is currently dragging over the drop zone */
22
23
  isDragging: boolean;
24
+ /** Optional format hint (e.g. "JPEG, PNG, PDF, and MP4 formats, up to 50MB"). Defaults to LABELS.formatHint. */
25
+ formatHint?: string;
26
+ /** Optional CSS class for the drop zone (e.g. "h-full" for flex layouts) */
27
+ className?: string;
23
28
  }
24
29
 
25
30
  const DROP_ZONE_BASE_CLASSES =
26
- "mb-2 flex cursor-pointer flex-wrap items-center gap-3 rounded-[4px] border border-dashed p-1 transition-colors";
31
+ "mb-2 flex cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed bg-white px-6 pt-4 pb-8 transition-colors";
27
32
  const DROP_ZONE_DRAGGING_CLASSES = "border-blue-500 bg-blue-50";
28
- const DROP_ZONE_IDLE_CLASSES = "hover:bg-gray-50";
29
- const BORDER_COLOR_IDLE = "rgb(116, 116, 116)";
33
+ const DROP_ZONE_IDLE_CLASSES = "border-gray-300 hover:border-gray-400 hover:bg-gray-50";
30
34
 
31
35
  /**
32
- * Drop zone for file selection. Renders a dashed border area with "Upload Files"
33
- * button and "Or drop files" / "Drop files here" text. Accepts click and drag-and-drop.
36
+ * Drop zone for file selection. Renders a dashed border area with document icon,
37
+ * primary instruction text, and format hint. Accepts click and drag-and-drop.
34
38
  * Uses a hidden file input; props come from useFileUpload.
35
39
  */
36
40
  export function FileUploadDropZone({
37
41
  inputProps,
38
42
  dropZoneProps,
39
43
  isDragging,
44
+ formatHint = LABELS.formatHint,
45
+ className = "",
40
46
  }: FileUploadDropZoneProps) {
41
47
  const dropZoneClassName = [
42
48
  DROP_ZONE_BASE_CLASSES,
43
49
  isDragging ? DROP_ZONE_DRAGGING_CLASSES : DROP_ZONE_IDLE_CLASSES,
44
- ].join(" ");
50
+ className,
51
+ ]
52
+ .filter(Boolean)
53
+ .join(" ");
45
54
 
46
55
  return (
47
- <>
48
- <p className="mb-1 text-sm font-semibold text-gray-900">{LABELS.attach}</p>
49
- <div
50
- role="button"
51
- tabIndex={0}
52
- onClick={dropZoneProps.onClick}
53
- onDragOver={dropZoneProps.onDragOver}
54
- onDragLeave={dropZoneProps.onDragLeave}
55
- onDrop={dropZoneProps.onDrop}
56
- onKeyDown={dropZoneProps.onKeyDown}
57
- className={dropZoneClassName}
58
- style={!isDragging ? { borderColor: BORDER_COLOR_IDLE } : undefined}
59
- aria-label={LABELS.dropZone}
60
- data-testid="file-upload-drop-zone"
56
+ <div
57
+ role="button"
58
+ tabIndex={0}
59
+ onClick={dropZoneProps.onClick}
60
+ onDragOver={dropZoneProps.onDragOver}
61
+ onDragLeave={dropZoneProps.onDragLeave}
62
+ onDrop={dropZoneProps.onDrop}
63
+ onKeyDown={dropZoneProps.onKeyDown}
64
+ className={dropZoneClassName}
65
+ aria-label={LABELS.dropZone}
66
+ data-testid="file-upload-drop-zone"
67
+ >
68
+ <input
69
+ id="file-upload-input-id"
70
+ ref={inputProps.ref}
71
+ type="file"
72
+ accept={inputProps.accept}
73
+ multiple={inputProps.multiple}
74
+ onChange={inputProps.onChange}
75
+ className="sr-only"
76
+ aria-hidden
77
+ />
78
+ <span
79
+ className="inline-flex h-12 w-12 shrink-0 items-center justify-center text-gray-500"
80
+ aria-hidden
61
81
  >
62
- <input
63
- id="file-upload-input-id"
64
- ref={inputProps.ref}
65
- type="file"
66
- accept={inputProps.accept}
67
- multiple={inputProps.multiple}
68
- onChange={inputProps.onChange}
69
- className="sr-only"
70
- aria-hidden
71
- />
72
- <span className="inline-flex items-center gap-2 rounded-[4px] border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-blue-600 hover:bg-gray-50">
73
- <UtilityIcon id="upload" size="sm" />
74
- {LABELS.uploadFiles}
75
- </span>
76
- <span className="text-sm text-gray-900">
77
- {isDragging ? LABELS.dropFilesHere : LABELS.orDropFiles}
78
- </span>
79
- </div>
80
- </>
82
+ <UtilityIcon id="image" />
83
+ </span>
84
+ <p className="text-center text-sm font-medium text-gray-900">
85
+ {isDragging ? LABELS.dropFilesHere : LABELS.chooseFileOrDrop}
86
+ </p>
87
+ <p className="text-center text-xs text-gray-500">{formatHint}</p>
88
+ </div>
81
89
  );
82
90
  }
@@ -1,4 +1,4 @@
1
- import { Button } from "@/components/ui";
1
+ import { Button } from "./ui/__inherit__button";
2
2
  import { FileTypeIcon, UtilityIcon } from "./FileUploadIcons";
3
3
  import {
4
4
  formatFileSize,
@@ -1,6 +1,6 @@
1
- import symbolsUrl from "@assets/symbols.svg?url";
1
+ import symbolsUrl from "../assets/symbols.svg?url";
2
2
 
3
- import utilitySvg from "@assets/utility.svg?url";
3
+ import utilitySvg from "../assets/utility.svg?url";
4
4
 
5
5
  const IMAGE_EXTENSIONS = new Set([
6
6
  "jpg",
@@ -0,0 +1,45 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ const buttonVariants = cva(
5
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
6
+ {
7
+ variants: {
8
+ variant: {
9
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
10
+ destructive:
11
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
12
+ outline:
13
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
14
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
15
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
16
+ link: "text-primary underline-offset-4 hover:underline",
17
+ },
18
+ size: {
19
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
20
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
21
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
22
+ icon: "size-9",
23
+ },
24
+ },
25
+ defaultVariants: {
26
+ variant: "default",
27
+ size: "default",
28
+ },
29
+ },
30
+ );
31
+
32
+ function Button({
33
+ className,
34
+ variant,
35
+ size,
36
+ asChild = false,
37
+ ...props
38
+ }: React.ComponentProps<"button"> &
39
+ VariantProps<typeof buttonVariants> & {
40
+ asChild?: boolean;
41
+ }) {
42
+ return <> </>;
43
+ }
44
+
45
+ export { Button, buttonVariants };
@@ -0,0 +1,102 @@
1
+ import * as React from "react";
2
+ import { Dialog as DialogPrimitive } from "radix-ui";
3
+
4
+ import { Button } from "./__inherit__button";
5
+ import { XIcon } from "lucide-react";
6
+
7
+ function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
8
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
9
+ }
10
+
11
+ function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
12
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
13
+ }
14
+
15
+ function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
16
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
17
+ }
18
+
19
+ function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
20
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
21
+ }
22
+
23
+ function DialogOverlay({
24
+ className,
25
+ ...props
26
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
27
+ return <DialogPrimitive.Overlay data-slot="dialog-overlay" {...props} />;
28
+ }
29
+
30
+ function DialogContent({
31
+ className,
32
+ children,
33
+ showCloseButton = true,
34
+ ...props
35
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
36
+ showCloseButton?: boolean;
37
+ }) {
38
+ return (
39
+ <DialogPortal>
40
+ <DialogOverlay />
41
+ <DialogPrimitive.Content data-slot="dialog-content" {...props}>
42
+ {children}
43
+ {showCloseButton && (
44
+ <DialogPrimitive.Close data-slot="dialog-close" asChild>
45
+ <Button variant="ghost" className="absolute top-2 right-2" size="icon-sm">
46
+ <XIcon />
47
+ <span className="sr-only">Close</span>
48
+ </Button>
49
+ </DialogPrimitive.Close>
50
+ )}
51
+ </DialogPrimitive.Content>
52
+ </DialogPortal>
53
+ );
54
+ }
55
+
56
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
57
+ return <div data-slot="dialog-header" {...props} />;
58
+ }
59
+
60
+ function DialogFooter({
61
+ className,
62
+ showCloseButton = false,
63
+ children,
64
+ ...props
65
+ }: React.ComponentProps<"div"> & {
66
+ showCloseButton?: boolean;
67
+ }) {
68
+ return (
69
+ <div data-slot="dialog-footer" {...props}>
70
+ {children}
71
+ {showCloseButton && (
72
+ <DialogPrimitive.Close asChild>
73
+ <Button variant="outline">Close</Button>
74
+ </DialogPrimitive.Close>
75
+ )}
76
+ </div>
77
+ );
78
+ }
79
+
80
+ function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
81
+ return <DialogPrimitive.Title data-slot="dialog-title" {...props} />;
82
+ }
83
+
84
+ function DialogDescription({
85
+ className,
86
+ ...props
87
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
88
+ return <DialogPrimitive.Description data-slot="dialog-description" {...props} />;
89
+ }
90
+
91
+ export {
92
+ Dialog,
93
+ DialogClose,
94
+ DialogContent,
95
+ DialogDescription,
96
+ DialogFooter,
97
+ DialogHeader,
98
+ DialogOverlay,
99
+ DialogPortal,
100
+ DialogTitle,
101
+ DialogTrigger,
102
+ };