@databiosphere/findable-ui 50.1.1 → 50.3.0
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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +27 -0
- package/lib/components/Layout/components/Footer/components/PoweredByCleverCanary/poweredByCleverCanary.d.ts +12 -0
- package/lib/components/Layout/components/Footer/components/PoweredByCleverCanary/poweredByCleverCanary.js +17 -0
- package/lib/components/Layout/components/Footer/components/PoweredByCleverCanary/types.d.ts +6 -0
- package/lib/components/Layout/components/Footer/components/PoweredByCleverCanary/types.js +1 -0
- package/lib/components/Layout/components/Footer/footer.d.ts +5 -1
- package/lib/components/Layout/components/Footer/footer.js +4 -3
- package/lib/components/Layout/components/Footer/stories/footer.stories.d.ts +6 -0
- package/lib/components/Layout/components/Footer/{footer.stories.js → stories/footer.stories.js} +11 -19
- package/lib/views/ResearchView/assistant/assistant.js +1 -1
- package/lib/views/ResearchView/assistant/components/Form/form.js +1 -9
- package/lib/views/ResearchView/assistant/components/Input/hooks/UseControlledInput/hook.d.ts +6 -0
- package/lib/views/ResearchView/assistant/components/Input/hooks/UseControlledInput/hook.js +26 -0
- package/lib/views/ResearchView/assistant/components/Input/hooks/UseControlledInput/types.d.ts +9 -0
- package/lib/views/ResearchView/assistant/components/Input/hooks/UseControlledInput/types.js +1 -0
- package/lib/views/ResearchView/assistant/components/Input/hooks/UseKeyShortCuts/hook.d.ts +3 -1
- package/lib/views/ResearchView/assistant/components/Input/hooks/UseKeyShortCuts/hook.js +6 -5
- package/lib/views/ResearchView/assistant/components/Input/hooks/UseKeyShortCuts/types.d.ts +3 -1
- package/lib/views/ResearchView/assistant/components/Input/hooks/UseKeyShortCuts/utils.d.ts +13 -7
- package/lib/views/ResearchView/assistant/components/Input/hooks/UseKeyShortCuts/utils.js +15 -11
- package/lib/views/ResearchView/assistant/components/Input/input.js +7 -5
- package/lib/views/ResearchView/assistant/components/Input/types.d.ts +1 -1
- package/package.json +4 -4
- package/src/components/Layout/components/Footer/components/PoweredByCleverCanary/poweredByCleverCanary.tsx +31 -0
- package/src/components/Layout/components/Footer/components/PoweredByCleverCanary/types.ts +8 -0
- package/src/components/Layout/components/Footer/footer.tsx +6 -1
- package/src/components/Layout/components/Footer/stories/footer.stories.tsx +63 -0
- package/src/images/cc.svg +18 -0
- package/src/views/ResearchView/assistant/assistant.tsx +1 -1
- package/src/views/ResearchView/assistant/components/Form/form.tsx +1 -8
- package/src/views/ResearchView/assistant/components/Input/hooks/UseControlledInput/hook.ts +33 -0
- package/src/views/ResearchView/assistant/components/Input/hooks/UseControlledInput/types.ts +12 -0
- package/src/views/ResearchView/assistant/components/Input/hooks/UseKeyShortCuts/hook.ts +10 -8
- package/src/views/ResearchView/assistant/components/Input/hooks/UseKeyShortCuts/types.ts +4 -1
- package/src/views/ResearchView/assistant/components/Input/hooks/UseKeyShortCuts/utils.ts +19 -17
- package/src/views/ResearchView/assistant/components/Input/input.tsx +15 -6
- package/src/views/ResearchView/assistant/components/Input/types.ts +2 -1
- package/tests/research.useKeyShortCuts.test.ts +26 -24
- package/lib/components/Layout/components/Footer/footer.stories.d.ts +0 -27
- package/src/components/Layout/components/Footer/footer.stories.tsx +0 -69
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [50.3.0](https://github.com/DataBiosphere/findable-ui/compare/v50.2.1...v50.3.0) (2026-03-27)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* convert input to controlled component with inputprovider ([#815](https://github.com/DataBiosphere/findable-ui/issues/815)) ([#843](https://github.com/DataBiosphere/findable-ui/issues/843)) ([699f186](https://github.com/DataBiosphere/findable-ui/commit/699f186f8bff76a0806e2afe93bfd5815fea698b))
|
|
9
|
+
|
|
10
|
+
## [50.2.1](https://github.com/DataBiosphere/findable-ui/compare/v50.2.0...v50.2.1) (2026-03-25)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* set poweredbyclevercanary image height to 32px ([#845](https://github.com/DataBiosphere/findable-ui/issues/845)) ([fd1cbcc](https://github.com/DataBiosphere/findable-ui/commit/fd1cbcc985f4b56d7cca53d5eddf0c710d889c92))
|
|
16
|
+
|
|
17
|
+
## [50.2.0](https://github.com/DataBiosphere/findable-ui/compare/v50.1.1...v50.2.0) (2026-03-25)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* add poweredbyclevercanary component to footer ([#838](https://github.com/DataBiosphere/findable-ui/issues/838)) ([#840](https://github.com/DataBiosphere/findable-ui/issues/840)) ([fbdcfaf](https://github.com/DataBiosphere/findable-ui/commit/fbdcfaf1b377ed765934cb885a923ae06122c540))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Chores
|
|
26
|
+
|
|
27
|
+
* update storybook to 10.3.3 ([#839](https://github.com/DataBiosphere/findable-ui/issues/839)) ([95b3792](https://github.com/DataBiosphere/findable-ui/commit/95b3792dc7033f8ff664e247285c205243b1fdce))
|
|
28
|
+
* update storybook to 10.3.3 ([#841](https://github.com/DataBiosphere/findable-ui/issues/841)) ([95b3792](https://github.com/DataBiosphere/findable-ui/commit/95b3792dc7033f8ff664e247285c205243b1fdce))
|
|
29
|
+
|
|
3
30
|
## [50.1.1](https://github.com/DataBiosphere/findable-ui/compare/v50.1.0...v50.1.1) (2026-03-23)
|
|
4
31
|
|
|
5
32
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { JSX } from "react";
|
|
2
|
+
import { PoweredByCleverCanaryProps } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* A component that displays a "Powered by CleverCanary" link with an image.
|
|
5
|
+
*
|
|
6
|
+
* @param props - Component props.
|
|
7
|
+
* @param props.className - An optional class name for styling.
|
|
8
|
+
* @param props.alt - The alt text for the image.
|
|
9
|
+
* @param props.src - The source URL for the image.
|
|
10
|
+
* @returns The rendered component or null if not powered by CleverCanary.
|
|
11
|
+
*/
|
|
12
|
+
export declare const PoweredByCleverCanary: ({ alt, className, ...props }: PoweredByCleverCanaryProps) => JSX.Element | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ANCHOR_TARGET } from "../../../../../Links/common/entities";
|
|
3
|
+
import { Logo } from "../../../Header/components/Content/components/Logo/logo";
|
|
4
|
+
/**
|
|
5
|
+
* A component that displays a "Powered by CleverCanary" link with an image.
|
|
6
|
+
*
|
|
7
|
+
* @param props - Component props.
|
|
8
|
+
* @param props.className - An optional class name for styling.
|
|
9
|
+
* @param props.alt - The alt text for the image.
|
|
10
|
+
* @param props.src - The source URL for the image.
|
|
11
|
+
* @returns The rendered component or null if not powered by CleverCanary.
|
|
12
|
+
*/
|
|
13
|
+
export const PoweredByCleverCanary = ({ alt = "Powered by CleverCanary", className, ...props /* StaticImageProps */ }) => {
|
|
14
|
+
if (!props.src)
|
|
15
|
+
return null;
|
|
16
|
+
return (_jsx(Logo, { alt: alt, className: className, height: 32, link: "https://www.clevercanary.com", src: props.src, target: ANCHOR_TARGET.BLANK }));
|
|
17
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { StaticImageProps } from "../../../../../common/StaticImage/staticImage";
|
|
2
|
+
import { BaseComponentProps } from "../../../../../types";
|
|
3
|
+
export type PoweredByCleverCanaryProps = BaseComponentProps & Omit<StaticImageProps, "alt" | "src"> & {
|
|
4
|
+
alt?: string;
|
|
5
|
+
src?: StaticImageProps["src"];
|
|
6
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { JSX, ReactNode } from "react";
|
|
2
2
|
import { Social } from "../../../common/Socials/socials";
|
|
3
|
+
import { StaticImageProps } from "../../../common/StaticImage/staticImage";
|
|
3
4
|
import { NavLinkItem } from "../Header/components/Content/components/Navigation/navigation";
|
|
4
5
|
export interface FooterProps {
|
|
5
6
|
Branding?: ReactNode;
|
|
6
7
|
className?: string;
|
|
7
8
|
navLinks?: NavLinkItem[];
|
|
9
|
+
poweredByCC?: Omit<StaticImageProps, "alt"> & {
|
|
10
|
+
alt?: string;
|
|
11
|
+
};
|
|
8
12
|
socials?: Social[];
|
|
9
13
|
versionInfo?: ReactNode;
|
|
10
14
|
}
|
|
11
|
-
export declare const Footer: ({ Branding, className, navLinks, socials, versionInfo, }: FooterProps) => JSX.Element;
|
|
15
|
+
export declare const Footer: ({ Branding, className, navLinks, poweredByCC, socials, versionInfo, }: FooterProps) => JSX.Element;
|
|
@@ -4,10 +4,11 @@ import { SELECTOR } from "../../../../common/selectors";
|
|
|
4
4
|
import { useLayoutDimensions } from "../../../../providers/layoutDimensions/hook";
|
|
5
5
|
import { ANCHOR_TARGET } from "../../../Links/common/entities";
|
|
6
6
|
import { isNodeBoolean } from "../../../utils";
|
|
7
|
+
import { PoweredByCleverCanary } from "./components/PoweredByCleverCanary/poweredByCleverCanary";
|
|
7
8
|
import { VersionInfo } from "./components/VersionInfo/versionInfo";
|
|
8
9
|
import { AppBar, Link, Links, Socials } from "./footer.styles";
|
|
9
|
-
export const Footer = ({ Branding, className, navLinks, socials, versionInfo, }) => {
|
|
10
|
+
export const Footer = ({ Branding, className, navLinks, poweredByCC, socials, versionInfo, }) => {
|
|
10
11
|
const { footerRef } = useLayoutDimensions();
|
|
11
|
-
return (_jsx(AppBar, { className: className, color: "inherit", component: "footer", id: SELECTOR.FOOTER, ref: footerRef, variant: "footer", children: _jsxs(Toolbar, { variant: "dense", children: [Branding, (navLinks || socials || versionInfo) && (_jsxs(Links, { children: [navLinks &&
|
|
12
|
-
navLinks.map(({ label, target = ANCHOR_TARGET.SELF, url }, i) => (_jsx(Link, { label: label, target: target, url: url }, `${url}${i}`))), socials && _jsx(Socials, { buttonSize: "small", socials: socials }), isNodeBoolean(versionInfo) ? _jsx(VersionInfo, {}) : versionInfo] }))] }) }));
|
|
12
|
+
return (_jsx(AppBar, { className: className, color: "inherit", component: "footer", id: SELECTOR.FOOTER, ref: footerRef, variant: "footer", children: _jsxs(Toolbar, { variant: "dense", children: [Branding, (navLinks || socials || versionInfo || poweredByCC) && (_jsxs(Links, { children: [navLinks &&
|
|
13
|
+
navLinks.map(({ label, target = ANCHOR_TARGET.SELF, url }, i) => (_jsx(Link, { label: label, target: target, url: url }, `${url}${i}`))), socials && _jsx(Socials, { buttonSize: "small", socials: socials }), isNodeBoolean(versionInfo) ? _jsx(VersionInfo, {}) : versionInfo, _jsx(PoweredByCleverCanary, { ...poweredByCC })] }))] }) }));
|
|
13
14
|
};
|
package/lib/components/Layout/components/Footer/{footer.stories.js → stories/footer.stories.js}
RENAMED
|
@@ -1,28 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { Footer } from "./footer";
|
|
2
|
+
import { DiscourseIcon } from "../../../../common/CustomIcon/components/DiscourseIcon/discourseIcon";
|
|
3
|
+
import { FacebookIcon } from "../../../../common/CustomIcon/components/FacebookIcon/facebookIcon";
|
|
4
|
+
import { GitHubIcon } from "../../../../common/CustomIcon/components/GitHubIcon/gitHubIcon";
|
|
5
|
+
import { XIcon } from "../../../../common/CustomIcon/components/XIcon/xIcon";
|
|
6
|
+
import { YouTubeIcon } from "../../../../common/CustomIcon/components/YouTubeIcon/youTubeIcon";
|
|
7
|
+
import { Logo } from "../../Header/components/Content/components/Logo/logo";
|
|
8
|
+
import { Footer } from "../footer";
|
|
10
9
|
const meta = {
|
|
11
|
-
argTypes: {
|
|
12
|
-
Branding: { control: { disabled: true } },
|
|
13
|
-
navLinks: { control: "object" },
|
|
14
|
-
socials: { control: { disabled: true } },
|
|
15
|
-
},
|
|
16
10
|
component: Footer,
|
|
17
|
-
parameters: {
|
|
18
|
-
layout: "fullscreen",
|
|
19
|
-
},
|
|
20
|
-
title: "Components/Layout/Footer",
|
|
11
|
+
parameters: { layout: "fullscreen" },
|
|
21
12
|
};
|
|
22
13
|
export default meta;
|
|
23
|
-
export const
|
|
14
|
+
export const Default = {
|
|
24
15
|
args: {
|
|
25
|
-
Branding: _jsx(Logo, { alt: "logo", height: 24, link: "/", src: logo.
|
|
16
|
+
Branding: (_jsx(Logo, { alt: "logo", height: 24, link: "/", src: "src/images/logo.svg" })),
|
|
26
17
|
navLinks: [
|
|
27
18
|
{
|
|
28
19
|
label: "Help",
|
|
@@ -33,6 +24,7 @@ export const FooterStory = {
|
|
|
33
24
|
url: "https://policies.google.com/privacy?hl=en-US",
|
|
34
25
|
},
|
|
35
26
|
],
|
|
27
|
+
poweredByCC: { src: "src/images/cc.svg" },
|
|
36
28
|
socials: [
|
|
37
29
|
{
|
|
38
30
|
Icon: DiscourseIcon,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useChatState } from "../state/hooks/UseChatState/hook";
|
|
3
|
+
import { Drawer } from "./components/Drawer/drawer";
|
|
3
4
|
import { Form } from "./components/Form/form";
|
|
4
5
|
import { Input } from "./components/Input/input";
|
|
5
6
|
import { getPlaceholder } from "./components/Input/utils";
|
|
6
7
|
import { Messages } from "./components/Messages/messages";
|
|
7
|
-
import { Drawer } from "./components/Drawer/drawer";
|
|
8
8
|
import { ToggleButtonGroup } from "./components/ToggleButtonGroup/toggleButtonGroup";
|
|
9
9
|
/**
|
|
10
10
|
* Renders the research assistant drawer.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { TEST_IDS } from "../../../../../tests/testIds";
|
|
3
3
|
import { useQuery } from "../../../state/query/hooks/UseQuery/hook";
|
|
4
|
-
import { FIELD_NAME } from "./constants";
|
|
5
4
|
import { StyledForm } from "./form.styles";
|
|
6
5
|
import { getPayload } from "./utils";
|
|
7
6
|
/**
|
|
@@ -15,13 +14,6 @@ import { getPayload } from "./utils";
|
|
|
15
14
|
export const Form = ({ children, className, status, }) => {
|
|
16
15
|
const { onSubmit } = useQuery();
|
|
17
16
|
return (_jsx(StyledForm, { className: className, "data-testid": TEST_IDS.RESEARCH_FORM, onSubmit: async (e) => {
|
|
18
|
-
await onSubmit(e, getPayload(e), {
|
|
19
|
-
onSettled: (form) => {
|
|
20
|
-
const input = form.elements.namedItem(FIELD_NAME.AI_PROMPT);
|
|
21
|
-
if (input instanceof HTMLElement)
|
|
22
|
-
input.focus();
|
|
23
|
-
},
|
|
24
|
-
status,
|
|
25
|
-
});
|
|
17
|
+
await onSubmit(e, getPayload(e), { status });
|
|
26
18
|
}, children: children }));
|
|
27
19
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { UseControlledInputProps } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Manages controlled input state with automatic clearing on form reset.
|
|
4
|
+
* @returns The controlled input state including value, onChange, setValue, and inputRef.
|
|
5
|
+
*/
|
|
6
|
+
export declare const useControlledInput: () => UseControlledInputProps;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Manages controlled input state with automatic clearing on form reset.
|
|
4
|
+
* @returns The controlled input state including value, onChange, setValue, and inputRef.
|
|
5
|
+
*/
|
|
6
|
+
export const useControlledInput = () => {
|
|
7
|
+
const inputRef = useRef(null);
|
|
8
|
+
const [value, setValue] = useState("");
|
|
9
|
+
const onChange = useCallback((e) => {
|
|
10
|
+
setValue(e.target.value);
|
|
11
|
+
}, []);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const form = inputRef.current?.form;
|
|
14
|
+
if (!form)
|
|
15
|
+
return;
|
|
16
|
+
const onReset = () => {
|
|
17
|
+
setValue("");
|
|
18
|
+
inputRef.current?.focus();
|
|
19
|
+
};
|
|
20
|
+
form.addEventListener("reset", onReset);
|
|
21
|
+
return () => {
|
|
22
|
+
form.removeEventListener("reset", onReset);
|
|
23
|
+
};
|
|
24
|
+
}, []);
|
|
25
|
+
return { inputRef, onChange, setValue, value };
|
|
26
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ChangeEvent, Dispatch, RefObject, SetStateAction } from "react";
|
|
2
|
+
export type InputElement = HTMLInputElement | HTMLTextAreaElement;
|
|
3
|
+
export type SetValue = Dispatch<SetStateAction<string>>;
|
|
4
|
+
export interface UseControlledInputProps {
|
|
5
|
+
inputRef: RefObject<InputElement | null>;
|
|
6
|
+
onChange: (e: ChangeEvent<InputElement>) => void;
|
|
7
|
+
setValue: SetValue;
|
|
8
|
+
value: string;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { SetValue } from "../UseControlledInput/types";
|
|
1
2
|
import { UseKeyShortCutsProps } from "./types";
|
|
2
3
|
/**
|
|
3
4
|
* Provides a keydown handler that implements keyboard shortcuts for the assistant input.
|
|
5
|
+
* @param setValue - Setter for the controlled input value.
|
|
4
6
|
* @returns An object containing the onKeyDown handler.
|
|
5
7
|
*/
|
|
6
|
-
export declare const useKeyShortCuts: () => UseKeyShortCutsProps;
|
|
8
|
+
export declare const useKeyShortCuts: (setValue: SetValue) => UseKeyShortCutsProps;
|
|
@@ -4,9 +4,10 @@ import { KEY } from "./constants";
|
|
|
4
4
|
import { getHistory, handleArrowKey, handleEnterKey, handleEscapeKey, handleTabKey, } from "./utils";
|
|
5
5
|
/**
|
|
6
6
|
* Provides a keydown handler that implements keyboard shortcuts for the assistant input.
|
|
7
|
+
* @param setValue - Setter for the controlled input value.
|
|
7
8
|
* @returns An object containing the onKeyDown handler.
|
|
8
9
|
*/
|
|
9
|
-
export const useKeyShortCuts = () => {
|
|
10
|
+
export const useKeyShortCuts = (setValue) => {
|
|
10
11
|
const { state } = useChatState();
|
|
11
12
|
const { messages } = state;
|
|
12
13
|
const history = getHistory(messages);
|
|
@@ -22,11 +23,11 @@ export const useKeyShortCuts = () => {
|
|
|
22
23
|
if (e.key === KEY.ENTER)
|
|
23
24
|
return handleEnterKey(e);
|
|
24
25
|
if (e.key === KEY.ESCAPE)
|
|
25
|
-
return handleEscapeKey(
|
|
26
|
+
return handleEscapeKey(refs, setValue);
|
|
26
27
|
if (e.key === KEY.ARROW_UP || e.key === KEY.ARROW_DOWN)
|
|
27
|
-
return handleArrowKey(e, history, refs);
|
|
28
|
+
return handleArrowKey(e, history, refs, setValue);
|
|
28
29
|
if (e.key === KEY.TAB)
|
|
29
|
-
return handleTabKey(e);
|
|
30
|
-
}, [history]);
|
|
30
|
+
return handleTabKey(e, setValue);
|
|
31
|
+
}, [history, setValue]);
|
|
31
32
|
return { onKeyDown };
|
|
32
33
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { KeyboardEvent, RefObject } from "react";
|
|
2
|
+
import { InputElement } from "../UseControlledInput/types";
|
|
3
|
+
export type KeyboardInputEvent = KeyboardEvent<InputElement>;
|
|
2
4
|
export interface Refs {
|
|
3
5
|
draftRef: RefObject<string>;
|
|
4
6
|
historyIndexRef: RefObject<number>;
|
|
5
7
|
}
|
|
6
8
|
export interface UseKeyShortCutsProps {
|
|
7
|
-
onKeyDown: (e:
|
|
9
|
+
onKeyDown: (e: KeyboardInputEvent) => void;
|
|
8
10
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { KeyboardEvent } from "react";
|
|
2
1
|
import { Message } from "../../../../../state/types";
|
|
3
|
-
import {
|
|
2
|
+
import { SetValue } from "../UseControlledInput/types";
|
|
3
|
+
import { KeyboardInputEvent, Refs } from "./types";
|
|
4
4
|
/**
|
|
5
5
|
* Extracts the text of user messages from a list of messages and returns them in reverse order.
|
|
6
6
|
* @param messages - An array of Message objects to extract user messages from.
|
|
@@ -12,21 +12,27 @@ export declare function getHistory(messages: Message[]): string[];
|
|
|
12
12
|
* @param e - The keyboard event.
|
|
13
13
|
* @param history - The history entries to navigate.
|
|
14
14
|
* @param refs - Refs for draft text and history index.
|
|
15
|
+
* @param setValue - Setter for the controlled input value.
|
|
16
|
+
* @returns Void.
|
|
15
17
|
*/
|
|
16
|
-
export declare function handleArrowKey(e:
|
|
18
|
+
export declare function handleArrowKey(e: KeyboardInputEvent, history: string[], refs: Refs, setValue: SetValue): void;
|
|
17
19
|
/**
|
|
18
20
|
* Handles the Enter key press to submit the form, or allows newline on Shift+Enter.
|
|
19
21
|
* @param e - The keyboard event.
|
|
22
|
+
* @returns Void.
|
|
20
23
|
*/
|
|
21
|
-
export declare function handleEnterKey(e:
|
|
24
|
+
export declare function handleEnterKey(e: KeyboardInputEvent): void;
|
|
22
25
|
/**
|
|
23
26
|
* Handles the Escape key press to clear the input and reset history navigation.
|
|
24
|
-
* @param e - The keyboard event.
|
|
25
27
|
* @param refs - Refs for draft text and history index.
|
|
28
|
+
* @param setValue - Setter for the controlled input value.
|
|
29
|
+
* @returns Void.
|
|
26
30
|
*/
|
|
27
|
-
export declare function handleEscapeKey(
|
|
31
|
+
export declare function handleEscapeKey(refs: Refs, setValue: SetValue): void;
|
|
28
32
|
/**
|
|
29
33
|
* Handles the Tab key press to auto-fill the input with the placeholder.
|
|
30
34
|
* @param e - The keyboard event.
|
|
35
|
+
* @param setValue - Setter for the controlled input value.
|
|
36
|
+
* @returns Void.
|
|
31
37
|
*/
|
|
32
|
-
export declare function handleTabKey(e:
|
|
38
|
+
export declare function handleTabKey(e: KeyboardInputEvent, setValue: SetValue): void;
|
|
@@ -16,31 +16,33 @@ export function getHistory(messages) {
|
|
|
16
16
|
* @param e - The keyboard event.
|
|
17
17
|
* @param history - The history entries to navigate.
|
|
18
18
|
* @param refs - Refs for draft text and history index.
|
|
19
|
+
* @param setValue - Setter for the controlled input value.
|
|
20
|
+
* @returns Void.
|
|
19
21
|
*/
|
|
20
|
-
export function handleArrowKey(e, history, refs) {
|
|
22
|
+
export function handleArrowKey(e, history, refs, setValue) {
|
|
21
23
|
const { draftRef, historyIndexRef } = refs;
|
|
22
|
-
const inputEl = e.currentTarget;
|
|
23
24
|
if (e.key === KEY.ARROW_DOWN && historyIndexRef.current === -1) {
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
26
27
|
if (historyIndexRef.current === -1) {
|
|
27
|
-
draftRef.current =
|
|
28
|
+
draftRef.current = e.currentTarget.value;
|
|
28
29
|
}
|
|
29
30
|
const currentIndex = historyIndexRef.current;
|
|
30
31
|
const newIndex = e.key === KEY.ARROW_UP
|
|
31
32
|
? Math.min(currentIndex + 1, history.length - 1)
|
|
32
33
|
: Math.max(currentIndex - 1, -1);
|
|
33
34
|
if (newIndex === -1) {
|
|
34
|
-
|
|
35
|
+
setValue(draftRef.current);
|
|
35
36
|
historyIndexRef.current = -1;
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
+
setValue(history[newIndex] || "");
|
|
39
40
|
historyIndexRef.current = newIndex;
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* Handles the Enter key press to submit the form, or allows newline on Shift+Enter.
|
|
43
44
|
* @param e - The keyboard event.
|
|
45
|
+
* @returns Void.
|
|
44
46
|
*/
|
|
45
47
|
export function handleEnterKey(e) {
|
|
46
48
|
if (e.shiftKey)
|
|
@@ -51,24 +53,26 @@ export function handleEnterKey(e) {
|
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
53
55
|
* Handles the Escape key press to clear the input and reset history navigation.
|
|
54
|
-
* @param e - The keyboard event.
|
|
55
56
|
* @param refs - Refs for draft text and history index.
|
|
57
|
+
* @param setValue - Setter for the controlled input value.
|
|
58
|
+
* @returns Void.
|
|
56
59
|
*/
|
|
57
|
-
export function handleEscapeKey(
|
|
60
|
+
export function handleEscapeKey(refs, setValue) {
|
|
58
61
|
const { draftRef, historyIndexRef } = refs;
|
|
59
|
-
|
|
60
|
-
inputEl.value = "";
|
|
62
|
+
setValue("");
|
|
61
63
|
draftRef.current = "";
|
|
62
64
|
historyIndexRef.current = -1;
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* Handles the Tab key press to auto-fill the input with the placeholder.
|
|
66
68
|
* @param e - The keyboard event.
|
|
69
|
+
* @param setValue - Setter for the controlled input value.
|
|
70
|
+
* @returns Void.
|
|
67
71
|
*/
|
|
68
|
-
export function handleTabKey(e) {
|
|
72
|
+
export function handleTabKey(e, setValue) {
|
|
69
73
|
const inputEl = e.currentTarget;
|
|
70
74
|
if (inputEl.value)
|
|
71
75
|
return;
|
|
72
76
|
e.preventDefault();
|
|
73
|
-
inputEl.
|
|
77
|
+
setValue(inputEl.placeholder);
|
|
74
78
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { IconButton, InputBase, Stack } from "@mui/material";
|
|
3
|
-
import { INPUT_BASE_PROPS } from "./constants";
|
|
4
|
-
import { StyledBox, StyledPaper } from "./input.styles";
|
|
5
3
|
import { UpArrowIcon } from "../../../../../components/common/CustomIcon/components/UpArrowIcon/upArrowIcon";
|
|
6
4
|
import { ICON_BUTTON_PROPS } from "../../../../../styles/common/mui/iconButton";
|
|
7
|
-
import { SVG_ICON_PROPS } from "../../../../../styles/common/mui/svgIcon";
|
|
8
5
|
import { STACK_PROPS } from "../../../../../styles/common/mui/stack";
|
|
6
|
+
import { SVG_ICON_PROPS } from "../../../../../styles/common/mui/svgIcon";
|
|
7
|
+
import { INPUT_BASE_PROPS } from "./constants";
|
|
8
|
+
import { useControlledInput } from "./hooks/UseControlledInput/hook";
|
|
9
9
|
import { useKeyShortCuts } from "./hooks/UseKeyShortCuts/hook";
|
|
10
|
+
import { StyledBox, StyledPaper } from "./input.styles";
|
|
10
11
|
/**
|
|
11
12
|
* Renders an input component for the research panel.
|
|
12
13
|
* @param props - Component props.
|
|
@@ -14,6 +15,7 @@ import { useKeyShortCuts } from "./hooks/UseKeyShortCuts/hook";
|
|
|
14
15
|
* @returns Research panel input component.
|
|
15
16
|
*/
|
|
16
17
|
export const Input = ({ disabled, ...props }) => {
|
|
17
|
-
const {
|
|
18
|
-
|
|
18
|
+
const { inputRef, onChange, setValue, value } = useControlledInput();
|
|
19
|
+
const { onKeyDown } = useKeyShortCuts(setValue);
|
|
20
|
+
return (_jsx(StyledBox, { children: _jsxs(StyledPaper, { elevation: 0, children: [_jsx(InputBase, { ...INPUT_BASE_PROPS, inputRef: inputRef, onChange: onChange, onKeyDown: onKeyDown, value: value, ...props }), _jsx(Stack, { direction: STACK_PROPS.DIRECTION.ROW, gap: 2, children: _jsx(IconButton, { color: ICON_BUTTON_PROPS.COLOR.SECONDARY, disabled: disabled, size: ICON_BUTTON_PROPS.SIZE.XSMALL, type: "submit", children: _jsx(UpArrowIcon, { fontSize: SVG_ICON_PROPS.FONT_SIZE.SMALL }) }) })] }) }));
|
|
19
21
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { IconButtonProps, InputBaseProps } from "@mui/material";
|
|
2
|
-
export type InputProps = InputBaseProps & Pick<IconButtonProps, "disabled">;
|
|
2
|
+
export type InputProps = Omit<InputBaseProps, "onChange" | "value"> & Pick<IconButtonProps, "disabled">;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@databiosphere/findable-ui",
|
|
3
|
-
"version": "50.
|
|
3
|
+
"version": "50.3.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@commitlint/config-conventional": "^20.2.0",
|
|
27
27
|
"@emotion/jest": "^11.14.2",
|
|
28
28
|
"@next/eslint-plugin-next": "^15.5.13",
|
|
29
|
-
"@storybook/nextjs-vite": "^10.
|
|
29
|
+
"@storybook/nextjs-vite": "^10.3.3",
|
|
30
30
|
"@testing-library/react": "^16.3.1",
|
|
31
31
|
"@types/d3": "^7.4.3",
|
|
32
32
|
"@types/jest": "^29.5.14",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
45
45
|
"eslint-plugin-sonarjs": "^0.25.1",
|
|
46
46
|
"eslint-plugin-sort-destructure-keys": "^2.0.0",
|
|
47
|
-
"eslint-plugin-storybook": "^10.
|
|
47
|
+
"eslint-plugin-storybook": "^10.3.3",
|
|
48
48
|
"eslint-plugin-typescript-sort-keys": "^3.3.0",
|
|
49
49
|
"husky": "^8.0.3",
|
|
50
50
|
"jest": "^29.7.0",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"jest-fetch-mock": "^3.0.3",
|
|
53
53
|
"prettier": "^3.7.4",
|
|
54
54
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
55
|
-
"storybook": "^10.
|
|
55
|
+
"storybook": "^10.3.3",
|
|
56
56
|
"ts-jest": "^29.4.6",
|
|
57
57
|
"typescript": "^5.9.3"
|
|
58
58
|
},
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { JSX } from "react";
|
|
2
|
+
import { ANCHOR_TARGET } from "../../../../../Links/common/entities";
|
|
3
|
+
import { Logo } from "../../../Header/components/Content/components/Logo/logo";
|
|
4
|
+
import { PoweredByCleverCanaryProps } from "./types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A component that displays a "Powered by CleverCanary" link with an image.
|
|
8
|
+
*
|
|
9
|
+
* @param props - Component props.
|
|
10
|
+
* @param props.className - An optional class name for styling.
|
|
11
|
+
* @param props.alt - The alt text for the image.
|
|
12
|
+
* @param props.src - The source URL for the image.
|
|
13
|
+
* @returns The rendered component or null if not powered by CleverCanary.
|
|
14
|
+
*/
|
|
15
|
+
export const PoweredByCleverCanary = ({
|
|
16
|
+
alt = "Powered by CleverCanary",
|
|
17
|
+
className,
|
|
18
|
+
...props /* StaticImageProps */
|
|
19
|
+
}: PoweredByCleverCanaryProps): JSX.Element | null => {
|
|
20
|
+
if (!props.src) return null;
|
|
21
|
+
return (
|
|
22
|
+
<Logo
|
|
23
|
+
alt={alt}
|
|
24
|
+
className={className}
|
|
25
|
+
height={32}
|
|
26
|
+
link="https://www.clevercanary.com"
|
|
27
|
+
src={props.src}
|
|
28
|
+
target={ANCHOR_TARGET.BLANK}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { StaticImageProps } from "../../../../../common/StaticImage/staticImage";
|
|
2
|
+
import { BaseComponentProps } from "../../../../../types";
|
|
3
|
+
|
|
4
|
+
export type PoweredByCleverCanaryProps = BaseComponentProps &
|
|
5
|
+
Omit<StaticImageProps, "alt" | "src"> & {
|
|
6
|
+
alt?: string;
|
|
7
|
+
src?: StaticImageProps["src"];
|
|
8
|
+
};
|
|
@@ -3,9 +3,11 @@ import { JSX, ReactNode } from "react";
|
|
|
3
3
|
import { SELECTOR } from "../../../../common/selectors";
|
|
4
4
|
import { useLayoutDimensions } from "../../../../providers/layoutDimensions/hook";
|
|
5
5
|
import { Social } from "../../../common/Socials/socials";
|
|
6
|
+
import { StaticImageProps } from "../../../common/StaticImage/staticImage";
|
|
6
7
|
import { ANCHOR_TARGET } from "../../../Links/common/entities";
|
|
7
8
|
import { isNodeBoolean } from "../../../utils";
|
|
8
9
|
import { NavLinkItem } from "../Header/components/Content/components/Navigation/navigation";
|
|
10
|
+
import { PoweredByCleverCanary } from "./components/PoweredByCleverCanary/poweredByCleverCanary";
|
|
9
11
|
import { VersionInfo } from "./components/VersionInfo/versionInfo";
|
|
10
12
|
import { AppBar, Link, Links, Socials } from "./footer.styles";
|
|
11
13
|
|
|
@@ -13,6 +15,7 @@ export interface FooterProps {
|
|
|
13
15
|
Branding?: ReactNode;
|
|
14
16
|
className?: string;
|
|
15
17
|
navLinks?: NavLinkItem[];
|
|
18
|
+
poweredByCC?: Omit<StaticImageProps, "alt"> & { alt?: string };
|
|
16
19
|
socials?: Social[];
|
|
17
20
|
versionInfo?: ReactNode;
|
|
18
21
|
}
|
|
@@ -21,6 +24,7 @@ export const Footer = ({
|
|
|
21
24
|
Branding,
|
|
22
25
|
className,
|
|
23
26
|
navLinks,
|
|
27
|
+
poweredByCC,
|
|
24
28
|
socials,
|
|
25
29
|
versionInfo,
|
|
26
30
|
}: FooterProps): JSX.Element => {
|
|
@@ -36,7 +40,7 @@ export const Footer = ({
|
|
|
36
40
|
>
|
|
37
41
|
<Toolbar variant="dense">
|
|
38
42
|
{Branding}
|
|
39
|
-
{(navLinks || socials || versionInfo) && (
|
|
43
|
+
{(navLinks || socials || versionInfo || poweredByCC) && (
|
|
40
44
|
<Links>
|
|
41
45
|
{navLinks &&
|
|
42
46
|
navLinks.map(({ label, target = ANCHOR_TARGET.SELF, url }, i) => (
|
|
@@ -49,6 +53,7 @@ export const Footer = ({
|
|
|
49
53
|
))}
|
|
50
54
|
{socials && <Socials buttonSize="small" socials={socials} />}
|
|
51
55
|
{isNodeBoolean(versionInfo) ? <VersionInfo /> : versionInfo}
|
|
56
|
+
<PoweredByCleverCanary {...poweredByCC} />
|
|
52
57
|
</Links>
|
|
53
58
|
)}
|
|
54
59
|
</Toolbar>
|