mirror repo (WIP)

Need to remove secrets before entire repo can be made public.
This commit is contained in:
Brandon Thomas
2025-09-18 22:55:03 -04:00
commit 0daf83dba9
8735 changed files with 2963896 additions and 0 deletions
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
@@ -0,0 +1,7 @@
# @storyteller/ui-image-input
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test @storyteller/ui-image-input` to execute the unit tests via [Vitest](https://vitest.dev/).
+2
View File
@@ -0,0 +1,2 @@
export * from './lib/image-input';
//# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,mBAAmB,CAAC"}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,23 @@
export interface ImageFile {
id: string;
url: string;
file: File;
mediaToken?: string;
}
export interface ImageInputProps {
/** Selected image */
value?: ImageFile | null;
/** Callback when image is selected or removed */
onChange: (image: ImageFile | null) => void;
/** Handler for opening gallery modal */
onGalleryOpen?: () => void;
/** Show gallery button */
showGalleryButton?: boolean;
/** Custom placeholder text */
placeholderText?: string;
/** Additional class names */
className?: string;
}
export declare function ImageInput({ value, onChange, onGalleryOpen, showGalleryButton, placeholderText, className, }: ImageInputProps): import("react/jsx-runtime").JSX.Element;
export default ImageInput;
//# sourceMappingURL=image-input.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"image-input.d.ts","sourceRoot":"","sources":["../../src/lib/image-input.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,iDAAiD;IACjD,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5C,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,0BAA0B;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,8BAA8B;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAa,EACb,iBAAwB,EACxB,eAA+C,EAC/C,SAAS,GACV,EAAE,eAAe,2CAkOjB;AAED,eAAe,UAAU,CAAC"}
@@ -0,0 +1,17 @@
{
"name": "@storyteller/ui-image-input",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
}
}
@@ -0,0 +1,2 @@
export * from './lib/image-input';
@@ -0,0 +1,7 @@
/*
* Replace this with your own classes
*
* e.g.
* .container {
* }
*/
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import ImageInput from './image-input';
describe('ImageInput', () => {
it('should render successfully', () => {
const { baseElement } = render(<ImageInput />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,265 @@
import { useState, useRef, useCallback } from "react";
import { Button } from "@storyteller/ui-button";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faUpload, faImages, faTrash } from "@fortawesome/pro-solid-svg-icons";
import { toast } from "react-hot-toast";
export interface ImageFile {
id: string;
url: string;
file: File;
mediaToken?: string;
}
export interface ImageInputProps {
/** Selected image */
value?: ImageFile | null;
/** Callback when image is selected or removed */
onChange: (image: ImageFile | null) => void;
/** Handler for opening gallery modal */
onGalleryOpen?: () => void;
/** Show gallery button */
showGalleryButton?: boolean;
/** Custom placeholder text */
placeholderText?: string;
/** Additional class names */
className?: string;
}
export function ImageInput({
value,
onChange,
onGalleryOpen,
showGalleryButton = true,
placeholderText = "Drag and drop an image here",
className,
}: ImageInputProps) {
const [isDragging, setIsDragging] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const processImageFile = (file: File) => {
// Only accept image files
if (!file.type.startsWith("image/")) {
toast.error("Please upload an image file");
return;
}
const reader = new FileReader();
reader.onloadend = () => {
const newImage: ImageFile = {
id: Math.random().toString(36).substring(7),
url: reader.result as string,
file: file,
};
onChange(newImage);
};
reader.readAsDataURL(file);
};
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files && files[0]) {
const file = files[0];
processImageFile(file);
}
};
const handleUploadClick = () => {
fileInputRef.current?.click();
};
// Drag and drop handlers
// Enhanced drag and drop handlers for better desktop compatibility
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
}, []);
const handleDragOver = useCallback(
(e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
if (!isDragging) setIsDragging(true);
// Explicitly set effectAllowed to make sure the browser shows the correct drop effect
if (e.dataTransfer) {
e.dataTransfer.dropEffect = "copy";
}
},
[isDragging]
);
const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
// Only set isDragging to false if we're leaving the drop target
// and not entering a child element
const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();
const x = e.clientX;
const y = e.clientY;
if (x < rect.left || x >= rect.right || y < rect.top || y >= rect.bottom) {
setIsDragging(false);
}
}, []);
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
// Debug logging
console.log("Files dropped:", e.dataTransfer.files);
console.log("DataTransfer types:", e.dataTransfer.types);
try {
// First try the files collection
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
const file = e.dataTransfer.files[0]; // Just take the first file
console.log("Processing file:", file.name, file.type, file.size);
processImageFile(file);
return;
}
// Then try the items collection
const items = e.dataTransfer.items;
if (items && items.length > 0) {
for (let i = 0; i < items.length; i++) {
if (items[i].kind === "file") {
const file = items[i].getAsFile();
if (file) {
console.log("Processing item as file:", file.name);
processImageFile(file);
return;
}
}
}
}
// If we got here, we couldn't process the drop
console.log("Could not process dropped content");
toast.error(
"Could not process the dropped file. Please try uploading instead."
);
} catch (error) {
console.error("Error processing drop:", error);
toast.error(
"Error processing the dropped file. Please try uploading instead."
);
}
}, []);
return (
<div className={twMerge("relative", className)}>
{/* Drag & drop area or image preview */}
<div
className={twMerge(
"mx-auto flex h-60 w-full flex-col items-center justify-center rounded-lg border border-dashed border-white/20 bg-white/5 p-4 transition-colors",
isDragging && "border-blue-400 bg-blue-500/10"
)}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onDragStart={(e) => e.preventDefault()}
>
{value ? (
<div className="h-full w-full overflow-hidden">
<img
src={value.url}
alt="Selected image"
className="h-full w-full object-contain"
/>
</div>
) : (
<div className="flex h-full w-full flex-col items-center justify-center text-white/60">
<FontAwesomeIcon icon={faImages} className="text-2xl mb-2" />
<p>{placeholderText}</p>
<div className="flex items-center justify-center gap-2 my-4 w-full">
<div className="h-[1px] w-12 bg-white/10" />
<p className="text-sm px-3">or</p>
<div className="h-[1px] w-12 bg-white/10" />
</div>
<div className="flex flex-col items-center justify-center gap-3 sm:flex-row">
{showGalleryButton && onGalleryOpen && (
<Button
onClick={onGalleryOpen}
variant="secondary"
className="flex items-center gap-2 px-2.5 py-1.5 text-sm bg-white/15"
>
<FontAwesomeIcon icon={faImages} />
Choose from Library
</Button>
)}
<Button
onClick={handleUploadClick}
variant="secondary"
className="flex items-center gap-2 px-2.5 py-1.5 text-sm bg-white/15"
>
<FontAwesomeIcon icon={faUpload} />
Upload Image
</Button>
</div>
</div>
)}
</div>
{/* Hidden file input */}
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleFileUpload}
className="hidden"
/>
{/* Action buttons when image is selected */}
{value && (
<div className="mt-4 flex flex-wrap items-center justify-center gap-3">
{showGalleryButton && onGalleryOpen && (
<Button
onClick={onGalleryOpen}
variant="secondary"
className="flex items-center gap-2 px-2.5 py-1.5 text-sm"
icon={faImages}
>
Choose Different Image
</Button>
)}
<Button
onClick={handleUploadClick}
variant="secondary"
className="flex items-center gap-2 px-2.5 py-1.5 text-sm"
icon={faUpload}
>
Upload Different Image
</Button>
<Button
onClick={() => {
onChange(null);
// Reset file input value to allow selecting the same file again
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
}}
variant="destructive"
className="flex items-center gap-2 px-2.5 py-1.5 text-sm"
icon={faTrash}
>
Remove Image
</Button>
</div>
)}
</div>
);
}
export default ImageInput;
@@ -0,0 +1,16 @@
{
"files": [],
"include": [],
"references": [
{
"path": "../button"
},
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json"
}
@@ -0,0 +1,50 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts",
"vite/client"
],
"rootDir": "src",
"jsx": "react-jsx",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
},
"exclude": [
"out-tsc",
"dist",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
],
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx"
],
"references": [
{
"path": "../button/tsconfig.lib.json"
}
]
}
@@ -0,0 +1,34 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/vitest",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
],
"jsx": "react-jsx"
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
@@ -0,0 +1,49 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import * as path from 'path';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../../node_modules/.vite/libs/components/image-input',
plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: '@storyteller/ui-image-input',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const]
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react','react-dom','react/jsx-runtime']
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
}
},
}));