-
+
+
{showNewsletterDot &&
}
diff --git a/web/apps/labelstudio/src/components/Userpic/Userpic.jsx b/web/apps/labelstudio/src/components/Userpic/Userpic.jsx
index dbb8f05f91be..cc792ca63506 100644
--- a/web/apps/labelstudio/src/components/Userpic/Userpic.jsx
+++ b/web/apps/labelstudio/src/components/Userpic/Userpic.jsx
@@ -6,73 +6,79 @@ import "./Userpic.scss";
const FALLBACK_IMAGE =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
-export const Userpic = forwardRef(({ username, size, src, user, className, showUsername, style, ...rest }, ref) => {
- const imgRef = useRef();
- const [finalUsername, setFinalUsername] = useState(username);
- const [finalSrc, setFinalSrc] = useState(user?.avatar ?? src);
- const [imgVisible, setImgVisible] = useState(false);
- const [nameVisible, setNameVisible] = useState(true);
+export const Userpic = forwardRef(
+ ({ username, size, src, user, className, showUsername, isInProgress, style, ...rest }, ref) => {
+ const imgRef = useRef();
+ const [finalUsername, setFinalUsername] = useState(username);
+ const [finalSrc, setFinalSrc] = useState(user?.avatar ?? src);
+ const [imgVisible, setImgVisible] = useState(false);
+ const [nameVisible, setNameVisible] = useState(true);
- if (size) {
- style = Object.assign({ width: size, height: size, fontSize: size * 0.4 }, style);
- }
+ if (size) {
+ style = Object.assign({ width: size, height: size, fontSize: size * 0.4 }, style);
+ }
+
+ useEffect(() => {
+ if (isInProgress) {
+ setFinalSrc(null);
+ setImgVisible(false);
+ setNameVisible(true);
+ } else if (user) {
+ const { first_name, last_name, email, initials, username } = user;
- useEffect(() => {
- if (user) {
- const { first_name, last_name, email, initials, username } = user;
+ if (initials) {
+ setFinalUsername(initials);
+ } else if (username) {
+ setFinalUsername(username);
+ } else if (first_name && last_name) {
+ setFinalUsername(`${first_name[0]}${last_name[0]}`);
+ } else if (email) {
+ setFinalUsername(email.substring(0, 2));
+ }
- if (initials) {
- setFinalUsername(initials);
- } else if (username) {
+ if (user.avatar) setFinalSrc(user.avatar);
+ } else {
setFinalUsername(username);
- } else if (first_name && last_name) {
- setFinalUsername(`${first_name[0]}${last_name[0]}`);
- } else if (email) {
- setFinalUsername(email.substring(0, 2));
+ setFinalSrc(src);
}
+ }, [user, isInProgress]);
- if (user.avatar) setFinalSrc(user.avatar);
- } else {
- setFinalUsername(username);
- setFinalSrc(src);
- }
- }, [user]);
-
- const onImageLoaded = useCallback(() => {
- setImgVisible(true);
- if (finalSrc !== FALLBACK_IMAGE) setNameVisible(false);
- }, [finalSrc]);
+ const onImageLoaded = useCallback(() => {
+ setImgVisible(true);
+ if (finalSrc !== FALLBACK_IMAGE) setNameVisible(false);
+ }, [finalSrc]);
- const userpic = (
-
- setFinalSrc(FALLBACK_IMAGE)}
- />
- {nameVisible && (
-
- {(finalUsername ?? "").toUpperCase()}
-
- )}
-
- );
+ const userpic = (
+
+ setFinalSrc(FALLBACK_IMAGE)}
+ />
+ {nameVisible && (
+
+ {(finalUsername ?? "").toUpperCase()}
+
+ )}
+
+ );
- const userFullName = useMemo(() => {
- if (user?.first_name || user?.last_name) {
- return `${user?.first_name ?? ""} ${user?.last_name ?? ""}`.trim();
- }
- if (user?.email) {
- return user.email;
- }
- return username;
- }, [user, username]);
+ const userFullName = useMemo(() => {
+ if (user?.first_name || user?.last_name) {
+ return `${user?.first_name ?? ""} ${user?.last_name ?? ""}`.trim();
+ }
+ if (user?.email) {
+ return user.email;
+ }
+ return username;
+ }, [user, username]);
- return showUsername && userFullName ?
{userpic} : userpic;
-});
+ return showUsername && userFullName ?
{userpic} : userpic;
+ },
+);
Userpic.displayName = "Userpic";
diff --git a/web/apps/labelstudio/src/config/ApiConfig.js b/web/apps/labelstudio/src/config/ApiConfig.js
index 8541f13ae1aa..9d593f7c8087 100644
--- a/web/apps/labelstudio/src/config/ApiConfig.js
+++ b/web/apps/labelstudio/src/config/ApiConfig.js
@@ -3,6 +3,9 @@ export const API_CONFIG = {
endpoints: {
// Users
users: "/users",
+ updateUser: "PATCH:/users/:pk",
+ updateUserAvatar: "POST:/users/:pk/avatar",
+ deleteUserAvatar: "DELETE:/users/:pk/avatar",
me: "/current-user/whoami",
// Organization
diff --git a/web/apps/labelstudio/src/pages/index.js b/web/apps/labelstudio/src/pages/index.js
index 0c35f0e157f7..0009ba51da78 100644
--- a/web/apps/labelstudio/src/pages/index.js
+++ b/web/apps/labelstudio/src/pages/index.js
@@ -1,5 +1,6 @@
import { ProjectsPage } from "./Projects/Projects";
import { OrganizationPage } from "./Organization";
import { ModelsPage } from "./Organization/Models/ModelsPage";
+import { AccountSettingsPage } from "@humansignal/core";
-export const Pages = [ProjectsPage, OrganizationPage, ModelsPage];
+export const Pages = [ProjectsPage, OrganizationPage, ModelsPage, AccountSettingsPage];
diff --git a/web/apps/labelstudio/src/providers/CurrentUser.d.ts b/web/apps/labelstudio/src/providers/CurrentUser.d.ts
index 60ba7025a625..40c80774b405 100644
--- a/web/apps/labelstudio/src/providers/CurrentUser.d.ts
+++ b/web/apps/labelstudio/src/providers/CurrentUser.d.ts
@@ -2,4 +2,6 @@ import type { APIFullUser } from "../../types/User";
declare const useCurrentUser: () => {
user: APIFullUser;
+ fetch: () => Promise
;
+ isInProgress: boolean;
};
diff --git a/web/apps/labelstudio/src/providers/CurrentUser.jsx b/web/apps/labelstudio/src/providers/CurrentUser.jsx
index 050335bb29ed..9d16943990ec 100644
--- a/web/apps/labelstudio/src/providers/CurrentUser.jsx
+++ b/web/apps/labelstudio/src/providers/CurrentUser.jsx
@@ -6,18 +6,20 @@ const CurrentUserContext = createContext();
export const CurrentUserProvider = ({ children }) => {
const api = useAPI();
const [user, setUser] = useState();
+ const [isInProgress, setIsInProgress] = useState(false);
const fetch = useCallback(() => {
- api.callApi("me").then((user) => {
- setUser(user);
- });
+ setIsInProgress(true);
+ api.callApi("me")
+ .then((user) => setUser(user))
+ .finally(() => setIsInProgress(false));
}, []);
useEffect(() => {
fetch();
}, [fetch]);
- return {children};
+ return {children};
};
export const useCurrentUser = () => useContext(CurrentUserContext) ?? {};
diff --git a/web/libs/core/package.json b/web/libs/core/package.json
new file mode 100644
index 000000000000..b0cb866bbd68
--- /dev/null
+++ b/web/libs/core/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@humansignal/core",
+ "version": "0.0.0",
+ "license": "MIT",
+ "private": true,
+ "dependencies": {
+ "react": "17.0.2",
+ "react-dom": "17.0.2"
+ },
+ "main": "src/index.ts"
+}
diff --git a/web/libs/core/src/index.ts b/web/libs/core/src/index.ts
index 90305f0e152a..acc86ac2616c 100644
--- a/web/libs/core/src/index.ts
+++ b/web/libs/core/src/index.ts
@@ -1 +1,2 @@
export * from "./lib/utils/analytics";
+export * from "./pages";
\ No newline at end of file
diff --git a/web/libs/core/src/pages/AccountSettings/AccountSettings.module.scss b/web/libs/core/src/pages/AccountSettings/AccountSettings.module.scss
new file mode 100644
index 000000000000..9bbaae326196
--- /dev/null
+++ b/web/libs/core/src/pages/AccountSettings/AccountSettings.module.scss
@@ -0,0 +1,41 @@
+.accountSettings {
+ display: flex;
+ flex-direction: column;
+
+ &__content {
+ max-width: 660px;
+
+ h1 {
+ font-size: var(--font-size-header, 28px);
+ margin: 0;
+ }
+ }
+}
+
+.sectionContent {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: var(--spacing-large);
+}
+
+.flexRow {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-large);
+ &.flexEnd {
+ justify-content: flex-end;
+ }
+}
+
+.flex1 {
+ flex: 1;
+}
+
+.userPic {
+ flex: none;
+}
+
+.saveButton {
+ width: 125px;
+}
\ No newline at end of file
diff --git a/web/libs/core/src/pages/AccountSettings/AccountSettings.tsx b/web/libs/core/src/pages/AccountSettings/AccountSettings.tsx
new file mode 100644
index 000000000000..2d9f5f2d28e2
--- /dev/null
+++ b/web/libs/core/src/pages/AccountSettings/AccountSettings.tsx
@@ -0,0 +1,46 @@
+import { useMemo } from "react";
+import { Redirect } from "react-router-dom";
+// import { useAPI } from "../../../../../apps/labelstudio/src/providers/ApiProvider";
+// import { SidebarMenu } from "../../../../../apps/labelstudio/src/components/SidebarMenu/SidebarMenu";
+import styles from "./AccountSettings.module.scss";
+import { accountSettingsSections } from "./sections";
+import { Card } from "@humansignal/ui";
+
+export const AccountSettingsPage = () => {
+ // const api = useAPI();
+ const menuItems = useMemo(
+ () => accountSettingsSections.map(({ title, id }) => ({ title, path: `#${id}` })),
+ [accountSettingsSections],
+ );
+
+ return (
+
+ {/*
*/}
+
+ {accountSettingsSections?.map(({ component: Section, id }: any) => (
+
+
+
+ ))}
+
+ {/* */}
+
+ );
+};
+
+AccountSettingsPage.title = "My Account";
+AccountSettingsPage.path = "/user/account";
+AccountSettingsPage.exact = true;
+AccountSettingsPage.routes = () => [
+ {
+ title: () => "My Account",
+ exact: true,
+ component: () => {
+ return ;
+ },
+ // pages: {
+ // DataManagerPage,
+ // SettingsPage,
+ // },
+ },
+];
diff --git a/web/libs/core/src/pages/AccountSettings/sections/EmailPreferences.tsx b/web/libs/core/src/pages/AccountSettings/sections/EmailPreferences.tsx
new file mode 100644
index 000000000000..6db00c3b730f
--- /dev/null
+++ b/web/libs/core/src/pages/AccountSettings/sections/EmailPreferences.tsx
@@ -0,0 +1,47 @@
+import { useCallback, useState } from "react";
+import { Checkbox } from "@humansignal/ui";
+// import { useConfig } from "../../../../../../apps/labelstudio/src/providers/ConfigProvider";
+import { useAPI } from "apps/labelstudio/src/providers/ApiProvider";
+// import { useCurrentUser } from "../../../../../../apps/labelstudio/src/providers/CurrentUser";
+// import { Spinner } from "apps/labelstudio/src/components";
+
+export const EmailPreferences = () => {
+ return <>>
+ // const config = useConfig();
+ // const { user } = useCurrentUser();
+ // const api = useAPI();
+ // const [isLoading, setIsLoading] = useState(false);
+ // const [isAllowNewsLetter, setIsAllowNewsLetter] = useState(config.user.allow_newsletters);
+
+ // const toggleHandler = useCallback(
+ // async (e) => {
+ // setIsAllowNewsLetter(e.target.checked);
+ // setIsLoading(true);
+ // await api.callApi("updateUser", {
+ // params: {
+ // pk: user?.id,
+ // },
+ // body: {
+ // allow_newsletters: e.target.checked ? 1 : 0,
+ // },
+ // });
+ // setIsLoading(false);
+ // },
+ // [user?.id],
+ // );
+
+ // return (
+ //
+ //
+ //
Email Preferences
+
+ // {isLoading ? (
+ //
+ // ) : (
+ //
+ // Subscribe to HumanSignal news and tips from Heidi
+ //
+ // )}
+ //
+ // );
+};
diff --git a/web/libs/core/src/pages/AccountSettings/sections/MembershipInfo.tsx b/web/libs/core/src/pages/AccountSettings/sections/MembershipInfo.tsx
new file mode 100644
index 000000000000..415926ba98d5
--- /dev/null
+++ b/web/libs/core/src/pages/AccountSettings/sections/MembershipInfo.tsx
@@ -0,0 +1,62 @@
+// import { Divider } from "../../../../../../apps/labelstudio/src/components/Divider/Divider";
+// import { useCurrentUser } from "../../../../../../apps/labelstudio/src/providers/CurrentUser";
+// import { useAPI } from "../../../../../../apps/labelstudio/src/providers/ApiProvider";
+// import { OrganizationPage } from "../../../../../../apps/labelstudio/src/pages/Organization";
+export const MembershipInfo = () => {
+ // const api = useAPI();
+ // const { user } = useCurrentUser();
+ const user = {};
+ return (
+
+
+
Membership Info
+
+
+
+
+
+
Annotations submitted
+
+
+
+
+
Projects contributed to
+
+
+
+ {/*
*/}
+
+
+
+
+
+
Organization ID
+
{user?.active_organization}
+
+
+
+
Owner
+
{user?.email}
+
+
+
+
+ );
+};
diff --git a/web/libs/core/src/pages/AccountSettings/sections/PersonalAccessToken.tsx b/web/libs/core/src/pages/AccountSettings/sections/PersonalAccessToken.tsx
new file mode 100644
index 000000000000..0f8825fbeade
--- /dev/null
+++ b/web/libs/core/src/pages/AccountSettings/sections/PersonalAccessToken.tsx
@@ -0,0 +1,32 @@
+// import { Input, TextArea } from "../../../../../../apps/labelstudio/src/components/Form";
+// import { Button } from "../../../../../../apps/labelstudio/src/components/Button/Button";
+// import { IconLaunch } from "apps/labelstudio/src/assets/icons";
+
+export const PersonalAccessToken = () => {
+ return (
+
+
+
Personal Access Token
+
+ Authenticate with our API using your personal access token.
+ {!APP_SETTINGS?.whitelabel_is_active && (
+ <>
+ See{" "}
+
+ {/* Docs */}
+
+ >
+ )}
+
+
+ {/* */}
+
+
+
+ {/* */}
+
+
+ );
+};
diff --git a/web/libs/core/src/pages/AccountSettings/sections/PersonalInfo.tsx b/web/libs/core/src/pages/AccountSettings/sections/PersonalInfo.tsx
new file mode 100644
index 000000000000..d915e32f1821
--- /dev/null
+++ b/web/libs/core/src/pages/AccountSettings/sections/PersonalInfo.tsx
@@ -0,0 +1,127 @@
+import { useCallback, useEffect, useRef, useState } from "react";
+import clsx from "clsx";
+import { InputFile, useToast } from "@humansignal/ui";
+// import { Button } from "apps/labelstudio/src/components";
+// import { Input } from "apps/labelstudio/src/components/Form/Elements";
+// import { Userpic } from "apps/labelstudio/src/components/Userpic/Userpic";
+// import { useCurrentUser } from "../../../../../../apps/labelstudio/src/providers/CurrentUser";
+// import { useAPI } from "apps/labelstudio/src/providers/ApiProvider";
+import styles from "../AccountSettings.module.scss";
+
+export const PersonalInfo = () => {
+ return <>>
+ // const api = useAPI();
+ // const toast = useToast();
+ // const { user, fetch, isInProgress: userInProgress } = useCurrentUser();
+ // const [fname, setFName] = useState("");
+ // const [lname, setLName] = useState("");
+ // const [email, setEmail] = useState("");
+ // const [phone, setPhone] = useState("");
+ // const [isInProgress, setIsInProgress] = useState(false);
+ // const userInfoForm = useRef();
+ // const userAvatarForm = useRef();
+ // const avatarRef = useRef();
+ // const fileChangeHandler = (e) => userAvatarForm.current.requestSubmit();
+ // const avatarFormSubmitHandler = useCallback(
+ // async (e, isDelete = false) => {
+ // e.preventDefault();
+ // const response = await api.callApi(isDelete ? "deleteUserAvatar" : "updateUserAvatar", {
+ // params: {
+ // pk: user?.id,
+ // },
+ // body: {
+ // avatar: avatarRef.current.files[0],
+ // },
+ // headers: {
+ // "Content-Type": "multipart/form-data",
+ // },
+ // errorFilter: () => true,
+ // });
+ // if (!isDelete && response?.status) {
+ // toast.show({ message: response?.response?.detail ?? "Error updating avatar", type: "error" });
+ // } else {
+ // fetch();
+ // }
+ // userAvatarForm.current.reset();
+ // },
+ // [user?.id, fetch],
+ // );
+ // const userFormSubmitHandler = useCallback(async (e) => {
+ // e.preventDefault();
+ // const response = await api.callApi("updateUser", {
+ // params: {
+ // pk: user?.id,
+ // },
+ // body: {
+ // first_name: fname,
+ // last_name: lname,
+ // phone,
+ // },
+ // errorFilter: () => true,
+ // });
+ // if (response?.status) {
+ // toast.show({ message: response?.response?.detail ?? "Error updating user", type: "error" });
+ // } else {
+ // fetch();
+ // }
+ // }, [fname, lname, phone, user?.id]);
+
+ // useEffect(() => {
+ // if (userInProgress) return;
+ // setFName(user?.first_name);
+ // setLName(user?.last_name);
+ // setEmail(user?.email);
+ // setPhone(user?.phone);
+ // setIsInProgress(userInProgress);
+ // }, [user, userInProgress]);
+
+ // useEffect(() => setIsInProgress(userInProgress), [userInProgress]);
+
+ // return (
+ //
+ //
+ //
+ //
Personal Info
+ //
+ //
+ //
+ // {user?.avatar && (
+ //
+ // )}
+ //
+ //
+ //
+ //
+ // );
+};
diff --git a/web/libs/core/src/pages/AccountSettings/sections/index.tsx b/web/libs/core/src/pages/AccountSettings/sections/index.tsx
new file mode 100644
index 000000000000..baf7fbe67122
--- /dev/null
+++ b/web/libs/core/src/pages/AccountSettings/sections/index.tsx
@@ -0,0 +1,32 @@
+import { PersonalInfo } from "./PersonalInfo";
+import { EmailPreferences } from "./EmailPreferences";
+import type React from "react";
+import { PersonalAccessToken } from "./PersonalAccessToken";
+import { MembershipInfo } from "./MembershipInfo";
+type SectionType = {
+ title: string;
+ id: string;
+ component: React.FC;
+};
+export const accountSettingsSections: SectionType[] = [
+ {
+ title: "Personal Info",
+ id: "personal-info",
+ component: PersonalInfo,
+ },
+ {
+ title: "Email Preferences",
+ id: "email-preferences",
+ component: EmailPreferences,
+ },
+ {
+ title: "Personal Access Token",
+ id: "personal-access-token",
+ component: PersonalAccessToken,
+ },
+ {
+ title: "Membership Info",
+ id: "membership-info",
+ component: MembershipInfo,
+ },
+];
diff --git a/web/libs/core/src/pages/index.ts b/web/libs/core/src/pages/index.ts
new file mode 100644
index 000000000000..22c3c2f50a97
--- /dev/null
+++ b/web/libs/core/src/pages/index.ts
@@ -0,0 +1 @@
+export * from './AccountSettings/AccountSettings';
\ No newline at end of file
diff --git a/web/libs/ui/src/assets/icons/index.ts b/web/libs/ui/src/assets/icons/index.ts
index 7ad660b91020..5c1999ac152c 100644
--- a/web/libs/ui/src/assets/icons/index.ts
+++ b/web/libs/ui/src/assets/icons/index.ts
@@ -1 +1,2 @@
export { ReactComponent as IconCross } from "./cross.svg";
+export { ReactComponent as IconUpload } from "./upload.svg";
diff --git a/web/libs/ui/src/assets/icons/upload.svg b/web/libs/ui/src/assets/icons/upload.svg
new file mode 100644
index 000000000000..22fc82a9c819
--- /dev/null
+++ b/web/libs/ui/src/assets/icons/upload.svg
@@ -0,0 +1,3 @@
+
diff --git a/web/libs/ui/src/index.ts b/web/libs/ui/src/index.ts
index 72ab72d32482..5c3c93ceb772 100644
--- a/web/libs/ui/src/index.ts
+++ b/web/libs/ui/src/index.ts
@@ -1,5 +1,7 @@
export * from "./lib/checkbox/checkbox";
export * from "./lib/Toast/Toast";
+export * from "./lib/InputFile/InputFile";
+export * from "./lib/Card/Card";
export * from "./lib/label/label";
export * from "./lib/toggle/toggle";
diff --git a/web/libs/ui/src/lib/Card/Card.module.scss b/web/libs/ui/src/lib/Card/Card.module.scss
new file mode 100644
index 000000000000..9a85a3f2170e
--- /dev/null
+++ b/web/libs/ui/src/lib/Card/Card.module.scss
@@ -0,0 +1,30 @@
+.card {
+ border-radius: 5px;
+ background-color: var(--sand_0);
+ border: 1px solid var(--sand_300);
+
+ &__header {
+ display: flex;
+ height: 48px;
+ padding: 0 15px;
+ align-items: center;
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 18px;
+ justify-content: space-between;
+ box-shadow: 0 1px 0 0 rgb(0 0 0 / 10%);
+
+ &-content {
+ display: flex;
+ align-items: center;
+ }
+ }
+
+ &__content {
+ padding: 15px;
+ }
+
+ &:not(:first-child) {
+ margin-top: 24px;
+ }
+}
\ No newline at end of file
diff --git a/web/libs/ui/src/lib/Card/Card.tsx b/web/libs/ui/src/lib/Card/Card.tsx
new file mode 100644
index 000000000000..bd0270d02082
--- /dev/null
+++ b/web/libs/ui/src/lib/Card/Card.tsx
@@ -0,0 +1,23 @@
+import styles from "./Card.module.scss";
+
+type CardProps = {
+ header?: React.ReactNode;
+ extra?: React.ReactNode;
+ children: React.ReactNode;
+ style?: React.CSSProperties;
+};
+
+export const Card = ({ header, extra, children, style }: CardProps) => {
+ return (
+
+ {(header || extra) && (
+
+
{header}
+
+ {extra &&
{extra}
}
+
+ )}
+
{children}
+
+ );
+};
diff --git a/web/libs/ui/src/lib/InputFile/InputFile.module.scss b/web/libs/ui/src/lib/InputFile/InputFile.module.scss
new file mode 100644
index 000000000000..cd0bd2341dac
--- /dev/null
+++ b/web/libs/ui/src/lib/InputFile/InputFile.module.scss
@@ -0,0 +1,37 @@
+.input {
+ border: 0 none;
+ padding: 0;
+ display: block;
+ width: 100%;
+ cursor: pointer;
+ outline: none;
+}
+
+.labelContent {
+ position: absolute;
+ left: 0;
+ top: 0;
+ border: 1px solid var(--primary_link);
+ color: var(--primary_link);
+ border-radius: var(--radius-xs);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: var(--spacing-small);
+ background-color: var(--white);
+}
+
+.inputWrapper {
+ position: relative;
+ display: flex;
+ height: 42px;
+ width: 100%;
+ margin: 0;
+ cursor: pointer;
+ align-items: center;
+ outline: none;
+
+ &:focus-within .labelContent {
+ outline: 2px solid var(--primary_link);
+ }
+}
\ No newline at end of file
diff --git a/web/libs/ui/src/lib/InputFile/InputFile.tsx b/web/libs/ui/src/lib/InputFile/InputFile.tsx
new file mode 100644
index 000000000000..4bb65f5f409f
--- /dev/null
+++ b/web/libs/ui/src/lib/InputFile/InputFile.tsx
@@ -0,0 +1,43 @@
+import { IconUpload } from "../../assets/icons";
+import clsx from "clsx";
+type InputFileProps = {
+ name?: string;
+ className?: string;
+ text?: React.ReactNode | string;
+ onChange?: (e: React.ChangeEvent) => void;
+ props?: Record;
+};
+import styles from "./InputFile.module.scss";
+import type React from "react";
+import { forwardRef, useCallback, useRef } from "react";
+export const InputFile = forwardRef(({ name, className, text, onChange, ...props }: InputFileProps, ref: any) => {
+ if (!ref) {
+ ref = useRef();
+ }
+ const interactiveKeys = ["Space", " "];
+ const wrapperKeyDownHandler = useCallback(
+ (e: any) => {
+ if (interactiveKeys.includes(e.key)) {
+ e.preventDefault();
+ ref.current.click();
+ }
+ },
+ [ref],
+ );
+ return (
+
+ );
+});