@onerjs/shared-ui-components 8.32.8 → 8.33.1

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.
@@ -5,5 +5,10 @@ type FileUploadLineProps = Omit<ButtonProps, "onClick" | "label"> & {
5
5
  label: string;
6
6
  accept: string;
7
7
  };
8
+ /**
9
+ * A full-width line with an upload button.
10
+ * For just the button without the line wrapper, use UploadButton directly.
11
+ * @returns An UploadButton wrapped in a LineContainer
12
+ */
8
13
  export declare const FileUploadLine: FunctionComponent<FileUploadLineProps>;
9
14
  export {};
@@ -1,20 +1,13 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef } from "react";
3
- import { ButtonLine } from "./buttonLine.js";
4
- import { ArrowUploadRegular } from "@fluentui/react-icons";
5
- export const FileUploadLine = (props) => {
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { LineContainer } from "./propertyLines/propertyLine.js";
3
+ import { UploadButton } from "../primitives/uploadButton.js";
4
+ /**
5
+ * A full-width line with an upload button.
6
+ * For just the button without the line wrapper, use UploadButton directly.
7
+ * @returns An UploadButton wrapped in a LineContainer
8
+ */
9
+ export const FileUploadLine = ({ onClick, label, accept, ...buttonProps }) => {
6
10
  FileUploadLine.displayName = "FileUploadLine";
7
- const inputRef = useRef(null);
8
- const handleButtonClick = () => {
9
- inputRef.current?.click();
10
- };
11
- const handleChange = (evt) => {
12
- const files = evt.target.files;
13
- if (files && files.length) {
14
- props.onClick(files);
15
- }
16
- evt.target.value = "";
17
- };
18
- return (_jsxs(_Fragment, { children: [_jsx(ButtonLine, { onClick: handleButtonClick, icon: ArrowUploadRegular, label: props.label }), _jsx("input", { ref: inputRef, type: "file", accept: props.accept, style: { display: "none" }, onChange: handleChange })] }));
11
+ return (_jsx(LineContainer, { children: _jsx(UploadButton, { onUpload: onClick, accept: accept, label: label, ...buttonProps }) }));
19
12
  };
20
13
  //# sourceMappingURL=fileUploadLine.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fileUploadLine.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/hoc/fileUploadLine.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAQ3D,MAAM,CAAC,MAAM,cAAc,GAA2C,CAAC,KAAK,EAAE,EAAE;IAC5E,cAAc,CAAC,WAAW,GAAG,gBAAgB,CAAC;IAC9C,MAAM,QAAQ,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAC;IAEhD,MAAM,iBAAiB,GAAG,GAAG,EAAE;QAC3B,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,CAAC,GAAwC,EAAE,EAAE;QAC9D,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC;QAC/B,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACxB,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;QACD,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;IAC1B,CAAC,CAAC;IAEF,OAAO,CACH,8BACI,KAAC,UAAU,IAAC,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,GAAe,EACnG,gBAAO,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAC,MAAM,EAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,YAAY,GAAI,IAC/G,CACN,CAAC;AACN,CAAC,CAAC","sourcesContent":["import { useRef } from \"react\";\r\nimport type { FunctionComponent } from \"react\";\r\nimport { ButtonLine } from \"./buttonLine\";\r\nimport type { ButtonProps } from \"../primitives/button\";\r\nimport { ArrowUploadRegular } from \"@fluentui/react-icons\";\r\n\r\ntype FileUploadLineProps = Omit<ButtonProps, \"onClick\" | \"label\"> & {\r\n onClick: (files: FileList) => void;\r\n label: string; // Require a label when button is the entire line (by default, label is optional on a button)\r\n accept: string;\r\n};\r\n\r\nexport const FileUploadLine: FunctionComponent<FileUploadLineProps> = (props) => {\r\n FileUploadLine.displayName = \"FileUploadLine\";\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n\r\n const handleButtonClick = () => {\r\n inputRef.current?.click();\r\n };\r\n\r\n const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {\r\n const files = evt.target.files;\r\n if (files && files.length) {\r\n props.onClick(files);\r\n }\r\n evt.target.value = \"\";\r\n };\r\n\r\n return (\r\n <>\r\n <ButtonLine onClick={handleButtonClick} icon={ArrowUploadRegular} label={props.label}></ButtonLine>\r\n <input ref={inputRef} type=\"file\" accept={props.accept} style={{ display: \"none\" }} onChange={handleChange} />\r\n </>\r\n );\r\n};\r\n"]}
1
+ {"version":3,"file":"fileUploadLine.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/hoc/fileUploadLine.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAS1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAA2C,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,EAAE,EAAE;IACjH,cAAc,CAAC,WAAW,GAAG,gBAAgB,CAAC;IAE9C,OAAO,CACH,KAAC,aAAa,cACV,KAAC,YAAY,IAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAM,WAAW,GAAI,GACtE,CACnB,CAAC;AACN,CAAC,CAAC","sourcesContent":["import type { FunctionComponent } from \"react\";\r\nimport { LineContainer } from \"./propertyLines/propertyLine\";\r\nimport { UploadButton } from \"../primitives/uploadButton\";\r\nimport type { ButtonProps } from \"../primitives/button\";\r\n\r\ntype FileUploadLineProps = Omit<ButtonProps, \"onClick\" | \"label\"> & {\r\n onClick: (files: FileList) => void;\r\n label: string; // Require a label when button is the entire line (by default, label is optional on an UploadButton\r\n accept: string;\r\n};\r\n\r\n/**\r\n * A full-width line with an upload button.\r\n * For just the button without the line wrapper, use UploadButton directly.\r\n * @returns An UploadButton wrapped in a LineContainer\r\n */\r\nexport const FileUploadLine: FunctionComponent<FileUploadLineProps> = ({ onClick, label, accept, ...buttonProps }) => {\r\n FileUploadLine.displayName = \"FileUploadLine\";\r\n\r\n return (\r\n <LineContainer>\r\n <UploadButton onUpload={onClick} accept={accept} label={label} {...buttonProps} />\r\n </LineContainer>\r\n );\r\n};\r\n"]}
@@ -0,0 +1,14 @@
1
+ import type { FunctionComponent } from "react";
2
+ import type { BaseTexture } from "@onerjs/core/Materials/Textures/baseTexture.js";
3
+ import type { Nullable } from "@onerjs/core/types.js";
4
+ import type { PropertyLineProps } from "./propertyLine.js";
5
+ import type { ChooseTextureProps } from "../../primitives/chooseTexture.js";
6
+ type ChooseTexturePropertyLineProps = PropertyLineProps<Nullable<BaseTexture>> & ChooseTextureProps;
7
+ /**
8
+ * A property line with a ComboBox for selecting from existing scene textures
9
+ * and a button for uploading new texture files.
10
+ * @param props - ChooseTextureProps & PropertyLineProps
11
+ * @returns property-line wrapped ChooseTexture component
12
+ */
13
+ export declare const ChooseTexturePropertyLine: FunctionComponent<ChooseTexturePropertyLineProps>;
14
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PropertyLine } from "./propertyLine.js";
3
+ import { ChooseTexture } from "../../primitives/chooseTexture.js";
4
+ /**
5
+ * A property line with a ComboBox for selecting from existing scene textures
6
+ * and a button for uploading new texture files.
7
+ * @param props - ChooseTextureProps & PropertyLineProps
8
+ * @returns property-line wrapped ChooseTexture component
9
+ */
10
+ export const ChooseTexturePropertyLine = (props) => {
11
+ ChooseTexturePropertyLine.displayName = "ChooseTexturePropertyLine";
12
+ return (_jsx(PropertyLine, { ...props, children: _jsx(ChooseTexture, { ...props }) }));
13
+ };
14
+ //# sourceMappingURL=chooseTexturePropertyLine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chooseTexturePropertyLine.js","sourceRoot":"","sources":["../../../../../../dev/sharedUiComponents/src/fluent/hoc/propertyLines/chooseTexturePropertyLine.tsx"],"names":[],"mappings":";AAMA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAI/D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAsD,CAAC,KAAK,EAAE,EAAE;IAClG,yBAAyB,CAAC,WAAW,GAAG,2BAA2B,CAAC;IAEpE,OAAO,CACH,KAAC,YAAY,OAAK,KAAK,YACnB,KAAC,aAAa,OAAK,KAAK,GAAI,GACjB,CAClB,CAAC;AACN,CAAC,CAAC","sourcesContent":["import type { FunctionComponent } from \"react\";\r\nimport type { BaseTexture } from \"core/Materials/Textures/baseTexture\";\r\nimport type { Nullable } from \"core/types\";\r\nimport type { PropertyLineProps } from \"./propertyLine\";\r\nimport type { ChooseTextureProps } from \"../../primitives/chooseTexture\";\r\n\r\nimport { PropertyLine } from \"./propertyLine\";\r\nimport { ChooseTexture } from \"../../primitives/chooseTexture\";\r\n\r\ntype ChooseTexturePropertyLineProps = PropertyLineProps<Nullable<BaseTexture>> & ChooseTextureProps;\r\n\r\n/**\r\n * A property line with a ComboBox for selecting from existing scene textures\r\n * and a button for uploading new texture files.\r\n * @param props - ChooseTextureProps & PropertyLineProps\r\n * @returns property-line wrapped ChooseTexture component\r\n */\r\nexport const ChooseTexturePropertyLine: FunctionComponent<ChooseTexturePropertyLineProps> = (props) => {\r\n ChooseTexturePropertyLine.displayName = \"ChooseTexturePropertyLine\";\r\n\r\n return (\r\n <PropertyLine {...props}>\r\n <ChooseTexture {...props} />\r\n </PropertyLine>\r\n );\r\n};\r\n"]}
@@ -0,0 +1,11 @@
1
+ import type { FunctionComponent } from "react";
2
+ import type { PropertyLineProps } from "./propertyLine.js";
3
+ import type { ComboBoxProps } from "../../primitives/comboBox.js";
4
+ type ComboBoxPropertyLineProps = ComboBoxProps & PropertyLineProps<string>;
5
+ /**
6
+ * A property line with a filterable ComboBox
7
+ * @param props - ComboBoxProps & PropertyLineProps
8
+ * @returns property-line wrapped ComboBox component
9
+ */
10
+ export declare const ComboBoxPropertyLine: FunctionComponent<ComboBoxPropertyLineProps>;
11
+ export {};
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PropertyLine } from "./propertyLine.js";
3
+ import { ComboBox } from "../../primitives/comboBox.js";
4
+ /**
5
+ * A property line with a filterable ComboBox
6
+ * @param props - ComboBoxProps & PropertyLineProps
7
+ * @returns property-line wrapped ComboBox component
8
+ */
9
+ export const ComboBoxPropertyLine = (props) => {
10
+ ComboBoxPropertyLine.displayName = "ComboBoxPropertyLine";
11
+ return (_jsx(PropertyLine, { ...props, children: _jsx(ComboBox, { ...props }) }));
12
+ };
13
+ //# sourceMappingURL=comboBoxPropertyLine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comboBoxPropertyLine.js","sourceRoot":"","sources":["../../../../../../dev/sharedUiComponents/src/fluent/hoc/propertyLines/comboBoxPropertyLine.tsx"],"names":[],"mappings":";AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAIrD;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAiD,CAAC,KAAK,EAAE,EAAE;IACxF,oBAAoB,CAAC,WAAW,GAAG,sBAAsB,CAAC;IAE1D,OAAO,CACH,KAAC,YAAY,OAAK,KAAK,YACnB,KAAC,QAAQ,OAAK,KAAK,GAAI,GACZ,CAClB,CAAC;AACN,CAAC,CAAC","sourcesContent":["import type { FunctionComponent } from \"react\";\r\nimport type { PropertyLineProps } from \"./propertyLine\";\r\nimport type { ComboBoxProps } from \"../../primitives/comboBox\";\r\n\r\nimport { PropertyLine } from \"./propertyLine\";\r\nimport { ComboBox } from \"../../primitives/comboBox\";\r\n\r\ntype ComboBoxPropertyLineProps = ComboBoxProps & PropertyLineProps<string>;\r\n\r\n/**\r\n * A property line with a filterable ComboBox\r\n * @param props - ComboBoxProps & PropertyLineProps\r\n * @returns property-line wrapped ComboBox component\r\n */\r\nexport const ComboBoxPropertyLine: FunctionComponent<ComboBoxPropertyLineProps> = (props) => {\r\n ComboBoxPropertyLine.displayName = \"ComboBoxPropertyLine\";\r\n\r\n return (\r\n <PropertyLine {...props}>\r\n <ComboBox {...props} />\r\n </PropertyLine>\r\n );\r\n};\r\n"]}
@@ -0,0 +1,40 @@
1
+ import type { FunctionComponent } from "react";
2
+ import type { BaseTexture } from "@onerjs/core/Materials/Textures/baseTexture.js";
3
+ import type { Scene } from "@onerjs/core/scene.js";
4
+ type TextureUploadUpdateProps = {
5
+ /**
6
+ * Existing texture to update via updateURL
7
+ */
8
+ texture: BaseTexture;
9
+ /**
10
+ * Callback after texture is updated
11
+ */
12
+ onChange?: (texture: BaseTexture) => void;
13
+ scene?: never;
14
+ cubeOnly?: never;
15
+ };
16
+ type TextureUploadCreateProps = {
17
+ /**
18
+ * The scene to create the texture in
19
+ */
20
+ scene: Scene;
21
+ /**
22
+ * Callback when a new texture is created
23
+ */
24
+ onChange: (texture: BaseTexture) => void;
25
+ /**
26
+ * Whether to create cube textures
27
+ */
28
+ cubeOnly?: boolean;
29
+ texture?: never;
30
+ };
31
+ type TextureUploadProps = TextureUploadUpdateProps | TextureUploadCreateProps;
32
+ /**
33
+ * A button that uploads a file and either:
34
+ * - Updates an existing Texture or CubeTexture via updateURL (if texture prop is provided)
35
+ * - Creates a new Texture or CubeTexture (if scene/onChange props are provided)
36
+ * @param props TextureUploadProps
37
+ * @returns UploadButton component that handles texture upload
38
+ */
39
+ export declare const TextureUpload: FunctionComponent<TextureUploadProps>;
40
+ export {};
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback } from "react";
3
+ import { Texture } from "@onerjs/core/Materials/Textures/texture.js";
4
+ import { CubeTexture } from "@onerjs/core/Materials/Textures/cubeTexture.js";
5
+ import { ReadFile } from "@onerjs/core/Misc/fileTools.js";
6
+ import { UploadButton } from "../primitives/uploadButton.js";
7
+ /**
8
+ * A button that uploads a file and either:
9
+ * - Updates an existing Texture or CubeTexture via updateURL (if texture prop is provided)
10
+ * - Creates a new Texture or CubeTexture (if scene/onChange props are provided)
11
+ * @param props TextureUploadProps
12
+ * @returns UploadButton component that handles texture upload
13
+ */
14
+ export const TextureUpload = (props) => {
15
+ TextureUpload.displayName = "TextureUpload";
16
+ const label = props.texture ? "Upload Texture" : undefined;
17
+ // TODO: This should probably be dynamically fetching a list of supported texture extensions
18
+ const accept = ".jpg, .png, .tga, .dds, .env, .exr";
19
+ const handleUpload = useCallback((files) => {
20
+ const file = files[0];
21
+ if (!file) {
22
+ return;
23
+ }
24
+ ReadFile(file, (data) => {
25
+ const blob = new Blob([data], { type: "octet/stream" });
26
+ // Update existing texture
27
+ if (props.texture) {
28
+ const { texture, onChange } = props;
29
+ const reader = new FileReader();
30
+ reader.readAsDataURL(blob);
31
+ reader.onloadend = () => {
32
+ const base64data = reader.result;
33
+ if (texture instanceof CubeTexture) {
34
+ let extension = undefined;
35
+ if (file.name.toLowerCase().indexOf(".dds") > 0) {
36
+ extension = ".dds";
37
+ }
38
+ else if (file.name.toLowerCase().indexOf(".env") > 0) {
39
+ extension = ".env";
40
+ }
41
+ texture.updateURL(base64data, extension, () => onChange?.(texture));
42
+ }
43
+ else if (texture instanceof Texture) {
44
+ texture.updateURL(base64data, null, () => onChange?.(texture));
45
+ }
46
+ };
47
+ }
48
+ else {
49
+ // Create new texture
50
+ const { scene, cubeOnly, onChange } = props;
51
+ const url = URL.createObjectURL(blob);
52
+ const extension = file.name.split(".").pop()?.toLowerCase();
53
+ // Revoke the object URL after texture loads to prevent memory leak
54
+ const revokeUrl = () => URL.revokeObjectURL(url);
55
+ const newTexture = cubeOnly
56
+ ? new CubeTexture(url, scene, [], false, undefined, revokeUrl, undefined, undefined, false, extension ? "." + extension : undefined)
57
+ : new Texture(url, scene, false, false, undefined, revokeUrl);
58
+ onChange(newTexture);
59
+ }
60
+ }, undefined, true);
61
+ }, [props]);
62
+ return _jsx(UploadButton, { onUpload: handleUpload, accept: accept, title: "Upload Texture", label: label });
63
+ };
64
+ //# sourceMappingURL=textureUpload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textureUpload.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/hoc/textureUpload.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { FunctionComponent } from \"react\";\r\nimport { useCallback } from \"react\";\r\nimport type { BaseTexture } from \"core/Materials/Textures/baseTexture\";\r\nimport type { Scene } from \"core/scene\";\r\nimport { Texture } from \"core/Materials/Textures/texture\";\r\nimport { CubeTexture } from \"core/Materials/Textures/cubeTexture\";\r\nimport { ReadFile } from \"core/Misc/fileTools\";\r\nimport { UploadButton } from \"../primitives/uploadButton\";\r\n\r\ntype TextureUploadUpdateProps = {\r\n /**\r\n * Existing texture to update via updateURL\r\n */\r\n texture: BaseTexture;\r\n /**\r\n * Callback after texture is updated\r\n */\r\n onChange?: (texture: BaseTexture) => void;\r\n scene?: never;\r\n cubeOnly?: never;\r\n};\r\n\r\ntype TextureUploadCreateProps = {\r\n /**\r\n * The scene to create the texture in\r\n */\r\n scene: Scene;\r\n /**\r\n * Callback when a new texture is created\r\n */\r\n onChange: (texture: BaseTexture) => void;\r\n /**\r\n * Whether to create cube textures\r\n */\r\n cubeOnly?: boolean;\r\n texture?: never;\r\n};\r\n\r\ntype TextureUploadProps = TextureUploadUpdateProps | TextureUploadCreateProps;\r\n\r\n/**\r\n * A button that uploads a file and either:\r\n * - Updates an existing Texture or CubeTexture via updateURL (if texture prop is provided)\r\n * - Creates a new Texture or CubeTexture (if scene/onChange props are provided)\r\n * @param props TextureUploadProps\r\n * @returns UploadButton component that handles texture upload\r\n */\r\nexport const TextureUpload: FunctionComponent<TextureUploadProps> = (props) => {\r\n TextureUpload.displayName = \"TextureUpload\";\r\n const label = props.texture ? \"Upload Texture\" : undefined;\r\n // TODO: This should probably be dynamically fetching a list of supported texture extensions\r\n const accept = \".jpg, .png, .tga, .dds, .env, .exr\";\r\n const handleUpload = useCallback(\r\n (files: FileList) => {\r\n const file = files[0];\r\n if (!file) {\r\n return;\r\n }\r\n\r\n ReadFile(\r\n file,\r\n (data) => {\r\n const blob = new Blob([data], { type: \"octet/stream\" });\r\n\r\n // Update existing texture\r\n if (props.texture) {\r\n const { texture, onChange } = props;\r\n const reader = new FileReader();\r\n reader.readAsDataURL(blob);\r\n reader.onloadend = () => {\r\n const base64data = reader.result as string;\r\n\r\n if (texture instanceof CubeTexture) {\r\n let extension: string | undefined = undefined;\r\n if (file.name.toLowerCase().indexOf(\".dds\") > 0) {\r\n extension = \".dds\";\r\n } else if (file.name.toLowerCase().indexOf(\".env\") > 0) {\r\n extension = \".env\";\r\n }\r\n texture.updateURL(base64data, extension, () => onChange?.(texture));\r\n } else if (texture instanceof Texture) {\r\n texture.updateURL(base64data, null, () => onChange?.(texture));\r\n }\r\n };\r\n } else {\r\n // Create new texture\r\n const { scene, cubeOnly, onChange } = props;\r\n const url = URL.createObjectURL(blob);\r\n const extension = file.name.split(\".\").pop()?.toLowerCase();\r\n\r\n // Revoke the object URL after texture loads to prevent memory leak\r\n const revokeUrl = () => URL.revokeObjectURL(url);\r\n\r\n const newTexture = cubeOnly\r\n ? new CubeTexture(url, scene, [], false, undefined, revokeUrl, undefined, undefined, false, extension ? \".\" + extension : undefined)\r\n : new Texture(url, scene, false, false, undefined, revokeUrl);\r\n\r\n onChange(newTexture);\r\n }\r\n },\r\n undefined,\r\n true\r\n );\r\n },\r\n [props]\r\n );\r\n\r\n return <UploadButton onUpload={handleUpload} accept={accept} title={\"Upload Texture\"} label={label} />;\r\n};\r\n"]}
@@ -0,0 +1,26 @@
1
+ import type { BaseTexture } from "@onerjs/core/Materials/Textures/baseTexture.js";
2
+ import type { Scene } from "@onerjs/core/scene.js";
3
+ import type { Nullable } from "@onerjs/core/types.js";
4
+ import type { FunctionComponent } from "react";
5
+ import type { PrimitiveProps } from "./primitive.js";
6
+ export type ChooseTextureProps = PrimitiveProps<Nullable<BaseTexture>> & {
7
+ /**
8
+ * The scene to get textures from
9
+ */
10
+ scene: Scene;
11
+ /**
12
+ * File types to accept for upload
13
+ */
14
+ accept?: string;
15
+ /**
16
+ * Whether to only allow cube textures
17
+ */
18
+ cubeOnly?: boolean;
19
+ };
20
+ /**
21
+ * A primitive component with a ComboBox for selecting from existing scene textures
22
+ * and a button for uploading new texture files.
23
+ * @param props ChooseTextureProps
24
+ * @returns ChooseTexture component
25
+ */
26
+ export declare const ChooseTexture: FunctionComponent<ChooseTextureProps>;
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { makeStyles, tokens } from "@fluentui/react-components";
3
+ import { useMemo } from "react";
4
+ import { TextureUpload } from "../hoc/textureUpload.js";
5
+ import { ComboBox } from "./comboBox.js";
6
+ const useStyles = makeStyles({
7
+ container: {
8
+ display: "flex",
9
+ flexDirection: "row",
10
+ alignItems: "center",
11
+ gap: tokens.spacingHorizontalS,
12
+ },
13
+ });
14
+ /**
15
+ * A primitive component with a ComboBox for selecting from existing scene textures
16
+ * and a button for uploading new texture files.
17
+ * @param props ChooseTextureProps
18
+ * @returns ChooseTexture component
19
+ */
20
+ export const ChooseTexture = (props) => {
21
+ ChooseTexture.displayName = "ChooseTexture";
22
+ const { scene, cubeOnly, value, onChange } = props;
23
+ const classes = useStyles();
24
+ // Get sorted texture names from scene
25
+ const textureOptions = useMemo(() => {
26
+ return scene.textures
27
+ .filter((t) => t.name && (!cubeOnly || t.isCube))
28
+ .map((t) => t.displayName || t.name)
29
+ .sort((a, b) => a.localeCompare(b));
30
+ }, [scene.textures, cubeOnly]);
31
+ const handleTextureSelect = (textureName) => {
32
+ const texture = scene.textures.find((t) => (t.displayName || t.name) === textureName);
33
+ onChange(texture ?? null);
34
+ };
35
+ // Get current texture name for initial display
36
+ const currentTextureName = value ? value.displayName || value.name : "";
37
+ return (_jsxs("div", { className: classes.container, children: [_jsx(ComboBox, { label: "", options: textureOptions, value: currentTextureName, onChange: handleTextureSelect }), _jsx(TextureUpload, { scene: scene, onChange: onChange, cubeOnly: cubeOnly })] }));
38
+ };
39
+ //# sourceMappingURL=chooseTexture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chooseTexture.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/chooseTexture.tsx"],"names":[],"mappings":";AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,SAAS,GAAG,UAAU,CAAC;IACzB,SAAS,EAAE;QACP,OAAO,EAAE,MAAM;QACf,aAAa,EAAE,KAAK;QACpB,UAAU,EAAE,QAAQ;QACpB,GAAG,EAAE,MAAM,CAAC,kBAAkB;KACjC;CACJ,CAAC,CAAC;AAiBH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAA0C,CAAC,KAAK,EAAE,EAAE;IAC1E,aAAa,CAAC,WAAW,GAAG,eAAe,CAAC;IAC5C,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACnD,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;IAE5B,sCAAsC;IACtC,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE;QAChC,OAAO,KAAK,CAAC,QAAQ;aAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;aAChD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI,CAAC;aACnC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE/B,MAAM,mBAAmB,GAAG,CAAC,WAAmB,EAAE,EAAE;QAChD,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,WAAW,CAAC,CAAC;QACtF,QAAQ,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;IAC9B,CAAC,CAAC;IAEF,+CAA+C;IAC/C,MAAM,kBAAkB,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAExE,OAAO,CACH,eAAK,SAAS,EAAE,OAAO,CAAC,SAAS,aAC7B,KAAC,QAAQ,IAAC,KAAK,EAAC,EAAE,EAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,mBAAmB,GAAI,EACxG,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAI,IACrE,CACT,CAAC;AACN,CAAC,CAAC","sourcesContent":["import type { BaseTexture } from \"core/Materials/Textures/baseTexture\";\r\nimport type { Scene } from \"core/scene\";\r\nimport type { Nullable } from \"core/types\";\r\nimport type { FunctionComponent } from \"react\";\r\nimport type { PrimitiveProps } from \"./primitive\";\r\n\r\nimport { makeStyles, tokens } from \"@fluentui/react-components\";\r\nimport { useMemo } from \"react\";\r\nimport { TextureUpload } from \"../hoc/textureUpload\";\r\nimport { ComboBox } from \"./comboBox\";\r\n\r\nconst useStyles = makeStyles({\r\n container: {\r\n display: \"flex\",\r\n flexDirection: \"row\",\r\n alignItems: \"center\",\r\n gap: tokens.spacingHorizontalS,\r\n },\r\n});\r\n\r\nexport type ChooseTextureProps = PrimitiveProps<Nullable<BaseTexture>> & {\r\n /**\r\n * The scene to get textures from\r\n */\r\n scene: Scene;\r\n /**\r\n * File types to accept for upload\r\n */\r\n accept?: string;\r\n /**\r\n * Whether to only allow cube textures\r\n */\r\n cubeOnly?: boolean;\r\n};\r\n\r\n/**\r\n * A primitive component with a ComboBox for selecting from existing scene textures\r\n * and a button for uploading new texture files.\r\n * @param props ChooseTextureProps\r\n * @returns ChooseTexture component\r\n */\r\nexport const ChooseTexture: FunctionComponent<ChooseTextureProps> = (props) => {\r\n ChooseTexture.displayName = \"ChooseTexture\";\r\n const { scene, cubeOnly, value, onChange } = props;\r\n const classes = useStyles();\r\n\r\n // Get sorted texture names from scene\r\n const textureOptions = useMemo(() => {\r\n return scene.textures\r\n .filter((t) => t.name && (!cubeOnly || t.isCube))\r\n .map((t) => t.displayName || t.name)\r\n .sort((a, b) => a.localeCompare(b));\r\n }, [scene.textures, cubeOnly]);\r\n\r\n const handleTextureSelect = (textureName: string) => {\r\n const texture = scene.textures.find((t) => (t.displayName || t.name) === textureName);\r\n onChange(texture ?? null);\r\n };\r\n\r\n // Get current texture name for initial display\r\n const currentTextureName = value ? value.displayName || value.name : \"\";\r\n\r\n return (\r\n <div className={classes.container}>\r\n <ComboBox label=\"\" options={textureOptions} value={currentTextureName} onChange={handleTextureSelect} />\r\n <TextureUpload scene={scene} onChange={onChange} cubeOnly={cubeOnly} />\r\n </div>\r\n );\r\n};\r\n"]}
@@ -1,8 +1,11 @@
1
1
  import type { FunctionComponent } from "react";
2
- export type ComboBoxProps = {
2
+ import type { PrimitiveProps } from "./primitive.js";
3
+ export type ComboBoxProps = PrimitiveProps<string> & {
3
4
  label: string;
4
- value: string[];
5
- onChange: (value: string) => void;
5
+ /**
6
+ * The list of options to display
7
+ */
8
+ options: string[];
6
9
  };
7
10
  /**
8
11
  * Wrapper around a Fluent ComboBox that allows for filtering options
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { useState, useContext, useEffect } from "react";
3
3
  import { Combobox as FluentComboBox, makeStyles, useComboboxFilter, useId } from "@fluentui/react-components";
4
+ import { ToolContext } from "../hoc/fluentToolWrapper.js";
5
+ import { CustomTokens } from "./utils.js";
4
6
  const useStyles = makeStyles({
5
7
  root: {
6
8
  // Stack the label above the field with a gap
@@ -10,6 +12,14 @@ const useStyles = makeStyles({
10
12
  gap: "2px",
11
13
  maxWidth: "400px",
12
14
  },
15
+ comboBox: {
16
+ width: CustomTokens.inputWidth,
17
+ minWidth: CustomTokens.inputWidth,
18
+ boxSizing: "border-box",
19
+ },
20
+ input: {
21
+ minWidth: 0, // Override Fluent's default minWidth on the input
22
+ },
13
23
  });
14
24
  /**
15
25
  * Wrapper around a Fluent ComboBox that allows for filtering options
@@ -20,14 +30,19 @@ export const ComboBox = (props) => {
20
30
  ComboBox.displayName = "ComboBox";
21
31
  const comboId = useId();
22
32
  const styles = useStyles();
23
- const [query, setQuery] = useState("");
24
- const children = useComboboxFilter(query, props.value, {
33
+ const { size } = useContext(ToolContext);
34
+ const [query, setQuery] = useState(props.value ?? "");
35
+ // Sync query with props.value when it changes externally
36
+ useEffect(() => {
37
+ setQuery(props.value ?? "");
38
+ }, [props.value]);
39
+ const children = useComboboxFilter(query, props.options, {
25
40
  noOptionsMessage: "No items match your search.",
26
41
  });
27
42
  const onOptionSelect = (_e, data) => {
28
43
  setQuery(data.optionText ?? "");
29
44
  data.optionText && props.onChange(data.optionText);
30
45
  };
31
- return (_jsxs("div", { className: styles.root, children: [_jsx("label", { id: comboId, children: props.label }), _jsx(FluentComboBox, { onOptionSelect: onOptionSelect, "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
46
+ return (_jsxs("div", { className: styles.root, children: [_jsx("label", { id: comboId, children: props.label }), _jsx(FluentComboBox, { size: size, root: { className: styles.comboBox }, input: { className: styles.input }, onOptionSelect: onOptionSelect, onBlur: () => props.onChange(query), "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
32
47
  };
33
48
  //# sourceMappingURL=comboBox.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"comboBox.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/comboBox.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAE9G,MAAM,SAAS,GAAG,UAAU,CAAC;IACzB,IAAI,EAAE;QACF,6CAA6C;QAC7C,OAAO,EAAE,MAAM;QACf,gBAAgB,EAAE,aAAa;QAC/B,YAAY,EAAE,OAAO;QACrB,GAAG,EAAE,KAAK;QACV,QAAQ,EAAE,OAAO;KACpB;CACJ,CAAC,CAAC;AAOH;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAqC,CAAC,KAAK,EAAE,EAAE;IAChE,QAAQ,CAAC,WAAW,GAAG,UAAU,CAAC;IAClC,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC;IACxB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE;QACnD,gBAAgB,EAAE,6BAA6B;KAClD,CAAC,CAAC;IACH,MAAM,cAAc,GAAG,CAAC,EAAmB,EAAE,IAAwB,EAAE,EAAE;QACrE,QAAQ,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACvD,CAAC,CAAC;IAEF,OAAO,CACH,eAAK,SAAS,EAAE,MAAM,CAAC,IAAI,aACvB,gBAAO,EAAE,EAAE,OAAO,YAAG,KAAK,CAAC,KAAK,GAAS,EACzC,KAAC,cAAc,IAAC,cAAc,EAAE,cAAc,qBAAmB,OAAO,EAAE,WAAW,EAAC,UAAU,EAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,YACrJ,QAAQ,GACI,IACf,CACT,CAAC;AACN,CAAC,CAAC","sourcesContent":["import type { FunctionComponent } from \"react\";\r\nimport { useState } from \"react\";\r\nimport { Combobox as FluentComboBox, makeStyles, useComboboxFilter, useId } from \"@fluentui/react-components\";\r\nimport type { OptionOnSelectData, SelectionEvents } from \"@fluentui/react-components\";\r\nconst useStyles = makeStyles({\r\n root: {\r\n // Stack the label above the field with a gap\r\n display: \"grid\",\r\n gridTemplateRows: \"repeat(1fr)\",\r\n justifyItems: \"start\",\r\n gap: \"2px\",\r\n maxWidth: \"400px\",\r\n },\r\n});\r\n\r\nexport type ComboBoxProps = {\r\n label: string;\r\n value: string[];\r\n onChange: (value: string) => void;\r\n};\r\n/**\r\n * Wrapper around a Fluent ComboBox that allows for filtering options\r\n * @param props\r\n * @returns\r\n */\r\nexport const ComboBox: FunctionComponent<ComboBoxProps> = (props) => {\r\n ComboBox.displayName = \"ComboBox\";\r\n const comboId = useId();\r\n const styles = useStyles();\r\n\r\n const [query, setQuery] = useState(\"\");\r\n const children = useComboboxFilter(query, props.value, {\r\n noOptionsMessage: \"No items match your search.\",\r\n });\r\n const onOptionSelect = (_e: SelectionEvents, data: OptionOnSelectData) => {\r\n setQuery(data.optionText ?? \"\");\r\n data.optionText && props.onChange(data.optionText);\r\n };\r\n\r\n return (\r\n <div className={styles.root}>\r\n <label id={comboId}>{props.label}</label>\r\n <FluentComboBox onOptionSelect={onOptionSelect} aria-labelledby={comboId} placeholder=\"Search..\" onChange={(ev) => setQuery(ev.target.value)} value={query}>\r\n {children}\r\n </FluentComboBox>\r\n </div>\r\n );\r\n};\r\n"]}
1
+ {"version":3,"file":"comboBox.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/comboBox.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACxD,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAE9G,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvC,MAAM,SAAS,GAAG,UAAU,CAAC;IACzB,IAAI,EAAE;QACF,6CAA6C;QAC7C,OAAO,EAAE,MAAM;QACf,gBAAgB,EAAE,aAAa;QAC/B,YAAY,EAAE,OAAO;QACrB,GAAG,EAAE,KAAK;QACV,QAAQ,EAAE,OAAO;KACpB;IACD,QAAQ,EAAE;QACN,KAAK,EAAE,YAAY,CAAC,UAAU;QAC9B,QAAQ,EAAE,YAAY,CAAC,UAAU;QACjC,SAAS,EAAE,YAAY;KAC1B;IACD,KAAK,EAAE;QACH,QAAQ,EAAE,CAAC,EAAE,kDAAkD;KAClE;CACJ,CAAC,CAAC;AASH;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAqC,CAAC,KAAK,EAAE,EAAE;IAChE,QAAQ,CAAC,WAAW,GAAG,UAAU,CAAC;IAClC,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC;IACxB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAEzC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAEtD,yDAAyD;IACzD,SAAS,CAAC,GAAG,EAAE;QACX,QAAQ,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAChC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAElB,MAAM,QAAQ,GAAG,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE;QACrD,gBAAgB,EAAE,6BAA6B;KAClD,CAAC,CAAC;IACH,MAAM,cAAc,GAAG,CAAC,EAAmB,EAAE,IAAwB,EAAE,EAAE;QACrE,QAAQ,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACvD,CAAC,CAAC;IAEF,OAAO,CACH,eAAK,SAAS,EAAE,MAAM,CAAC,IAAI,aACvB,gBAAO,EAAE,EAAE,OAAO,YAAG,KAAK,CAAC,KAAK,GAAS,EACzC,KAAC,cAAc,IACX,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,EACpC,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,EAClC,cAAc,EAAE,cAAc,EAC9B,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,qBAClB,OAAO,EACxB,WAAW,EAAC,UAAU,EACtB,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAC3C,KAAK,EAAE,KAAK,YAEX,QAAQ,GACI,IACf,CACT,CAAC;AACN,CAAC,CAAC","sourcesContent":["import type { FunctionComponent } from \"react\";\r\nimport { useState, useContext, useEffect } from \"react\";\r\nimport { Combobox as FluentComboBox, makeStyles, useComboboxFilter, useId } from \"@fluentui/react-components\";\r\nimport type { OptionOnSelectData, SelectionEvents } from \"@fluentui/react-components\";\r\nimport { ToolContext } from \"../hoc/fluentToolWrapper\";\r\nimport { CustomTokens } from \"./utils\";\r\nimport type { PrimitiveProps } from \"./primitive\";\r\n\r\nconst useStyles = makeStyles({\r\n root: {\r\n // Stack the label above the field with a gap\r\n display: \"grid\",\r\n gridTemplateRows: \"repeat(1fr)\",\r\n justifyItems: \"start\",\r\n gap: \"2px\",\r\n maxWidth: \"400px\",\r\n },\r\n comboBox: {\r\n width: CustomTokens.inputWidth,\r\n minWidth: CustomTokens.inputWidth,\r\n boxSizing: \"border-box\",\r\n },\r\n input: {\r\n minWidth: 0, // Override Fluent's default minWidth on the input\r\n },\r\n});\r\n\r\nexport type ComboBoxProps = PrimitiveProps<string> & {\r\n label: string;\r\n /**\r\n * The list of options to display\r\n */\r\n options: string[];\r\n};\r\n/**\r\n * Wrapper around a Fluent ComboBox that allows for filtering options\r\n * @param props\r\n * @returns\r\n */\r\nexport const ComboBox: FunctionComponent<ComboBoxProps> = (props) => {\r\n ComboBox.displayName = \"ComboBox\";\r\n const comboId = useId();\r\n const styles = useStyles();\r\n const { size } = useContext(ToolContext);\r\n\r\n const [query, setQuery] = useState(props.value ?? \"\");\r\n\r\n // Sync query with props.value when it changes externally\r\n useEffect(() => {\r\n setQuery(props.value ?? \"\");\r\n }, [props.value]);\r\n\r\n const children = useComboboxFilter(query, props.options, {\r\n noOptionsMessage: \"No items match your search.\",\r\n });\r\n const onOptionSelect = (_e: SelectionEvents, data: OptionOnSelectData) => {\r\n setQuery(data.optionText ?? \"\");\r\n data.optionText && props.onChange(data.optionText);\r\n };\r\n\r\n return (\r\n <div className={styles.root}>\r\n <label id={comboId}>{props.label}</label>\r\n <FluentComboBox\r\n size={size}\r\n root={{ className: styles.comboBox }}\r\n input={{ className: styles.input }}\r\n onOptionSelect={onOptionSelect}\r\n onBlur={() => props.onChange(query)}\r\n aria-labelledby={comboId}\r\n placeholder=\"Search..\"\r\n onChange={(ev) => setQuery(ev.target.value)}\r\n value={query}\r\n >\r\n {children}\r\n </FluentComboBox>\r\n </div>\r\n );\r\n};\r\n"]}
@@ -0,0 +1,24 @@
1
+ import type { FunctionComponent } from "react";
2
+ import type { ButtonProps } from "./button.js";
3
+ type UploadButtonProps = Omit<ButtonProps, "onClick" | "icon"> & {
4
+ /**
5
+ * Callback when files are selected
6
+ */
7
+ onUpload: (files: FileList) => void;
8
+ /**
9
+ * File types to accept (e.g., ".jpg, .png, .dds")
10
+ */
11
+ accept?: string;
12
+ /**
13
+ * Text label to display on the button (optional)
14
+ */
15
+ label?: string;
16
+ };
17
+ /**
18
+ * A button that triggers a file upload dialog.
19
+ * Combines a Button with a hidden file input.
20
+ * @param props UploadButtonProps
21
+ * @returns UploadButton component
22
+ */
23
+ export declare const UploadButton: FunctionComponent<UploadButtonProps>;
24
+ export {};
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef } from "react";
3
+ import { ArrowUploadRegular } from "@fluentui/react-icons";
4
+ import { Button } from "./button.js";
5
+ /**
6
+ * A button that triggers a file upload dialog.
7
+ * Combines a Button with a hidden file input.
8
+ * @param props UploadButtonProps
9
+ * @returns UploadButton component
10
+ */
11
+ export const UploadButton = (props) => {
12
+ const { onUpload, accept, label, ...buttonProps } = props;
13
+ UploadButton.displayName = "UploadButton";
14
+ const inputRef = useRef(null);
15
+ const handleClick = () => {
16
+ inputRef.current?.click();
17
+ };
18
+ const handleChange = (evt) => {
19
+ const files = evt.target.files;
20
+ if (files && files.length) {
21
+ onUpload(files);
22
+ }
23
+ evt.target.value = "";
24
+ };
25
+ return (_jsxs(_Fragment, { children: [_jsx(Button, { icon: ArrowUploadRegular, title: label ?? "Upload", label: label, onClick: handleClick, ...buttonProps }), _jsx("input", { ref: inputRef, type: "file", accept: accept, style: { display: "none" }, onChange: handleChange })] }));
26
+ };
27
+ //# sourceMappingURL=uploadButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploadButton.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/fluent/primitives/uploadButton.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAkBlC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAyC,CAAC,KAAK,EAAE,EAAE;IACxE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,WAAW,EAAE,GAAG,KAAK,CAAC;IAC1D,YAAY,CAAC,WAAW,GAAG,cAAc,CAAC;IAC1C,MAAM,QAAQ,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAC;IAEhD,MAAM,WAAW,GAAG,GAAG,EAAE;QACrB,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,CAAC,GAAwC,EAAE,EAAE;QAC9D,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC;QAC/B,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACxB,QAAQ,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;QACD,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;IAC1B,CAAC,CAAC;IAEF,OAAO,CACH,8BACI,KAAC,MAAM,IAAC,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,KAAK,IAAI,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,KAAM,WAAW,GAAI,EACnH,gBAAO,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAC,MAAM,EAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,YAAY,GAAI,IACzG,CACN,CAAC;AACN,CAAC,CAAC","sourcesContent":["import type { FunctionComponent } from \"react\";\r\nimport { useRef } from \"react\";\r\nimport { ArrowUploadRegular } from \"@fluentui/react-icons\";\r\nimport { Button } from \"./button\";\r\nimport type { ButtonProps } from \"./button\";\r\n\r\ntype UploadButtonProps = Omit<ButtonProps, \"onClick\" | \"icon\"> & {\r\n /**\r\n * Callback when files are selected\r\n */\r\n onUpload: (files: FileList) => void;\r\n /**\r\n * File types to accept (e.g., \".jpg, .png, .dds\")\r\n */\r\n accept?: string;\r\n /**\r\n * Text label to display on the button (optional)\r\n */\r\n label?: string;\r\n};\r\n\r\n/**\r\n * A button that triggers a file upload dialog.\r\n * Combines a Button with a hidden file input.\r\n * @param props UploadButtonProps\r\n * @returns UploadButton component\r\n */\r\nexport const UploadButton: FunctionComponent<UploadButtonProps> = (props) => {\r\n const { onUpload, accept, label, ...buttonProps } = props;\r\n UploadButton.displayName = \"UploadButton\";\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n\r\n const handleClick = () => {\r\n inputRef.current?.click();\r\n };\r\n\r\n const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {\r\n const files = evt.target.files;\r\n if (files && files.length) {\r\n onUpload(files);\r\n }\r\n evt.target.value = \"\";\r\n };\r\n\r\n return (\r\n <>\r\n <Button icon={ArrowUploadRegular} title={label ?? \"Upload\"} label={label} onClick={handleClick} {...buttonProps} />\r\n <input ref={inputRef} type=\"file\" accept={accept} style={{ display: \"none\" }} onChange={handleChange} />\r\n </>\r\n );\r\n};\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onerjs/shared-ui-components",
3
- "version": "8.32.8",
3
+ "version": "8.33.1",
4
4
  "main": "index.js",
5
5
  "module": "index.js",
6
6
  "types": "index.d.ts",