Merge branch 'master' into feature/defaultsearchprovider
This commit is contained in:
commit
0ce5d3aea6
53 changed files with 2934 additions and 1856 deletions
|
@ -1,12 +1,17 @@
|
|||
import React, { useEffect } from "react";
|
||||
import Icon from "./icon";
|
||||
import styled from "styled-components";
|
||||
import selectedTheme from "../lib/theme";
|
||||
|
||||
const AppContainer = styled.div`
|
||||
const AppContainer = styled.a`
|
||||
display: flex;
|
||||
flex: auto 25%;
|
||||
flex: 1 0 auto;
|
||||
padding: 1rem;
|
||||
color: ${selectedTheme.mainColor};
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
`;
|
||||
|
||||
const IconContainer = styled.div`
|
||||
|
@ -21,16 +26,8 @@ const DetailsContainer = styled.div`
|
|||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const AppLink = styled.a`
|
||||
flex: 1 0 auto;
|
||||
color: ${selectedTheme.mainColor};
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
|
||||
&:hover {
|
||||
const AppName = styled.div`
|
||||
a:hover & {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
@ -53,26 +50,29 @@ export interface IAppProps {
|
|||
|
||||
/**
|
||||
* Renders a single app shortcut
|
||||
* @param {IAppProps} props - The props of the given app
|
||||
* @param {IAppProps} props the props of the given app
|
||||
* @returns {React.ReactNode} the child node for the given app
|
||||
*/
|
||||
export const App = ({ name, icon, url, displayURL, newTab }: IAppProps) => {
|
||||
|
||||
useEffect(() => { console.log(newTab) }, [newTab])
|
||||
const App = ({ name, icon, url, displayURL, newTab }: IAppProps) => {
|
||||
const linkAttrs =
|
||||
newTab !== undefined && newTab
|
||||
? {
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
}
|
||||
: {};
|
||||
|
||||
return (
|
||||
<AppContainer>
|
||||
<AppContainer href={url} {...linkAttrs}>
|
||||
<IconContainer>
|
||||
<Icon name={icon} />
|
||||
</IconContainer>
|
||||
<DetailsContainer>
|
||||
|
||||
{
|
||||
(newTab !== undefined && newTab) ?
|
||||
<AppLink href={url} target="_blank" rel="noopener noreferrer">{name}</AppLink> : <AppLink href={url}>{name}</AppLink>
|
||||
}
|
||||
|
||||
<AppName>{name}</AppName>
|
||||
<AppDescription>{displayURL}</AppDescription>
|
||||
</DetailsContainer>
|
||||
</AppContainer>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { App, IAppProps } from "./app";
|
||||
import App, { IAppProps } from "./app";
|
||||
import { ItemList, Item, SubHeadline } from "./elements";
|
||||
|
||||
const CategoryHeadline = styled(SubHeadline)`
|
||||
|
@ -18,9 +17,10 @@ export interface IAppCategoryProps {
|
|||
|
||||
/**
|
||||
* Renders one app category
|
||||
* @param {IAppCategoryProps} props - The props of the given category
|
||||
* @param {IAppCategoryProps} props props of the given category
|
||||
* @returns {React.ReactNode} the app category node
|
||||
*/
|
||||
export const AppCategory = ({ name, items }: IAppCategoryProps) => (
|
||||
const AppCategory = ({ name, items }: IAppCategoryProps) => (
|
||||
<CategoryContainer>
|
||||
{name && <CategoryHeadline>{name}</CategoryHeadline>}
|
||||
<ItemList>
|
||||
|
@ -38,3 +38,5 @@ export const AppCategory = ({ name, items }: IAppCategoryProps) => (
|
|||
</ItemList>
|
||||
</CategoryContainer>
|
||||
);
|
||||
|
||||
export default AppCategory;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AppCategory, IAppCategoryProps } from "./appCategory";
|
||||
import AppCategory, { IAppCategoryProps } from "./appCategory";
|
||||
import { IAppProps } from "./app";
|
||||
|
||||
import { Headline, ListContainer } from "./elements";
|
||||
|
@ -10,7 +10,8 @@ export interface IAppListProps {
|
|||
|
||||
/**
|
||||
* Renders one list containing all app categories and uncategorized apps
|
||||
* @param {IAppListProps} props - The props of the given list of apps
|
||||
* @param {IAppListProps} props props of the given list of apps
|
||||
* @returns {React.ReactNode} the app list component
|
||||
*/
|
||||
const AppList = ({ categories, apps }: IAppListProps) => (
|
||||
<ListContainer>
|
||||
|
@ -20,10 +21,7 @@ const AppList = ({ categories, apps }: IAppListProps) => (
|
|||
<AppCategory key={[name, idx].join("")} name={name} items={items} />
|
||||
))}
|
||||
{apps && (
|
||||
<AppCategory
|
||||
name={categories ? "Uncategorized apps" : ""}
|
||||
items={apps}
|
||||
/>
|
||||
<AppCategory name={categories ? "Uncategorized apps" : ""} items={apps} />
|
||||
)}
|
||||
</ListContainer>
|
||||
);
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import React from "react";
|
||||
import { Headline, ListContainer, ItemList } from "./elements";
|
||||
import { BookmarkGroup, IBookmarkGroupProps } from "./bookmarkGroup";
|
||||
|
||||
interface IBookmarkListProps {
|
||||
groups: Array<IBookmarkGroupProps>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a given list of categorized bookmarks
|
||||
* @param {IBookmarkListProps} props - The props of the given bookmark list
|
||||
*/
|
||||
const BookmarkList = ({ groups }: IBookmarkListProps) => (
|
||||
<ListContainer>
|
||||
<Headline>Bookmarks</Headline>
|
||||
<ItemList>
|
||||
{groups.map(({ name, items }, idx) => (
|
||||
<BookmarkGroup key={[name, idx].join("")} name={name} items={items} />
|
||||
))}
|
||||
</ItemList>
|
||||
</ListContainer>
|
||||
);
|
||||
|
||||
export default BookmarkList;
|
|
@ -1,6 +1,11 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { Item, SubHeadline } from "./elements";
|
||||
import {
|
||||
Headline,
|
||||
Item,
|
||||
ItemList,
|
||||
ListContainer,
|
||||
SubHeadline,
|
||||
} from "./elements";
|
||||
import selectedTheme from "../lib/theme";
|
||||
|
||||
const GroupContainer = styled.div`
|
||||
|
@ -32,9 +37,14 @@ export interface IBookmarkGroupProps {
|
|||
items: Array<IBookmarkProps>;
|
||||
}
|
||||
|
||||
export interface IBookmarkListProps {
|
||||
groups: Array<IBookmarkGroupProps>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a given bookmark group
|
||||
* @param {IBookmarkGroupProps} props - The given props of the bookmark group
|
||||
* @param {IBookmarkGroupProps} props given props of the bookmark group
|
||||
* @returns {React.ReactNode} the bookmark group component
|
||||
*/
|
||||
export const BookmarkGroup = ({ name, items }: IBookmarkGroupProps) => (
|
||||
<Item>
|
||||
|
@ -48,3 +58,21 @@ export const BookmarkGroup = ({ name, items }: IBookmarkGroupProps) => (
|
|||
</GroupContainer>
|
||||
</Item>
|
||||
);
|
||||
|
||||
/**
|
||||
* Renders a given list of categorized bookmarks
|
||||
* @param {IBookmarkListProps} props props of the given bookmark list
|
||||
* @returns {React.ReactNode} the bookmark list component
|
||||
*/
|
||||
const BookmarkList = ({ groups }: IBookmarkListProps) => (
|
||||
<ListContainer>
|
||||
<Headline>Bookmarks</Headline>
|
||||
<ItemList>
|
||||
{groups.map(({ name, items }, idx) => (
|
||||
<BookmarkGroup key={[name, idx].join("")} name={name} items={items} />
|
||||
))}
|
||||
</ItemList>
|
||||
</ListContainer>
|
||||
);
|
||||
|
||||
export default BookmarkList;
|
|
@ -1,7 +1,5 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import selectedTheme from "../lib/theme";
|
||||
import Icon from "./icon";
|
||||
|
||||
export const ListContainer = styled.div`
|
||||
padding: 2rem 0;
|
||||
|
@ -56,29 +54,3 @@ export const Button = styled.button`
|
|||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled.button`
|
||||
float: right;
|
||||
border: none;
|
||||
padding: 0;
|
||||
background: none;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
interface IIconButtonProps {
|
||||
icon: string;
|
||||
onClick: (e: React.FormEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a button with an icon
|
||||
* @param {IIconProps} props - The props of the given IconButton
|
||||
*/
|
||||
export const IconButton = ({ icon, onClick }: IIconButtonProps) => (
|
||||
<StyledButton onClick={onClick}>
|
||||
<Icon name={icon} />
|
||||
</StyledButton>
|
||||
);
|
||||
|
|
|
@ -20,6 +20,10 @@ const DateText = styled.h3`
|
|||
color: ${selectedTheme.accentColor};
|
||||
`;
|
||||
|
||||
export interface IGreeterComponentProps {
|
||||
data: IGreeterProps;
|
||||
}
|
||||
|
||||
export interface IGreeterProps {
|
||||
months: Array<string>;
|
||||
days: Array<string>;
|
||||
|
@ -33,50 +37,48 @@ interface IGreetingProps {
|
|||
end: number;
|
||||
}
|
||||
|
||||
interface IGreeterComponentProps {
|
||||
data: IGreeterProps;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param a the number that's supposed to be checked
|
||||
* @param b the minimum
|
||||
* @param c the maximum
|
||||
* Checks if a number is between two numbers
|
||||
* @param {number} a number that's supposed to be checked
|
||||
* @param {number} b minimum
|
||||
* @param {number} c maximum
|
||||
*/
|
||||
const isBetween = (a: number, b: number, c: number): boolean => (a > b && a < c)
|
||||
export const isBetween = (a: number, b: number, c: number): boolean =>
|
||||
a >= b && a <= c;
|
||||
|
||||
/**
|
||||
* Returns a greeting based on the current time
|
||||
* @returns {string} - A greeting
|
||||
* @param {Array<IGreetingProps>} greetings a list of greetings with start and end date
|
||||
* @returns {string} a greeting
|
||||
*/
|
||||
const getGreeting = (greetings: Array<IGreetingProps>): string => {
|
||||
|
||||
let hours = Math.floor(new Date().getHours())
|
||||
export const getGreeting = (greetings: Array<IGreetingProps>): string => {
|
||||
let hours = Math.floor(new Date().getHours());
|
||||
let result = "";
|
||||
|
||||
greetings.forEach(greeting => {
|
||||
if (isBetween(hours, greeting.start, greeting.end)) result = greeting.greeting;
|
||||
})
|
||||
greetings.forEach((greeting) => {
|
||||
if (isBetween(hours, greeting.start, greeting.end))
|
||||
result = greeting.greeting;
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the appropriate extension for a number (eg. 'rd' for '3' to make '3rd')
|
||||
* @param {number} day - The number of a day within a month
|
||||
* @returns {string} - The extension for that number
|
||||
* @param {number} day number of a day within a month
|
||||
* @returns {string} extension for that number
|
||||
*/
|
||||
const getExtension = (day: number) => {
|
||||
export const getExtension = (day: number) => {
|
||||
let extension = "";
|
||||
|
||||
if ((day > 4 && day <= 20) || (day > 20 && day % 10 >= 4)) {
|
||||
extension = "th";
|
||||
} else if (day % 10 === 1) {
|
||||
if (day % 10 === 1) {
|
||||
extension = "st";
|
||||
} else if (day % 10 === 2) {
|
||||
extension = "nd";
|
||||
} else if (day % 10 === 3) {
|
||||
extension = "rd";
|
||||
} else if (isBetween(day, 4, 20) || (day > 20 && day % 10 >= 4)) {
|
||||
extension = "th";
|
||||
}
|
||||
|
||||
return extension;
|
||||
|
@ -84,10 +86,14 @@ const getExtension = (day: number) => {
|
|||
|
||||
/**
|
||||
* Generates the current date
|
||||
* @param {string} format - The format of the date string
|
||||
* @returns {string} - The current date as a string
|
||||
* @param {string} format format of the date string
|
||||
* @returns {string} current date as a string
|
||||
*/
|
||||
const getDateString = (weekdays: Array<string>, months: Array<string>, format: string) => {
|
||||
export const getDateString = (
|
||||
weekdays: Array<string>,
|
||||
months: Array<string>,
|
||||
format: string,
|
||||
) => {
|
||||
let currentDate = new Date();
|
||||
|
||||
let weekday = weekdays[currentDate.getUTCDay()];
|
||||
|
@ -96,15 +102,24 @@ const getDateString = (weekdays: Array<string>, months: Array<string>, format: s
|
|||
let extension = getExtension(day);
|
||||
let year = currentDate.getFullYear();
|
||||
|
||||
return format.replace("%wd", weekday).replace("%d", day.toString()).replace("%e", extension).replace("%m", month).replace("%y", year.toString());
|
||||
return format
|
||||
.replace("%wd", weekday)
|
||||
.replace("%d", day.toString())
|
||||
.replace("%e", extension)
|
||||
.replace("%m", month)
|
||||
.replace("%y", year.toString());
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the Greeter
|
||||
* @param {IGreeterComponentProps} data required greeter data
|
||||
* @returns {React.ReactNode} the greeter
|
||||
*/
|
||||
const Greeter = ({ data }: IGreeterComponentProps) => (
|
||||
<GreeterContainer>
|
||||
<DateText>{getDateString(data.days, data.months, data.dateformat)}</DateText>
|
||||
<DateText>
|
||||
{getDateString(data.days, data.months, data.dateformat)}
|
||||
</DateText>
|
||||
<GreetText>{getGreeting(data.greetings)}</GreetText>
|
||||
</GreeterContainer>
|
||||
);
|
||||
|
|
|
@ -7,32 +7,62 @@ interface IIconProps {
|
|||
size?: string;
|
||||
}
|
||||
|
||||
interface IIconButtonProps {
|
||||
testid?: string;
|
||||
icon: string;
|
||||
onClick: (e: React.FormEvent) => void;
|
||||
}
|
||||
|
||||
const StyledButton = styled.button`
|
||||
float: right;
|
||||
border: none;
|
||||
padding: 0;
|
||||
background: none;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const IconContainer = styled.i`
|
||||
font-family: "Material Icons";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-feature-settings: "liga";
|
||||
font-size: ${(props) => props.about};
|
||||
color: ${(props) => props.color};
|
||||
`;
|
||||
|
||||
/**
|
||||
* Renders an Icon
|
||||
* @param {IIconProps} props - The props needed for the given icon
|
||||
* @param {IIconProps} props props needed for the given icon
|
||||
* @returns {React.ReactNode} the icon node
|
||||
*/
|
||||
export const Icon = ({ name, size }: IIconProps) => {
|
||||
export const Icon = ({ name, size }: IIconProps) => (
|
||||
<IconContainer color={selectedTheme.mainColor} about={size}>
|
||||
{name}
|
||||
</IconContainer>
|
||||
);
|
||||
|
||||
let IconContainer = styled.i`
|
||||
font-family: "Material Icons";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: ${size ? size : "24px"};
|
||||
color: ${selectedTheme.mainColor};
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-feature-settings: "liga";
|
||||
`;
|
||||
|
||||
return <IconContainer>{name}</IconContainer>;
|
||||
};
|
||||
/**
|
||||
* Renders a button with an icon
|
||||
* @param {IIconProps} props - The props of the given IconButton
|
||||
* @returns {React.ReactNode} the icon button node
|
||||
*/
|
||||
export const IconButton = ({ testid, icon, onClick }: IIconButtonProps) => (
|
||||
<StyledButton data-testid={testid} onClick={onClick}>
|
||||
<Icon name={icon} />
|
||||
</StyledButton>
|
||||
);
|
||||
|
||||
export default Icon;
|
||||
|
|
|
@ -2,12 +2,7 @@ import React from "react";
|
|||
import Modal from "./modal";
|
||||
import styled from "styled-components";
|
||||
import selectedTheme from "../lib/theme";
|
||||
import {
|
||||
ListContainer,
|
||||
ItemList,
|
||||
Headline,
|
||||
SubHeadline,
|
||||
} from "./elements";
|
||||
import { ListContainer, ItemList, Headline, SubHeadline } from "./elements";
|
||||
|
||||
const ModalSubHeadline = styled(SubHeadline)`
|
||||
display: block;
|
||||
|
@ -37,11 +32,6 @@ const ItemContainer = styled.div`
|
|||
padding: 1rem 0;
|
||||
`;
|
||||
|
||||
interface IImprintFieldProps {
|
||||
text: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export interface IImprintProps {
|
||||
name: IImprintFieldProps;
|
||||
address: IImprintFieldProps;
|
||||
|
@ -51,25 +41,32 @@ export interface IImprintProps {
|
|||
text: string;
|
||||
}
|
||||
|
||||
export interface IImprintComponentProps {
|
||||
imprint: IImprintProps;
|
||||
}
|
||||
|
||||
interface IImprintFieldComponentProps {
|
||||
field: IImprintFieldProps;
|
||||
}
|
||||
|
||||
interface IImprintComponentProps {
|
||||
imprint: IImprintProps;
|
||||
interface IImprintFieldProps {
|
||||
text: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an imprint field
|
||||
* @param {IImprintFieldComponentProps} props - The data for the field
|
||||
* @param {IImprintFieldComponentProps} props data for the field
|
||||
* @returns {React.ReactNode} the imprint field component
|
||||
*/
|
||||
const ImprintField = ({ field }: IImprintFieldComponentProps) => (
|
||||
export const ImprintField = ({ field }: IImprintFieldComponentProps) => (
|
||||
<Link href={field.link}>{field.text}</Link>
|
||||
);
|
||||
|
||||
/**
|
||||
* Renders the imprint component
|
||||
* @param {IImprintProps} props - The contents of the imprint
|
||||
* @param {IImprintProps} props contents of the imprint
|
||||
* @returns {React.ReactNode} the imprint node
|
||||
*/
|
||||
const Imprint = ({ imprint }: IImprintComponentProps) => (
|
||||
<>
|
||||
|
@ -85,15 +82,17 @@ const Imprint = ({ imprint }: IImprintComponentProps) => (
|
|||
condition={!window.location.href.endsWith("#imprint")}
|
||||
onClose={() => {
|
||||
if (window.location.href.endsWith("#imprint")) {
|
||||
let location = window.location.href.replace("#imprint", "");
|
||||
window.location.href = location;
|
||||
window.location.href = window.location.href.replace(
|
||||
"#imprint",
|
||||
"",
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<ModalSubHeadline>
|
||||
Information in accordance with section 5 TMG
|
||||
</ModalSubHeadline>
|
||||
</ModalSubHeadline>
|
||||
<>
|
||||
{imprint.name && <ImprintField field={imprint.name} />}
|
||||
{imprint.address && <ImprintField field={imprint.address} />}
|
||||
|
@ -103,9 +102,7 @@ const Imprint = ({ imprint }: IImprintComponentProps) => (
|
|||
</>
|
||||
</div>
|
||||
<div>
|
||||
<ModalSubHeadline>
|
||||
Imprint
|
||||
</ModalSubHeadline>
|
||||
<ModalSubHeadline>Imprint</ModalSubHeadline>
|
||||
{imprint.text && <Text>{imprint.text}</Text>}
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
@ -2,7 +2,8 @@ import React, { useState } from "react";
|
|||
import styled from "styled-components";
|
||||
import selectedTheme from "../lib/theme";
|
||||
|
||||
import { Headline, IconButton } from "./elements";
|
||||
import { Headline } from "./elements";
|
||||
import { IconButton } from "./icon";
|
||||
|
||||
const ModalContainer = styled.div`
|
||||
position: absolute;
|
||||
|
@ -37,7 +38,7 @@ const TitleContainer = styled.div`
|
|||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
interface IModalProps {
|
||||
export interface IModalProps {
|
||||
element: string;
|
||||
icon?: string;
|
||||
text?: string;
|
||||
|
@ -49,9 +50,18 @@ interface IModalProps {
|
|||
|
||||
/**
|
||||
* Renders a modal with button to hide and un-hide
|
||||
* @param {IModalProps} props - The needed props for the modal
|
||||
* @param {IModalProps} props needed props for the modal
|
||||
* @returns {React.ReactNode} the modal component
|
||||
*/
|
||||
const Modal = ({ element, icon, text, condition, title, onClose, children }: IModalProps) => {
|
||||
const Modal = ({
|
||||
element,
|
||||
icon,
|
||||
text,
|
||||
condition,
|
||||
title,
|
||||
onClose,
|
||||
children,
|
||||
}: IModalProps) => {
|
||||
const [modalHidden, setModalHidden] = useState<boolean>(condition ?? true);
|
||||
|
||||
const closeModal = () => {
|
||||
|
@ -62,17 +72,27 @@ const Modal = ({ element, icon, text, condition, title, onClose, children }: IMo
|
|||
return (
|
||||
<>
|
||||
{element === "icon" && (
|
||||
<IconButton icon={icon ?? ""} onClick={() => closeModal()} />
|
||||
<IconButton
|
||||
icon={icon ?? ""}
|
||||
testid="toggle-button"
|
||||
onClick={() => closeModal()}
|
||||
/>
|
||||
)}
|
||||
|
||||
{element === "text" && (
|
||||
<Text onClick={() => closeModal()}>{text}</Text>
|
||||
<Text data-testid="toggle-button" onClick={() => closeModal()}>
|
||||
{text}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<ModalContainer hidden={modalHidden}>
|
||||
<TitleContainer>
|
||||
<Headline>{title}</Headline>
|
||||
<IconButton icon="close" onClick={() => closeModal()} />
|
||||
<IconButton
|
||||
icon="close"
|
||||
testid="close-button"
|
||||
onClick={() => closeModal()}
|
||||
/>
|
||||
</TitleContainer>
|
||||
{children}
|
||||
</ModalContainer>
|
||||
|
|
|
@ -16,18 +16,17 @@ const Search = styled.form`
|
|||
|
||||
const SearchInput = styled.input`
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
|
||||
font-size: 1rem;
|
||||
|
||||
border: none;
|
||||
border-bottom: 1px solid ${selectedTheme.accentColor};
|
||||
border-radius: 0;
|
||||
|
||||
background: none;
|
||||
border-radius: 0;
|
||||
color: ${selectedTheme.mainColor};
|
||||
|
||||
margin: 0px;
|
||||
|
||||
:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
@ -53,9 +52,33 @@ interface ISearchBarProps {
|
|||
search: ISearchProps;
|
||||
}
|
||||
|
||||
export const handleQueryWithProvider = (
|
||||
search: ISearchProps,
|
||||
query: string,
|
||||
) => {
|
||||
let queryArray: Array<string> = query.split(" ");
|
||||
let prefix: string = queryArray[0];
|
||||
|
||||
queryArray.shift();
|
||||
|
||||
let searchQuery: string = queryArray.join(" ");
|
||||
|
||||
let providerFound: boolean = false;
|
||||
if (search.providers) {
|
||||
search.providers.forEach((provider: ISearchProviderProps) => {
|
||||
if (provider.prefix === prefix) {
|
||||
providerFound = true;
|
||||
window.location.href = provider.url + searchQuery;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!providerFound) window.location.href = search.defaultProvider + query;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a search bar
|
||||
* @param {ISearchBarProps} search - The search providers for the search bar to use
|
||||
* @param {ISearchBarProps} search - The search providers for the search bar to use
|
||||
*/
|
||||
const SearchBar = ({ search }: ISearchBarProps) => {
|
||||
let [input, setInput] = useState<string>("");
|
||||
|
@ -67,7 +90,7 @@ const SearchBar = ({ search }: ISearchBarProps) => {
|
|||
var query: string = input || "";
|
||||
|
||||
if (query.split(" ")[0].includes("/")) {
|
||||
handleQueryWithProvider(query);
|
||||
handleQueryWithProvider(search, query);
|
||||
} else {
|
||||
window.location.href = search.defaultProvider + query;
|
||||
}
|
||||
|
@ -75,32 +98,11 @@ const SearchBar = ({ search }: ISearchBarProps) => {
|
|||
e.preventDefault();
|
||||
};
|
||||
|
||||
const handleQueryWithProvider = (query: string) => {
|
||||
let queryArray: Array<string> = query.split(" ");
|
||||
let prefix: string = queryArray[0];
|
||||
|
||||
queryArray.shift();
|
||||
|
||||
let searchQuery: string = queryArray.join(" ");
|
||||
|
||||
let providerFound: boolean = false;
|
||||
if (search.providers) {
|
||||
search.providers.forEach((provider: ISearchProviderProps) => {
|
||||
if (provider.prefix === prefix) {
|
||||
providerFound = true;
|
||||
window.location.href = provider.url + searchQuery;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!providerFound)
|
||||
window.location.href = search.defaultProvider + query;
|
||||
};
|
||||
|
||||
return (
|
||||
<Search onSubmit={(e) => handleSearchQuery(e)}>
|
||||
<SearchInput
|
||||
type="text"
|
||||
data-testid="search-input"
|
||||
value={input}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setInput(e.target.value)
|
||||
|
@ -108,12 +110,17 @@ const SearchBar = ({ search }: ISearchBarProps) => {
|
|||
></SearchInput>
|
||||
<SearchButton
|
||||
type="button"
|
||||
data-testid="search-clear"
|
||||
onClick={() => setInput("")}
|
||||
hidden={buttonsHidden}
|
||||
>
|
||||
Clear
|
||||
</SearchButton>
|
||||
<SearchButton type="submit" hidden={buttonsHidden}>
|
||||
<SearchButton
|
||||
type="submit"
|
||||
data-testid="search-submit"
|
||||
hidden={buttonsHidden}
|
||||
>
|
||||
Search
|
||||
</SearchButton>
|
||||
</Search>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import Select, { ValueType } from "react-select";
|
||||
|
@ -9,36 +9,37 @@ import { Button, SubHeadline } from "./elements";
|
|||
|
||||
import Modal from "./modal";
|
||||
|
||||
const FormContainer = styled.div`
|
||||
export const FormContainer = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
`;
|
||||
|
||||
const Table = styled.table`
|
||||
export const Table = styled.table`
|
||||
font-weight: 400;
|
||||
background: none;
|
||||
width: 100%;
|
||||
color: ${selectedTheme.mainColor};
|
||||
`;
|
||||
|
||||
const TableRow = styled.tr`
|
||||
export const TableRow = styled.tr`
|
||||
border-bottom: 1px solid ${selectedTheme.mainColor};
|
||||
`;
|
||||
|
||||
const TableCell = styled.td`
|
||||
export const TableCell = styled.td`
|
||||
background: none;
|
||||
padding-top: 0.5rem;
|
||||
`;
|
||||
|
||||
const HeadCell = styled.th`
|
||||
export const HeadCell = styled.th`
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
const Section = styled.div`
|
||||
export const Section = styled.div`
|
||||
padding: 1rem 0;
|
||||
`;
|
||||
|
||||
const SectionHeadline = styled(SubHeadline)`
|
||||
export const SectionHeadline = styled(SubHeadline)`
|
||||
width: 100%;
|
||||
border-bottom: 1px solid ${selectedTheme.accentColor};
|
||||
margin-bottom: 0.5rem;
|
||||
|
@ -54,7 +55,7 @@ const Code = styled.p`
|
|||
color: ${selectedTheme.accentColor};
|
||||
`;
|
||||
|
||||
const SelectorStyle: any = {
|
||||
export const SelectorStyle: any = {
|
||||
container: (base: any): any => ({
|
||||
...base,
|
||||
margin: "0 2px",
|
||||
|
@ -72,15 +73,15 @@ const SelectorStyle: any = {
|
|||
boxShadow: "none",
|
||||
"&:hover": {
|
||||
border: "1px solid",
|
||||
borderColor: selectedTheme.mainColor
|
||||
borderColor: selectedTheme.mainColor,
|
||||
},
|
||||
}),
|
||||
dropdownIndicator: (base: any): any => ({
|
||||
...base,
|
||||
color: selectedTheme.mainColor,
|
||||
"&:hover": {
|
||||
color: selectedTheme.mainColor
|
||||
}
|
||||
color: selectedTheme.mainColor,
|
||||
},
|
||||
}),
|
||||
indicatorSeparator: () => ({
|
||||
display: "none",
|
||||
|
@ -91,7 +92,7 @@ const SelectorStyle: any = {
|
|||
border: "1px solid " + selectedTheme.mainColor,
|
||||
borderRadius: 0,
|
||||
boxShadow: "none",
|
||||
margin: "4px 0"
|
||||
margin: "4px 0",
|
||||
}),
|
||||
option: (base: any): any => ({
|
||||
...base,
|
||||
|
@ -125,7 +126,7 @@ interface ISettingsProps {
|
|||
const Settings = ({ themes, search }: ISettingsProps) => {
|
||||
const [newTheme, setNewTheme] = useState<IThemeProps>();
|
||||
|
||||
if (themes && search) {
|
||||
if (themes || search) {
|
||||
return (
|
||||
<Modal element="icon" icon="settings" title="Settings">
|
||||
{themes && (
|
||||
|
@ -133,6 +134,7 @@ const Settings = ({ themes, search }: ISettingsProps) => {
|
|||
<SectionHeadline>Theme:</SectionHeadline>
|
||||
<FormContainer>
|
||||
<Select
|
||||
classNamePrefix="list"
|
||||
options={themes}
|
||||
defaultValue={selectedTheme}
|
||||
onChange={(e: ValueType<IThemeProps, false>) => {
|
||||
|
@ -140,10 +142,20 @@ const Settings = ({ themes, search }: ISettingsProps) => {
|
|||
}}
|
||||
styles={SelectorStyle}
|
||||
/>
|
||||
<Button onClick={() => setTheme(JSON.stringify(newTheme))}>
|
||||
<Button
|
||||
data-testid="button-submit"
|
||||
onClick={() => {
|
||||
if (newTheme) setTheme(newTheme);
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
<Button onClick={() => window.location.reload()}>Refresh</Button>
|
||||
<Button
|
||||
data-testid="button-refresh"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</FormContainer>
|
||||
</Section>
|
||||
)}
|
||||
|
@ -155,24 +167,22 @@ const Settings = ({ themes, search }: ISettingsProps) => {
|
|||
<Code>{search.defaultProvider}</Code>
|
||||
</>
|
||||
<>
|
||||
{
|
||||
search.providers && (
|
||||
<Table>
|
||||
<tbody>
|
||||
<TableRow>
|
||||
<HeadCell>Search Provider</HeadCell>
|
||||
<HeadCell>Prefix</HeadCell>
|
||||
{search.providers && (
|
||||
<Table>
|
||||
<tbody>
|
||||
<TableRow>
|
||||
<HeadCell>Search Provider</HeadCell>
|
||||
<HeadCell>Prefix</HeadCell>
|
||||
</TableRow>
|
||||
{search.providers.map((provider, index) => (
|
||||
<TableRow key={provider.name + index}>
|
||||
<TableCell>{provider.name}</TableCell>
|
||||
<TableCell>{provider.prefix}</TableCell>
|
||||
</TableRow>
|
||||
{search.providers.map((provider, index) => (
|
||||
<TableRow key={provider.name + index}>
|
||||
<TableCell>{provider.name}</TableCell>
|
||||
<TableCell>{provider.prefix}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
)}
|
||||
</>
|
||||
</Section>
|
||||
)}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue