#react #ui
Fairly simple widget cards with tab-able panes made with react typescript and bootstrap.
We start with a generalized widget card. I gave only a few options for fixed sizes, because I want them to be consistent if you are placing a bunch together on a page. Other than that it is basically a nice frame or wrapper around whatever children you give the component.
The style style={{ overflowY: "auto", overflowX: "hidden" }}
is important if you want the widget to be scroll able from within the pane. Pretty cool.
You can also pass true to hideOnMobile
, which will hide the widget on small screens.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| import { FC, ReactNode } from "react";
export const WidgetCard: FC<{
children: ReactNode;
cardTitle: string;
path?: string;
hideOnMobile?: boolean;
size?: string;
}> = ({ children, cardTitle, path = "", hideOnMobile, size = "" }) => {
const normalSize = { height: "600px", width: "400px" };
const wideSize = { height: "600px", width: "800px" };
const halfSize = { height: "300px", width: "400px" };
var cardSize = setCardSize();
return (
<div
className={
"card m-1 p-0 shadow mb-2 " + (hideOnMobile ? "d-lg-flex d-none" : "")
}
style={cardSize}
>
<div className="card-header bg-primary text-light">
<a className="text-decoration-none text-reset" href={path}>
<p className="col m-0">
{cardTitle} <i className="bi bi-box-arrow-up-right col-3"></i>
</p>
</a>
</div>
<div
className="card-body p-2 pt-0"
style={{ overflowY: "auto", overflowX: "hidden" }}
>
{children}
</div>
</div>
);
function setCardSize() {
var cardSize = {};
if (size === "wide") {
cardSize = wideSize;
} else if (size === "half") {
cardSize = halfSize;
} else {
cardSize = normalSize;
}
return cardSize;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
| import { FC, useState } from "react";
import classes from "../../../components/NavBar.module.scss";
export const WidgetTabMenu: FC<{
tabs: {
key: string;
name: string;
component: JSX.Element;
}[];
}> = ({ tabs }) => {
const [selectedTab, setSelectedTab] = useState(
tabs.length > 0 ? tabs[0].key : ""
);
const [hoverState, setHoverState] = useState(false);
const toggleHoverState = () => {
setHoverState(!hoverState);
};
return (
<>
<nav className="sticky-top">
<div
className="nav nav-tabs nav-justified bg-white "
id="nav-tab"
role="tablist"
>
{tabs.map((tab) => (
<button
key={tab.key + " button"}
className={
"nav-link bg-white " +
(selectedTab === tab.key
? "active text-primary " + classes.active
: " text-secondary " + classes.link)
}
id={`nav-${tab.key}-tab`}
data-bs-toggle="tab"
data-bs-target={`#nav-${tab.key}`}
type="button"
role="tab"
aria-controls={`nav-${tab.key}`}
aria-selected={selectedTab === tab.key}
onClick={() => setSelectedTab(tab.key)}
onMouseEnter={toggleHoverState}
onMouseLeave={toggleHoverState}
>
{tab.name}
</button>
))}
</div>
</nav>
<br />
<div className="tab-content" id="nav-tabContent">
{tabs.map(({ key, component }) => (
<div
key={key + "content"}
className={`tab-pane fade ${
selectedTab === key ? "active show " : ""
}`}
id={`nav-${key}`}
role="tabpanel"
aria-labelledby={`nav-${key}-tab`}
>
{component}
</div>
))}
</div>
</>
);
};
|
Dashboard Page#
Here you can see some of the different sizes together on the dashboard page.
The tabs at the top are all the default size, some have the tabs passed into them as a child component so it allows switching panes from within the card.
The link cards at the bottom use the same card, half size, and don’t have tabs passed as children.
Here’s a closer look at how tabs with different paged components are passed to a widget.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| export const MyFinances = () => {
const tabs = [
{
key: "financesummary",
name: "Summary", //Tab name
component: <FinanceSummary />, //Component to show when tab is selected
},
{
key: "financehistory",
name: "History",
component: <FinanceHistory />,
},
{
key: "financenotifications",
name: "Notifications",
component: <FinanceNotifications />,
},
];
return (
<WidgetCard cardTitle="My Finances" hideOnMobile={true}>
<WidgetTabMenu tabs={tabs}></WidgetTabMenu>
</WidgetCard>
);
};
|