Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
62c1cfd697 | ||
![]() |
0364c20240 |
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",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hotkeys": "^2.0.0",
|
||||
"react-i18next": "^11.18.3",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-scrollbars-custom": "^4.1.0",
|
||||
"storybook-addon-pseudo-states": "^1.15.1",
|
||||
"tailwindcss": "^3.1.7",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
@ -96,6 +96,7 @@
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/react-custom-scrollbars": "^4.0.10",
|
||||
"autoprefixer": "^10.4.8",
|
||||
"babel-plugin-named-exports-order": "^0.0.2",
|
||||
"jest": "^28.1.3",
|
||||
|
47
src/App.tsx
47
src/App.tsx
@ -1,7 +1,28 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* 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 */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@ -10,7 +31,29 @@ import React from "react";
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
function App() {
|
||||
return <div>Hello world!</div>;
|
||||
return <Page />;
|
||||
}
|
||||
|
||||
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;
|
||||
|
7
src/assets/svg/background.svg
Normal file
7
src/assets/svg/background.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg width="1187" height="1234" viewBox="0 0 1187 1234" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M408.998 745.256C206.829 467.159 -0.745132 678.488 53.4493 872.858C189.728 1182.79 541.828 1319.16 839.898 1177.41C942.643 1128.57 1029.84 1050.24 1091.25 951.643C934.96 1103.83 651.196 1078.4 408.998 745.208V745.256Z" fill="#F0F0F0"/>
|
||||
<path d="M589.732 553.768C311.677 115.242 0.000483082 348.859 0.000483082 616.83C-0.108684 705.549 18.2856 793.237 53.9153 873.85C16.9159 692.296 217.547 552.363 404.339 812.896C675.684 1191.27 941.763 1124.69 1090.72 952.538C1138.53 876.188 1169.42 789.736 1181.14 699.443V700.63C1117.18 906.92 834.959 940.474 589.732 553.768Z" fill="#F5F5F5"/>
|
||||
<path d="M780.298 357.556C573.819 3.33809 311.98 35.3415 134.904 225.06C47.4877 335.363 -0.21692 473.901 0.000741558 616.829C18.6403 357.604 320.112 177.116 593.041 616.829C835.635 1008.16 1137.87 906.216 1180.86 701.744V699.781C1184.64 671.939 1186.24 643.826 1185.66 615.715V587.685C1080.6 665.864 942.439 635.678 780.252 357.532L780.298 357.556Z" fill="#FAFAFA"/>
|
||||
<path d="M781.72 420.618C970.818 736.727 1133.84 655.859 1186.06 588.242C1183.96 540.989 1176.61 494.146 1164.16 448.648C1076.01 450.611 1055.46 435.275 968.721 297.983C836.17 86.7264 669.183 -55.8716 377.451 42.2463C282.888 80.7758 199.359 143.787 134.788 225.303C335.256 33.4762 581.228 85.5393 781.72 420.618Z" fill="white"/>
|
||||
<path d="M955.787 325.116C1042.25 462.675 1094.88 469.676 1164.15 448.672C1074.33 120.595 745.743 -69.6327 430.245 23.761C412.352 29.0424 394.753 35.204 377.449 42.2459C638.053 -43.2499 822.887 113.86 955.787 325.116Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
3
src/assets/svg/selectIcon.svg
Normal file
3
src/assets/svg/selectIcon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.1315 4.6875H2.86897C2.56116 4.6875 2.38929 5.0125 2.57991 5.23438L7.71116 11.1844C7.85804 11.3547 8.14085 11.3547 8.28929 11.1844L13.4205 5.23438C13.6112 5.0125 13.4393 4.6875 13.1315 4.6875Z" fill="#262626"/>
|
||||
</svg>
|
After Width: | Height: | Size: 326 B |
111
src/components/MainSection.tsx
Normal file
111
src/components/MainSection.tsx
Normal file
@ -0,0 +1,111 @@
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import Inputgroup from "./Inputgroup";
|
||||
import Select from "./Select";
|
||||
import { useState } from "react";
|
||||
import { Button } from "./Button/Button";
|
||||
import { SVGSearch } from "../components/icons";
|
||||
import TextInputBox from "components/TextInputBox";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const options = [
|
||||
{ name: "All" },
|
||||
{ name: "Wade Cooper" },
|
||||
{ name: "Arlene Mccoy" },
|
||||
{ name: "Devon Webb" },
|
||||
{ name: "Tom Cook" },
|
||||
{ name: "Tanya Fox" },
|
||||
{ name: "Hellen Schmidt" },
|
||||
];
|
||||
const Items = [
|
||||
{ name: "test" },
|
||||
{ name: "Wade Cooper" },
|
||||
{ name: "Arlene Mccoy" },
|
||||
{ name: "Devon Webb" },
|
||||
{ name: "Tom Cook" },
|
||||
{ name: "Tanya Fox" },
|
||||
{ name: "Hellen Schmidt" },
|
||||
];
|
||||
export default function MainSection({
|
||||
className,
|
||||
...props
|
||||
}: Props): JSX.Element {
|
||||
const [value, setValue] = useState(options[0]);
|
||||
const [selectedPerson, setSelectedPerson] = useState<any>(Items);
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
let filteredItems =
|
||||
query === ""
|
||||
? Items
|
||||
: Items.filter((item) =>
|
||||
item.name
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "")
|
||||
.includes(query.toLowerCase().replace(/\s+/g, ""))
|
||||
);
|
||||
|
||||
const removeOption = (e: React.SyntheticEvent) => {
|
||||
const newSelect = filteredItems.filter(
|
||||
(element: any) => element.name !== e
|
||||
);
|
||||
setSelectedPerson(newSelect);
|
||||
};
|
||||
return (
|
||||
<div className="bg-main bg-center bg-cover bg-origin-border bg-no-repeat h-full py-32 px-2 sm:px-6 md:px-6 lg:px-0">
|
||||
<div className="m-auto text-center font-semibold text-4xl ">
|
||||
Scientific Library with Free Access
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-center space-x-3 pt-2">
|
||||
<div className=" text-2xl text-gray-400">Search</div>
|
||||
<div className=" text-3xl text-blue-500">320 455</div>
|
||||
<div className=" text-2xl text-gray-400">Items</div>
|
||||
</div>
|
||||
<div className="max-w-xl m-auto pt-16">
|
||||
<Inputgroup className="m-0 p-0">
|
||||
<div className="flex items-center w-full divide-x-2 divide-solid divide-gray-200">
|
||||
<div className="flex w-1/3">
|
||||
<Select
|
||||
inGroup={true}
|
||||
className="w-full top-0"
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
displayValueResolver={(value) => value.name}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<TextInputBox
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
onChange={setSelectedPerson}
|
||||
value={selectedPerson}
|
||||
queryItems={filteredItems}
|
||||
displayValueResolver={(value) => value.name}
|
||||
inGroup={true}
|
||||
removeOption={removeOption}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pr-0.5">
|
||||
<Button
|
||||
onClick={() => {
|
||||
const Query = { option: value.name, item: selectedPerson.name };
|
||||
console.log(Query);
|
||||
}}
|
||||
>
|
||||
<Button.Icon>
|
||||
<SVGSearch className="fill-white stroke-white w-4 "></SVGSearch>
|
||||
</Button.Icon>
|
||||
</Button>
|
||||
</div>
|
||||
</Inputgroup>
|
||||
<div className="mt-7 pr-1 text-right font-semibold text-sm">
|
||||
Advanced Search
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import classNames from "classnames";
|
||||
import React, { useEffect, useLayoutEffect } from "react";
|
||||
import Scrollbar from "react-scrollbars-custom";
|
||||
import { Scrollbars } from "react-custom-scrollbars";
|
||||
import { WithRouteProps } from "routes";
|
||||
import { withRouteParams } from "routes/withRoute";
|
||||
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"
|
||||
)}
|
||||
>
|
||||
<Scrollbar>
|
||||
<Scrollbars>
|
||||
<div className="px-6 py-1">
|
||||
<SideNav collapsed={isDrawerCollapsed} />
|
||||
</div>
|
||||
</Scrollbar>
|
||||
</Scrollbars>
|
||||
</aside>
|
||||
<main className="relative rounded-lg m-2 pl-24 bg-main shadow-lg shadow-serv-900/20">
|
||||
<div className="absolute -left-5 top-6">
|
||||
<DrawerController />
|
||||
</div>
|
||||
<Scrollbar>
|
||||
<Scrollbars>
|
||||
<div className="px-8 pt-20">{children}</div>
|
||||
</Scrollbar>
|
||||
</Scrollbars>
|
||||
</main>
|
||||
</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 "../index.css";
|
||||
import { ReactComponent as SelectIcon } from "../assets/svg/select-arrow.svg";
|
||||
import { Scrollbar } from "react-scrollbars-custom";
|
||||
import { Scrollbars } from "react-custom-scrollbars";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component props */
|
||||
@ -114,7 +114,7 @@ function Select<T>({
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className={`${SelectOptionsStyle}`}>
|
||||
<Scrollbar
|
||||
<Scrollbars
|
||||
style={{
|
||||
height: options.length * elementScrollSize,
|
||||
maxHeight: maxScrollSize,
|
||||
@ -139,7 +139,7 @@ function Select<T>({
|
||||
}`}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Scrollbar>
|
||||
</Scrollbars>
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
|
155
src/components/TextInputBox.tsx
Normal file
155
src/components/TextInputBox.tsx
Normal file
@ -0,0 +1,155 @@
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import "../index.css";
|
||||
import { Combobox, Transition } from "@headlessui/react";
|
||||
import { Fragment } from "react";
|
||||
|
||||
type Props<T> = {
|
||||
removeOption: Function;
|
||||
inGroup?: boolean;
|
||||
queryItems?: T[];
|
||||
query: string;
|
||||
setQuery: (element: any) => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
value?: T;
|
||||
displayValueResolver?: (element: T) => any;
|
||||
onChange: (element: T) => void;
|
||||
} & Omit<
|
||||
React.ComponentPropsWithRef<"select">,
|
||||
"value" | "onChange" | "queryItems"
|
||||
>;
|
||||
|
||||
const inputStyle = `
|
||||
w-full
|
||||
cursor-default
|
||||
rounded
|
||||
overflow-hidden
|
||||
border
|
||||
border-solid
|
||||
shadow-none
|
||||
border-gray-100
|
||||
focus:outline-none
|
||||
focus:border-gray-200
|
||||
hover:border-gray-200
|
||||
py-2 pl-3
|
||||
text-sm
|
||||
text-gray-900
|
||||
`;
|
||||
|
||||
const inputList = `
|
||||
absolute z-10 mt-1 w-full max-h-56
|
||||
bg-white shadow-lg
|
||||
rounded py-1
|
||||
overflow-hidden
|
||||
focus:outline-none
|
||||
text-base
|
||||
sm:text-sm`;
|
||||
|
||||
const inputInGroup = [
|
||||
`border-none
|
||||
hover:none
|
||||
active:none
|
||||
focus:none
|
||||
`,
|
||||
];
|
||||
|
||||
function TextInputBox<T>({
|
||||
removeOption,
|
||||
inGroup = false, // We should use this flag to choose how we will style our component
|
||||
query,
|
||||
setQuery,
|
||||
className,
|
||||
queryItems = [],
|
||||
value,
|
||||
onChange,
|
||||
displayValueResolver,
|
||||
disabled,
|
||||
...props
|
||||
}: Props<T>): JSX.Element {
|
||||
return (
|
||||
<div className={classNames("", className)}>
|
||||
<Combobox value={value} onChange={onChange}>
|
||||
<div className="relative">
|
||||
<div className="relative w-full bg-white text-left focus:outline-none sm:text-sm">
|
||||
<Combobox.Input
|
||||
className={classNames([
|
||||
[`${inputStyle}`],
|
||||
{ [`${inputInGroup}`]: inGroup },
|
||||
className,
|
||||
])}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder={"Search..."}
|
||||
displayValue={(value: T) =>
|
||||
displayValueResolver ? displayValueResolver(value) : value
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{query.length > 0 && (
|
||||
<div className={`${inputList}`}>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
afterLeave={() => setQuery("")}
|
||||
>
|
||||
<Combobox.Options>
|
||||
{queryItems.length === 0 && query !== "" ? (
|
||||
<p >Nothing found.</p>
|
||||
) : null}
|
||||
{query.length > 0 && (
|
||||
<Combobox.Option value={{ id: null, name: query }} className="cursor-default">
|
||||
query: "{query}"
|
||||
</Combobox.Option>
|
||||
)}
|
||||
{queryItems.map((item: any, id: number) => (
|
||||
<Combobox.Option
|
||||
key={id}
|
||||
className={({ active, selected }) =>
|
||||
classNames(
|
||||
active
|
||||
? "text-gray-900 bg-blue-50"
|
||||
: "font-normal ",
|
||||
"cursor-default select-none relative py-2 pl-3 pr-9",
|
||||
selected
|
||||
? "text-gray-900 bg-blue-100"
|
||||
: "font-normal "
|
||||
)
|
||||
}
|
||||
value={item}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{`${
|
||||
displayValueResolver
|
||||
? displayValueResolver(item)
|
||||
: item
|
||||
}`}
|
||||
<span className=" block relative left-0 right-0 text-gray-200">
|
||||
<button
|
||||
onClick={() =>
|
||||
removeOption(
|
||||
displayValueResolver
|
||||
? displayValueResolver(item)
|
||||
: item
|
||||
)
|
||||
}
|
||||
>
|
||||
x
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Combobox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default TextInputBox;
|
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, {
|
||||
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 React, { useState, useRef, useEffect } from "react";
|
||||
import "./ColumnLayout.css";
|
||||
import { Children } from "react";
|
||||
import MainColumn from "./Maincolumn";
|
||||
import LeftColumn from "./LeftColumn";
|
||||
import RightColumn from "./RightColumn";
|
||||
@ -101,8 +88,8 @@ export const ColumnLayout: React.FC<ColumnLayoutProps> & ColumnExtentions = ({
|
||||
React.Children.only(child).type === LeftColumn
|
||||
) {
|
||||
return React.cloneElement(child, {
|
||||
openLeftBar: openLeftBar,
|
||||
widthElement: widthElement,
|
||||
// openLeftBar: openLeftBar,
|
||||
// widthElement: widthElement,
|
||||
});
|
||||
} else {
|
||||
return child;
|
||||
|
Loading…
x
Reference in New Issue
Block a user