mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Merge branch 'master' into feature/add-changelog-modal/178
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
import { DetailedHTMLProps, Fragment } from "react";
|
||||
import { BsmIconType } from "../svgs/bsm-icon.component";
|
||||
|
||||
type Props<T> = {
|
||||
className?: string;
|
||||
tabIndex: number;
|
||||
tabs: BsContentNavBarTab<T>[];
|
||||
renderTab: (props: DetailedHTMLProps<React.HTMLAttributes<any>, any>, tab?: BsContentNavBarTab<T>, activeTab?: BsContentNavBarTab<T>) => JSX.Element;
|
||||
onTabChange?: (index: number, tab?: BsContentNavBarTab<T>) => void;
|
||||
};
|
||||
|
||||
export type BsContentNavBarTab<T = unknown> = {
|
||||
text: string;
|
||||
icon?: BsmIconType;
|
||||
extra?: T;
|
||||
};
|
||||
|
||||
export function BsContentNavBar<T>({ className, tabIndex, tabs, renderTab, onTabChange }: Props<T>) {
|
||||
const handleTabClick = (index: number) => {
|
||||
onTabChange?.(index, tabs[index]);
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className={`h-full grid grid-flow-row ${className ?? ""}`}>
|
||||
{tabs.map((tab, i) => (
|
||||
<Fragment key={JSON.stringify(tab)}>{renderTab({ onClick: () => handleTabClick(i) }, tab, tabs[tabIndex])}</Fragment>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { LinkBtnProps, LinkButton } from "renderer/components/maps-mangement-components/link-button.component";
|
||||
import { SvgIcon } from "renderer/components/svgs/svg-icon.type";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
export type BsContentTabItemProps<T = unknown> = {
|
||||
text: string;
|
||||
icon: SvgIcon;
|
||||
active?: boolean;
|
||||
value?: T;
|
||||
linkProps?: LinkBtnProps;
|
||||
onClick: (value?: T) => void;
|
||||
};
|
||||
|
||||
type BsContentTabItemComponent<T = unknown> = ({text, icon, active, value, linkProps, onClick}: BsContentTabItemProps<T>) => JSX.Element;
|
||||
|
||||
export const BsContentTabItem: BsContentTabItemComponent = ({ text, icon: Icon, active, value, linkProps, onClick }) => {
|
||||
|
||||
const t = useTranslation();
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLLIElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClick(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={`relative w-full cursor-pointer flex-1 text-center text-lg font-bold flex justify-center items-center content-center px-7 bg-light-main-color-2 dark:bg-main-color-2 hover:bg-light-main-color-1 dark:hover:bg-main-color-1 ${active && "!bg-light-main-color-1 dark:!bg-main-color-1"}`} onClick={handleClick}>
|
||||
<div className="flex flex-col gap-0.5 justify-start items-center text-main-color-1 dark:text-gray-200">
|
||||
<Icon className="w-7 h-7"/>
|
||||
<span className=" font-thin italic text-xs">{t(text)}</span>
|
||||
</div>
|
||||
{linkProps && (
|
||||
<div className="flex items-center absolute top-1.5 left-1.5">
|
||||
<LinkButton
|
||||
state={linkProps.state}
|
||||
className={linkProps.className ?? "block w-6 h-6 aspect-square blur-0 cursor-pointer hover:brightness-75"}
|
||||
title={linkProps.title}
|
||||
onClick={linkProps.onClick}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { LaserSlider } from "../laser-slider.component";
|
||||
import { BsContentTabItem, BsContentTabItemProps } from "./bs-content-tab-item.component";
|
||||
|
||||
type Props<T = unknown> = Readonly<{
|
||||
className?: string;
|
||||
tabs: BsContentTabItemProps<T>[];
|
||||
tabIndex: number;
|
||||
onTabChange: (index: number, tab: BsContentTabItemProps<T>) => void;
|
||||
children: JSX.Element;
|
||||
}>;
|
||||
|
||||
export function BsContentTabPanel({className, tabIndex, tabs, onTabChange, children}: Props) {
|
||||
|
||||
const sliderColor = useThemeColor("second-color");
|
||||
|
||||
return (
|
||||
<div className={className ?? "w-full h-full flex flex-row bg-light-main-color-1 dark:bg-main-color-1 rounded-md shadow-black shadow-md overflow-hidden"}>
|
||||
<nav className="h-full grid grid-flow-row !rounded-none shadow-sm flex-shrink-0">
|
||||
{tabs.map((tab, i) => (
|
||||
<BsContentTabItem
|
||||
key={`${tab.text}${tab.icon}`}
|
||||
{...tab}
|
||||
active={tab.active ?? tabIndex === i}
|
||||
onClick={value => {onTabChange(i, tab); tab.onClick(value);}}
|
||||
/>
|
||||
))}
|
||||
</nav>
|
||||
<LaserSlider className="h-full w-1 relative shrink-0" mode="vertical" color={sliderColor} nbSteps={tabs.length} step={tabIndex}/>
|
||||
<div className="flex-grow h-full flex flex-col transition-all duration-300" style={{ translate: `0 ${0 - tabIndex * 100}%` }}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { LinkOpenerService } from "renderer/services/link-opener.service";
|
||||
export function BsmIframeView() {
|
||||
const linkService = useService(LinkOpenerService);
|
||||
|
||||
const iframeUrl = useObservable(linkService.iframeLink$);
|
||||
const iframeUrl = useObservable(() => linkService.iframeLink$);
|
||||
const ref = useRef(null);
|
||||
useClickOutside(ref, () => linkService.closeIframe());
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { CSSProperties } from "react";
|
||||
|
||||
type Props = Readonly<{
|
||||
mode: "horizontal" | "vertical";
|
||||
color: string;
|
||||
className?: string;
|
||||
nbSteps: number;
|
||||
step: number;
|
||||
}>
|
||||
|
||||
export function LaserSlider({mode, color, className, nbSteps, step}: Props) {
|
||||
|
||||
const sliderStyle = ((): CSSProperties => {
|
||||
if(mode === "vertical"){
|
||||
return {
|
||||
transform: `translate(0, ${step * 100}%)`,
|
||||
height: `calc(100% / ${nbSteps})`,
|
||||
width: "100%"
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
transform: `translate(${step * 100}%, 0)`,
|
||||
width: `calc(100% / ${nbSteps})`,
|
||||
height: "100%"
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<div className={className} style={{ color }}>
|
||||
<span className="absolute h-full w-full bg-current brightness-50" />
|
||||
<span className="absolute block bg-current transition-transform duration-300 shadow-center shadow-current" style={sliderStyle} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DetailedHTMLProps, Fragment } from "react";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { LaserSlider } from "./laser-slider.component";
|
||||
|
||||
type Props = {
|
||||
tabIndex: number;
|
||||
@@ -22,10 +23,7 @@ export function TabNavBar(props: Props) {
|
||||
|
||||
return (
|
||||
<nav className={`relative h-8 shrink-0 cursor-pointer rounded-md overflow-hidden shadow-md shadow-black bg-light-main-color-2 dark:bg-main-color-2 ${props.className}`}>
|
||||
<div className="absolute w-full h-1 bottom-0" style={{ color: secondColor }}>
|
||||
<span className="absolute h-full w-full bg-current brightness-50" />
|
||||
<span className="absolute h-full block bg-current transition-transform duration-300 shadow-center shadow-current" style={{ transform: `translate(${currentIndex * 100}%, 0)`, width: `calc(100% / ${props.tabsText.length})` }} />
|
||||
</div>
|
||||
<LaserSlider className="absolute w-full h-1 bottom-0" mode="horizontal" color={secondColor} nbSteps={props.tabsText.length} step={currentIndex} />
|
||||
<ul className="grid" style={{ gridTemplateColumns: `repeat(${props.tabsText.length}, minmax(0, 1fr))` }}>
|
||||
{props.tabsText.map((text, index) =>
|
||||
props.renderTab ? (
|
||||
|
||||
Reference in New Issue
Block a user