Unfinished solution
This commit is contained in:
parent
0364c20240
commit
62c1cfd697
package-lock.jsonpackage.json
src
2375
package-lock.json
generated
2375
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,13 +19,13 @@
|
|||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-custom-scrollbars": "^4.2.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hotkeys": "^2.0.0",
|
"react-hotkeys": "^2.0.0",
|
||||||
"react-i18next": "^11.18.3",
|
"react-i18next": "^11.18.3",
|
||||||
"react-redux": "^8.0.2",
|
"react-redux": "^8.0.2",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"react-scrollbars-custom": "^4.1.0",
|
|
||||||
"storybook-addon-pseudo-states": "^1.15.1",
|
"storybook-addon-pseudo-states": "^1.15.1",
|
||||||
"tailwindcss": "^3.1.7",
|
"tailwindcss": "^3.1.7",
|
||||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||||
@ -96,6 +96,7 @@
|
|||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
|
"@types/react-custom-scrollbars": "^4.0.10",
|
||||||
"autoprefixer": "^10.4.8",
|
"autoprefixer": "^10.4.8",
|
||||||
"babel-plugin-named-exports-order": "^0.0.2",
|
"babel-plugin-named-exports-order": "^0.0.2",
|
||||||
"jest": "^28.1.3",
|
"jest": "^28.1.3",
|
||||||
|
49
src/App.tsx
49
src/App.tsx
@ -1,7 +1,28 @@
|
|||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Libraries */
|
/* Libraries */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
import React from "react";
|
import Searchbar from "components/Searchbar/Searchbar";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Scrollbars } from "react-custom-scrollbars";
|
||||||
|
|
||||||
|
const hintsValues = [
|
||||||
|
{ id: "1", caption: "Wade Cooper" },
|
||||||
|
{ id: "2", caption: "Arlene Mccoy" },
|
||||||
|
{ id: "3", caption: "Devon Webb" },
|
||||||
|
{ id: "4", caption: "Tom Cook" },
|
||||||
|
{ id: "5", caption: "Tanya Fox" },
|
||||||
|
{ id: "6", caption: "Hellen Schmidt" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const hintsValues2 = [
|
||||||
|
{ id: "1", caption: "Wade Cooper2" },
|
||||||
|
{ id: "2", caption: "Arlene Mccoy2" },
|
||||||
|
{ id: "3", caption: "Devon Webb2" },
|
||||||
|
{ id: "4", caption: "Tom Cook2" },
|
||||||
|
{ id: "5", caption: "Tanya Fox2" },
|
||||||
|
{ id: "6", caption: "Hellen Schmidt2" },
|
||||||
|
];
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Application root component */
|
/* Application root component */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
@ -10,9 +31,29 @@ import React from "react";
|
|||||||
* @return {JSX.Element}
|
* @return {JSX.Element}
|
||||||
*/
|
*/
|
||||||
function App() {
|
function App() {
|
||||||
return <Scrollbar style={{ width: 250, height: 250 }}>
|
return <Page />;
|
||||||
<p>Hello world!</p>
|
}
|
||||||
</Scrollbar>;
|
|
||||||
|
function Page() {
|
||||||
|
const [hints, setHints] = useState<any[]>([]);
|
||||||
|
function delayedMock(hints: any[]): Promise<any> {
|
||||||
|
return new Promise((resolve) => setTimeout(() => resolve(hints), 2000));
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = async (query: string) => {
|
||||||
|
console.log(query);
|
||||||
|
if (query === "ok") {
|
||||||
|
await delayedMock(hintsValues2).then((v) => setHints(v));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("touched");
|
||||||
|
await delayedMock([]).then((v) => setHints(v));
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Searchbar hints={hints} onChange={onChange} onSelected={console.log} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import React, { useEffect, useLayoutEffect } from "react";
|
import React, { useEffect, useLayoutEffect } from "react";
|
||||||
import Scrollbar from "react-scrollbars-custom";
|
import { Scrollbars } from "react-custom-scrollbars";
|
||||||
import { WithRouteProps } from "routes";
|
import { WithRouteProps } from "routes";
|
||||||
import { withRouteParams } from "routes/withRoute";
|
import { withRouteParams } from "routes/withRoute";
|
||||||
import { useUIViewModel } from "ui/controller/uiViewModel";
|
import { useUIViewModel } from "ui/controller/uiViewModel";
|
||||||
@ -34,19 +34,19 @@ const Page = ({ title, withOutlet, children, activePath }: Props) => {
|
|||||||
isDrawerCollapsed ? "w-24" : "1.5xl:w-80 2xl:w-96"
|
isDrawerCollapsed ? "w-24" : "1.5xl:w-80 2xl:w-96"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Scrollbar>
|
<Scrollbars>
|
||||||
<div className="px-6 py-1">
|
<div className="px-6 py-1">
|
||||||
<SideNav collapsed={isDrawerCollapsed} />
|
<SideNav collapsed={isDrawerCollapsed} />
|
||||||
</div>
|
</div>
|
||||||
</Scrollbar>
|
</Scrollbars>
|
||||||
</aside>
|
</aside>
|
||||||
<main className="relative rounded-lg m-2 pl-24 bg-main shadow-lg shadow-serv-900/20">
|
<main className="relative rounded-lg m-2 pl-24 bg-main shadow-lg shadow-serv-900/20">
|
||||||
<div className="absolute -left-5 top-6">
|
<div className="absolute -left-5 top-6">
|
||||||
<DrawerController />
|
<DrawerController />
|
||||||
</div>
|
</div>
|
||||||
<Scrollbar>
|
<Scrollbars>
|
||||||
<div className="px-8 pt-20">{children}</div>
|
<div className="px-8 pt-20">{children}</div>
|
||||||
</Scrollbar>
|
</Scrollbars>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
81
src/components/Searchbar/Searchbar.tsx
Normal file
81
src/components/Searchbar/Searchbar.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { Fragment, useEffect, useState } from "react";
|
||||||
|
import { Combobox, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
|
type Hint = {
|
||||||
|
id: string;
|
||||||
|
caption: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onChange: (query: string) => void;
|
||||||
|
onSelected?: (value: Hint) => void;
|
||||||
|
hints: Hint[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Searchbar({ onChange, onSelected, hints }: Props) {
|
||||||
|
const [selected, setSelected] = useState(hints[0] ?? null);
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onChange(query);
|
||||||
|
}, [query, onChange]);
|
||||||
|
|
||||||
|
const handleSelected = (value: Hint) => {
|
||||||
|
setSelected(value);
|
||||||
|
onSelected && onSelected(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-16 w-72">
|
||||||
|
<Combobox value={selected} onChange={handleSelected}>
|
||||||
|
<div className="relative mt-1">
|
||||||
|
<div className="relative w-full cursor-default overflow-hidden rounded-lg bg-white text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-teal-300 sm:text-sm">
|
||||||
|
<Combobox.Input
|
||||||
|
className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0"
|
||||||
|
displayValue={(hint: Hint | undefined) => hint?.caption ?? ""}
|
||||||
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
leave="transition ease-in duration-100"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<Combobox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||||
|
{hints.length === 0 && query !== "" ? (
|
||||||
|
<div className="relative cursor-default select-none py-2 px-4 text-gray-700">
|
||||||
|
Nothing found.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
hints.map((hint) => (
|
||||||
|
<Combobox.Option
|
||||||
|
key={hint.id}
|
||||||
|
className={({ active }) =>
|
||||||
|
`relative cursor-default select-none py-2 pl-10 pr-4 ${
|
||||||
|
active ? "bg-teal-600 text-white" : "text-gray-900"
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
value={hint}
|
||||||
|
>
|
||||||
|
{({ selected, active }) => (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className={`block truncate ${
|
||||||
|
selected ? "font-medium" : "font-normal"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{hint.caption}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Combobox.Option>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Combobox.Options>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</Combobox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -7,7 +7,7 @@ import { Listbox, Transition } from "@headlessui/react";
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import "../index.css";
|
import "../index.css";
|
||||||
import { ReactComponent as SelectIcon } from "../assets/svg/select-arrow.svg";
|
import { ReactComponent as SelectIcon } from "../assets/svg/select-arrow.svg";
|
||||||
import { Scrollbar } from "react-scrollbars-custom";
|
import { Scrollbars } from "react-custom-scrollbars";
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Component props */
|
/* Component props */
|
||||||
@ -114,7 +114,7 @@ function Select<T>({
|
|||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Listbox.Options className={`${SelectOptionsStyle}`}>
|
<Listbox.Options className={`${SelectOptionsStyle}`}>
|
||||||
<Scrollbar
|
<Scrollbars
|
||||||
style={{
|
style={{
|
||||||
height: options.length * elementScrollSize,
|
height: options.length * elementScrollSize,
|
||||||
maxHeight: maxScrollSize,
|
maxHeight: maxScrollSize,
|
||||||
@ -139,7 +139,7 @@ function Select<T>({
|
|||||||
}`}
|
}`}
|
||||||
</Listbox.Option>
|
</Listbox.Option>
|
||||||
))}
|
))}
|
||||||
</Scrollbar>
|
</Scrollbars>
|
||||||
</Listbox.Options>
|
</Listbox.Options>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
|
114
src/components/drop-down/DropDownMenu.tsx
Normal file
114
src/components/drop-down/DropDownMenu.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Imports */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
import React, { Fragment } from "react";
|
||||||
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
|
import { PropsPartion } from "./DropDownMenuItem";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { ReactComponent as SelectIcon } from "assets/svg/select-arrow.svg";
|
||||||
|
type ChildType = React.ReactElement<any & PropsPartion[]>;
|
||||||
|
type ChildrenType = ChildType[] | ChildType;
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Component props */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
type MenuProps = {
|
||||||
|
emphasis?: "high" | "low";
|
||||||
|
disabled?: boolean;
|
||||||
|
className?: string | undefined;
|
||||||
|
button: React.ReactNode;
|
||||||
|
children: ChildrenType;
|
||||||
|
};
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Styles */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
const MenuButtonStyle = `
|
||||||
|
inline-flex
|
||||||
|
justify-center w-full
|
||||||
|
cursor-default
|
||||||
|
rounded
|
||||||
|
border border-gray-100
|
||||||
|
outline-8
|
||||||
|
bg-white
|
||||||
|
py-2
|
||||||
|
pl-4
|
||||||
|
pr-1
|
||||||
|
text-base`;
|
||||||
|
|
||||||
|
const MenuItemStyle = `
|
||||||
|
absolute
|
||||||
|
left-0
|
||||||
|
mt-2 w-60
|
||||||
|
origin-top-left
|
||||||
|
rounded
|
||||||
|
bg-white
|
||||||
|
shadow-lg
|
||||||
|
focus:outline-none
|
||||||
|
py-2 px-4 sm:text-sm`;
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Component implementation */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/**
|
||||||
|
* Component usage:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* <ContextMenu button={<ButtonComponent/>} emphasis="low|hight">
|
||||||
|
* <ContextMenuAction caption="Menu item action" icon={<IconComponent/>} action={()=>{}} />
|
||||||
|
* </ContextMenu>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export default function DropDownMenu({
|
||||||
|
button,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
emphasis = "low",
|
||||||
|
}: MenuProps) {
|
||||||
|
return (
|
||||||
|
<Menu as="div" className="relative inline-block text-right">
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Menu.Button
|
||||||
|
className={classNames([
|
||||||
|
`${MenuButtonStyle}`,
|
||||||
|
{ "bg-gray-100 font-bold uppercase": emphasis === "high" },
|
||||||
|
className,
|
||||||
|
])}
|
||||||
|
>
|
||||||
|
{button}
|
||||||
|
<SelectIcon
|
||||||
|
className={`${
|
||||||
|
open ? "rotate-180 transform transition-transform duration-100" : "font-normal"
|
||||||
|
} my-2 mx-3 h-2 w-3 flex-center`}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</Menu.Button>
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
show={open}
|
||||||
|
enter="transition ease-out duration-100"
|
||||||
|
enterFrom="transform opacity-0 scale-95"
|
||||||
|
enterTo="transform opacity-100 scale-100"
|
||||||
|
leave="transition ease-in duration-75"
|
||||||
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
|
leaveTo="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Menu.Items
|
||||||
|
static
|
||||||
|
className={classNames([
|
||||||
|
className,
|
||||||
|
`${MenuItemStyle}`,
|
||||||
|
{ "ml-2": emphasis === "high" },
|
||||||
|
])}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
36
src/components/drop-down/DropDownMenuAction.tsx
Normal file
36
src/components/drop-down/DropDownMenuAction.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import classNames from "classnames";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
action: Function;
|
||||||
|
caption: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
className?: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DropDownMenuAction({
|
||||||
|
action,
|
||||||
|
caption,
|
||||||
|
disabled,
|
||||||
|
icon,
|
||||||
|
className,
|
||||||
|
}: Props) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={(e) => action(e)}
|
||||||
|
disabled={disabled}
|
||||||
|
className={classNames([
|
||||||
|
"group flex items-center text-base",
|
||||||
|
{ "opacity-50": disabled, "cursor-default": !disabled },
|
||||||
|
className,
|
||||||
|
])}
|
||||||
|
>
|
||||||
|
{icon && <div className="mr-2 h-5 w-5">{icon}</div>}
|
||||||
|
<span className="px-2 py-2">{caption}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
27
src/components/drop-down/DropDownMenuItem.tsx
Normal file
27
src/components/drop-down/DropDownMenuItem.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import {Menu} from '@headlessui/react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export type PropsPartion = {
|
||||||
|
disabled?: boolean | undefined;
|
||||||
|
active?: boolean | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactElement<
|
||||||
|
any & PropsPartion
|
||||||
|
>;
|
||||||
|
} & PropsPartion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context menu item component
|
||||||
|
* @return {JSX.Element}
|
||||||
|
*/
|
||||||
|
export default function DropDownMenuItem({children, ...props}: Props) {
|
||||||
|
return (
|
||||||
|
<Menu.Item {...props}>
|
||||||
|
{(params) => {
|
||||||
|
return React.cloneElement(children, params);
|
||||||
|
}}
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
|
}
|
@ -1,18 +1,5 @@
|
|||||||
import React, {
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
Fragment,
|
|
||||||
useState,
|
|
||||||
useRef,
|
|
||||||
useEffect,
|
|
||||||
FC,
|
|
||||||
isValidElement,
|
|
||||||
} from "react";
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
import { BottomSheetModal } from "components/containers/modal/BottomSheetModal";
|
|
||||||
import { BottomBarAcceptCookies } from "components/containers/modal/BottomBarAcceptCookies";
|
|
||||||
import { Button } from "components/Button/Button";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import "./ColumnLayout.css";
|
import "./ColumnLayout.css";
|
||||||
import { Children } from "react";
|
|
||||||
import MainColumn from "./Maincolumn";
|
import MainColumn from "./Maincolumn";
|
||||||
import LeftColumn from "./LeftColumn";
|
import LeftColumn from "./LeftColumn";
|
||||||
import RightColumn from "./RightColumn";
|
import RightColumn from "./RightColumn";
|
||||||
@ -101,8 +88,8 @@ export const ColumnLayout: React.FC<ColumnLayoutProps> & ColumnExtentions = ({
|
|||||||
React.Children.only(child).type === LeftColumn
|
React.Children.only(child).type === LeftColumn
|
||||||
) {
|
) {
|
||||||
return React.cloneElement(child, {
|
return React.cloneElement(child, {
|
||||||
openLeftBar: openLeftBar,
|
// openLeftBar: openLeftBar,
|
||||||
widthElement: widthElement,
|
// widthElement: widthElement,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return child;
|
return child;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user