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
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+7
View File
@@ -0,0 +1,7 @@
# @storyteller/ui-input
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test @storyteller/ui-input` to execute the unit tests via [Vitest](https://vitest.dev/).
+4
View File
@@ -0,0 +1,4 @@
export * from './lib/input';
export * from './lib/InputVector';
export * from './lib/NumberInput';
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC"}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
import { DomLevels } from '@storyteller/common';
interface InputVectorProps {
x: string;
y: string;
z: string;
onChange: (v: Record<string, string>) => void;
increment?: number;
disabled?: boolean;
disableHotkeyInput: (level: DomLevels) => void;
enableHotkeyInput: (level: DomLevels) => void;
}
export declare const InputVector: ({ x, y, z, onChange, increment, disabled, disableHotkeyInput, enableHotkeyInput, }: InputVectorProps) => import("react/jsx-runtime").JSX.Element;
export {};
//# sourceMappingURL=InputVector.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"InputVector.d.ts","sourceRoot":"","sources":["../../src/lib/InputVector.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,UAAU,gBAAgB;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kBAAkB,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC/C,iBAAiB,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CAC/C;AAED,eAAO,MAAM,WAAW,GAAI,oFASzB,gBAAgB,4CA+MlB,CAAC"}
@@ -0,0 +1,8 @@
import { default as React } from 'react';
interface NumberInputProps {
value: number;
onChange: (value: number) => void;
}
export declare const NumberInput: React.FC<NumberInputProps>;
export {};
//# sourceMappingURL=NumberInput.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"NumberInput.d.ts","sourceRoot":"","sources":["../../src/lib/NumberInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAMnD,UAAU,gBAAgB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA0ElD,CAAC"}
+13
View File
@@ -0,0 +1,13 @@
import { default as React } from 'react';
import { IconDefinition } from '@fortawesome/pro-solid-svg-icons';
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
inputClassName?: string;
iconClassName?: string;
label?: string;
icon?: IconDefinition;
isError?: boolean;
errorMessage?: string;
}
export declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
export default Input;
//# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/lib/input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAIlE,UAAU,UAAW,SAAQ,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,KAAK,qFA2DjB,CAAC;AAIF,eAAe,KAAK,CAAC"}
@@ -0,0 +1,17 @@
{
"name": "@storyteller/ui-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,3 @@
export * from './lib/input';
export * from './lib/InputVector';
export * from './lib/NumberInput';
@@ -0,0 +1,232 @@
import React, { ChangeEvent, RefObject, useRef } from "react";
import { twMerge } from "tailwind-merge";
import { DomLevels } from "@storyteller/common";
interface InputVectorProps {
x: string;
y: string;
z: string;
onChange: (v: Record<string, string>) => void;
increment?: number;
disabled?: boolean;
disableHotkeyInput: (level: DomLevels) => void;
enableHotkeyInput: (level: DomLevels) => void;
}
export const InputVector = ({
x,
y,
z,
onChange,
increment = 0.1,
disabled,
disableHotkeyInput,
enableHotkeyInput,
}: InputVectorProps) => {
const xRef = useRef<HTMLInputElement>(null);
const yRef = useRef<HTMLInputElement>(null);
const zRef = useRef<HTMLInputElement>(null);
const isDragging = useRef(false);
const previousMousePosition = useRef(0);
const oldX = useRef("");
const oldY = useRef("");
const oldZ = useRef("");
oldX.current = x;
oldY.current = y;
oldZ.current = z;
const inputCommonClasses =
"relative h-6 rounded-r-lg bg-black/25 p-2 text-sm text-white transition-all duration-100 ease-in-out outline-none -outline-offset-2 text-end w-full hover:cursor-e-resize hover:bg-black/40";
const wrapperCommonClasses =
"relative flex items-center before:inline-block before:h-6 before:bg-brand-primary before:text-white before:rounded-l-lg before:w-1.5 before:text-center before:justify-center before:items-center before:font-semibold before:flex before:align-middle before:leading-8 text-xs";
const lockedCommonClasses =
"hover:cursor-not-allowed hover:bg-brand-secondary";
function validateInput(newValue: string, oldValue: string) {
if (newValue === "" || newValue === "-" || newValue === ".") {
return newValue;
}
return /^-?\d*(\.\d{0,2})?$/.test(newValue) ? newValue : oldValue;
}
function handleOnChange(event: ChangeEvent<HTMLInputElement>) {
const newVector: Record<string, string> = {
x: oldX.current,
y: oldY.current,
z: oldZ.current,
};
newVector[event.target.name] = validateInput(
event.target.value,
newVector[event.target.name],
);
onChange(newVector);
}
const blurAllInputs = () => {
xRef.current?.blur();
yRef.current?.blur();
zRef.current?.blur();
};
// For dragging the input value to increment/decrement
const handleMouseDown = (
e: React.MouseEvent,
ref: RefObject<HTMLInputElement|null>,
) => {
e.stopPropagation();
blurAllInputs();
previousMousePosition.current = e.clientX;
isDragging.current = true;
const mouseMoveHandler = (e: MouseEvent) => {
e.stopPropagation();
const currentMousePosition = e.clientX;
const direction =
currentMousePosition === previousMousePosition.current
? 0
: currentMousePosition > previousMousePosition.current
? 1
: -1;
previousMousePosition.current = currentMousePosition;
const newVector = {
x: oldX.current,
y: oldY.current,
z: oldZ.current,
} as Record<string, string>;
const newValue =
parseFloat(newVector[ref.current?.name ?? ""]) + direction * increment;
newVector[ref.current?.name ?? ""] = (
Math.round(newValue * 1000) / 1000
).toString();
onChange(newVector);
};
const mouseUpHandler = () => {
document.removeEventListener("mousemove", mouseMoveHandler);
document.removeEventListener("mouseup", mouseUpHandler);
isDragging.current = false;
};
document.addEventListener("mousemove", mouseMoveHandler);
document.addEventListener("mouseup", mouseUpHandler);
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
(e.currentTarget as HTMLInputElement).blur(); // Unfocus the input field on pressing Enter
}
};
return (
<div className="flex w-full flex-col justify-between gap-1.5">
<span
className={twMerge(
wrapperCommonClasses,
"before:bg-axis-x",
disabled && "opacity-60",
)}
>
<div className="absolute left-3.5 z-10 font-semibold">X</div>
<input
className={twMerge(
inputCommonClasses,
"focus:outline-axis-x",
disabled && lockedCommonClasses,
)}
type="text"
name="x"
onChange={handleOnChange}
ref={xRef}
value={x}
disabled={disabled}
onFocus={() => {
disableHotkeyInput(DomLevels.INPUT);
}}
onBlur={() => {
enableHotkeyInput(DomLevels.INPUT);
}}
onMouseDown={(e) => {
handleMouseDown(e, xRef);
}}
onKeyDown={handleKeyDown}
/>
</span>
<span
className={twMerge(
wrapperCommonClasses,
"before:bg-axis-y",
disabled && "opacity-60",
)}
>
<div className="absolute left-3.5 z-10 font-semibold">Y</div>
<input
className={twMerge(
inputCommonClasses,
"focus:outline-axis-y",
disabled && lockedCommonClasses,
)}
type="text"
name="y"
onChange={handleOnChange}
ref={yRef}
value={y}
disabled={disabled}
onFocus={() => {
disableHotkeyInput(DomLevels.INPUT);
}}
onBlur={() => {
enableHotkeyInput(DomLevels.INPUT);
}}
onMouseDown={(e) => {
handleMouseDown(e, yRef);
}}
onKeyDown={handleKeyDown}
/>
</span>
<span
className={twMerge(
wrapperCommonClasses,
"before:bg-axis-z",
disabled && "opacity-60",
)}
>
<div className="absolute left-3.5 z-10 font-semibold">Z</div>
<input
className={twMerge(
inputCommonClasses,
"focus:outline-axis-z",
disabled && lockedCommonClasses,
)}
type="text"
name="z"
onChange={handleOnChange}
ref={zRef}
value={z}
disabled={disabled}
onFocus={() => {
disableHotkeyInput(DomLevels.INPUT);
}}
onBlur={() => {
enableHotkeyInput(DomLevels.INPUT);
}}
onMouseDown={(e) => {
handleMouseDown(e, zRef);
}}
onKeyDown={handleKeyDown}
/>
</span>
</div>
);
};
@@ -0,0 +1,86 @@
import React, { useEffect, useState } from "react";
import { Input } from "./input";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronDown, faChevronUp } from "@fortawesome/pro-solid-svg-icons";
import { twMerge } from "tailwind-merge";
interface NumberInputProps {
value: number;
onChange: (value: number) => void;
}
export const NumberInput: React.FC<NumberInputProps> = ({
value,
onChange,
}) => {
const [inputValue, setInputValue] = useState<string>(value.toString());
useEffect(() => {
setInputValue(Math.round(value * 100) / 100 + "");
}, [value]);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
if (
newValue === "" ||
(!isNaN(Number(newValue)) && Number(newValue) <= 100)
) {
setInputValue(newValue);
const numericValue = parseFloat(newValue);
if (newValue !== "") {
onChange(Math.round(numericValue * 100) / 100);
}
}
};
const handleBlur = () => {
if (inputValue === "") {
setInputValue(Math.round(value * 100) / 100 + "");
}
};
const handleIncrement = () => {
const newValue = Math.min(Number(inputValue) + 1, 100);
setInputValue(Math.round(newValue * 100) / 100 + "");
onChange(Math.round(newValue * 100) / 100);
};
const handleDecrement = () => {
const newValue = Math.max(Number(inputValue) - 1, 0);
setInputValue(Math.round(newValue * 100) / 100 + "");
onChange(Math.round(newValue * 100) / 100);
};
const incrementButtonStyle =
"flex h-[15px] w-6 items-center justify-center text-[10px] text-white/80 bg-ui-controls hover:bg-ui-controls-button outline-none focus:outline-none transition-all active:outline-none";
return (
<div className="relative">
<Input
type="text"
value={inputValue}
onChange={handleChange}
onBlur={handleBlur}
inputClassName="w-[70px] h-[30px] rounded-md text-sm"
/>
<div className="absolute right-0 top-0 flex h-[30px] flex-col rounded-r-md border-l border-white/15 bg-gray-800">
<button
className={twMerge(
incrementButtonStyle,
"rounded-tr-md border-b border-white/10",
)}
onClick={handleIncrement}
>
<FontAwesomeIcon icon={faChevronUp} />
</button>
<button
className={twMerge(incrementButtonStyle, "rounded-br-md")}
onClick={handleDecrement}
>
<FontAwesomeIcon icon={faChevronDown} />
</button>
</div>
</div>
);
};
@@ -0,0 +1,7 @@
/*
* Replace this with your own classes
*
* e.g.
* .container {
* }
*/
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import Input from './input';
describe('Input', () => {
it('should render successfully', () => {
const { baseElement } = render(<Input />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,79 @@
import React from "react";
import { twMerge } from "tailwind-merge";
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Label } from "@storyteller/ui-label";
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
inputClassName?: string;
iconClassName?: string;
label?: string;
icon?: IconDefinition;
isError?: boolean;
errorMessage?: string;
}
export const Input = React.forwardRef(
(
{
label,
icon,
inputClassName,
iconClassName,
className,
id,
isError,
onBlur,
onFocus,
errorMessage,
...rest
}: InputProps,
ref: React.ForwardedRef<HTMLInputElement>
) => {
return (
<div className={twMerge("flex flex-col", className)}>
{label && <Label htmlFor={id ? id : label}>{label}</Label>}
<div className="relative w-full">
{icon && (
<FontAwesomeIcon
icon={icon}
className={twMerge("text-md absolute pl-3 pt-3", iconClassName)}
/>
)}
<input
ref={ref}
id={id ? id : label ? label : undefined}
className={twMerge(
"h-10 w-full rounded-lg px-3 py-2.5 outline-none",
"bg-ui-panel text-base-fg placeholder-base-fg/50",
"border border-ui-panel-border transition-all duration-150 ease-in-out hover:border-primary/60 focus:border-primary focus:!outline-none",
"disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:border-ui-panel-border",
icon && "pl-10",
isError && "outline-red focus:outline-red",
inputClassName
)}
onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
if (onFocus) {
onFocus(e);
}
}}
onBlur={(e: React.FocusEvent<HTMLInputElement>) => {
if (onBlur) {
onBlur(e);
}
}}
{...rest}
/>
{errorMessage && (
<h6 className="absolute z-10 text-red">{errorMessage}</h6>
)}
</div>
</div>
);
}
);
Input.displayName = "Input";
export default Input;
@@ -0,0 +1,19 @@
{
"files": [],
"include": [],
"references": [
{
"path": "../label"
},
{
"path": "../../common"
},
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json"
}
@@ -0,0 +1,53 @@
{
"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": "../label/tsconfig.lib.json"
},
{
"path": "../../common/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/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-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,
}
},
}));