first commit
163
src/App.jsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
Routes,
|
||||
Route,
|
||||
useLocation
|
||||
} from 'react-router-dom';
|
||||
|
||||
import './css/style.scss';
|
||||
|
||||
import './charts/ChartjsConfig';
|
||||
|
||||
// Import pages
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import Analytics from './pages/Analytics';
|
||||
import Fintech from './pages/Fintech';
|
||||
import Customers from './pages/ecommerce/Customers';
|
||||
import Orders from './pages/ecommerce/Orders';
|
||||
import Invoices from './pages/ecommerce/Invoices';
|
||||
import Shop from './pages/ecommerce/Shop';
|
||||
import Shop2 from './pages/ecommerce/Shop2';
|
||||
import Product from './pages/ecommerce/Product';
|
||||
import Cart from './pages/ecommerce/Cart';
|
||||
import Cart2 from './pages/ecommerce/Cart2';
|
||||
import Cart3 from './pages/ecommerce/Cart3';
|
||||
import Pay from './pages/ecommerce/Pay';
|
||||
import Campaigns from './pages/Campaigns';
|
||||
import UsersTabs from './pages/community/UsersTabs';
|
||||
import UsersTiles from './pages/community/UsersTiles';
|
||||
import Profile from './pages/community/Profile';
|
||||
import Feed from './pages/community/Feed';
|
||||
import Forum from './pages/community/Forum';
|
||||
import ForumPost from './pages/community/ForumPost';
|
||||
import Meetups from './pages/community/Meetups';
|
||||
import MeetupsPost from './pages/community/MeetupsPost';
|
||||
import CreditCards from './pages/finance/CreditCards';
|
||||
import Transactions from './pages/finance/Transactions';
|
||||
import TransactionDetails from './pages/finance/TransactionDetails';
|
||||
import JobListing from './pages/job/JobListing';
|
||||
import JobPost from './pages/job/JobPost';
|
||||
import CompanyProfile from './pages/job/CompanyProfile';
|
||||
import Messages from './pages/Messages';
|
||||
import TasksKanban from './pages/tasks/TasksKanban';
|
||||
import TasksList from './pages/tasks/TasksList';
|
||||
import Inbox from './pages/Inbox';
|
||||
import Calendar from './pages/Calendar';
|
||||
import Account from './pages/settings/Account';
|
||||
import Notifications from './pages/settings/Notifications';
|
||||
import Apps from './pages/settings/Apps';
|
||||
import Plans from './pages/settings/Plans';
|
||||
import Billing from './pages/settings/Billing';
|
||||
import Feedback from './pages/settings/Feedback';
|
||||
import Changelog from './pages/utility/Changelog';
|
||||
import Roadmap from './pages/utility/Roadmap';
|
||||
import Faqs from './pages/utility/Faqs';
|
||||
import EmptyState from './pages/utility/EmptyState';
|
||||
import PageNotFound from './pages/utility/PageNotFound';
|
||||
import KnowledgeBase from './pages/utility/KnowledgeBase';
|
||||
import Signin from './pages/Signin';
|
||||
import Signup from './pages/Signup';
|
||||
import ResetPassword from './pages/ResetPassword';
|
||||
import Onboarding01 from './pages/Onboarding01';
|
||||
import Onboarding02 from './pages/Onboarding02';
|
||||
import Onboarding03 from './pages/Onboarding03';
|
||||
import Onboarding04 from './pages/Onboarding04';
|
||||
import ButtonPage from './pages/component/ButtonPage';
|
||||
import FormPage from './pages/component/FormPage';
|
||||
import DropdownPage from './pages/component/DropdownPage';
|
||||
import AlertPage from './pages/component/AlertPage';
|
||||
import ModalPage from './pages/component/ModalPage';
|
||||
import PaginationPage from './pages/component/PaginationPage';
|
||||
import TabsPage from './pages/component/TabsPage';
|
||||
import BreadcrumbPage from './pages/component/BreadcrumbPage';
|
||||
import BadgePage from './pages/component/BadgePage';
|
||||
import AvatarPage from './pages/component/AvatarPage';
|
||||
import TooltipPage from './pages/component/TooltipPage';
|
||||
import AccordionPage from './pages/component/AccordionPage';
|
||||
import IconsPage from './pages/component/IconsPage';
|
||||
|
||||
function App() {
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
document.querySelector('html').style.scrollBehavior = 'auto'
|
||||
window.scroll({ top: 0 })
|
||||
document.querySelector('html').style.scrollBehavior = ''
|
||||
}, [location.pathname]); // triggered on route change
|
||||
|
||||
return (
|
||||
<>
|
||||
<Routes>
|
||||
<Route exact path="/" element={<Dashboard />} />
|
||||
<Route path="/dashboard/analytics" element={<Analytics />} />
|
||||
<Route path="/dashboard/fintech" element={<Fintech />} />
|
||||
<Route path="/ecommerce/customers" element={<Customers />} />
|
||||
<Route path="/ecommerce/orders" element={<Orders />} />
|
||||
<Route path="/ecommerce/invoices" element={<Invoices />} />
|
||||
<Route path="/ecommerce/shop" element={<Shop />} />
|
||||
<Route path="/ecommerce/shop-2" element={<Shop2 />} />
|
||||
<Route path="/ecommerce/product" element={<Product />} />
|
||||
<Route path="/ecommerce/cart" element={<Cart />} />
|
||||
<Route path="/ecommerce/cart-2" element={<Cart2 />} />
|
||||
<Route path="/ecommerce/cart-3" element={<Cart3 />} />
|
||||
<Route path="/ecommerce/pay" element={<Pay />} />
|
||||
<Route path="/campaigns" element={<Campaigns />} />
|
||||
<Route path="/community/users-tabs" element={<UsersTabs />} />
|
||||
<Route path="/community/users-tiles" element={<UsersTiles />} />
|
||||
<Route path="/community/profile" element={<Profile />} />
|
||||
<Route path="/community/feed" element={<Feed />} />
|
||||
<Route path="/community/forum" element={<Forum />} />
|
||||
<Route path="/community/forum-post" element={<ForumPost />} />
|
||||
<Route path="/community/meetups" element={<Meetups />} />
|
||||
<Route path="/community/meetups-post" element={<MeetupsPost />} />
|
||||
<Route path="/finance/cards" element={<CreditCards />} />
|
||||
<Route path="/finance/transactions" element={<Transactions />} />
|
||||
<Route path="/finance/transaction-details" element={<TransactionDetails />} />
|
||||
<Route path="/job/job-listing" element={<JobListing />} />
|
||||
<Route path="/job/job-post" element={<JobPost />} />
|
||||
<Route path="/job/company-profile" element={<CompanyProfile />} />
|
||||
<Route path="/messages" element={<Messages />} />
|
||||
<Route path="/tasks/kanban" element={<TasksKanban />} />
|
||||
<Route path="/tasks/list" element={<TasksList />} />
|
||||
<Route path="/inbox" element={<Inbox />} />
|
||||
<Route path="/calendar" element={<Calendar />} />
|
||||
<Route path="/settings/account" element={<Account />} />
|
||||
<Route path="/settings/notifications" element={<Notifications />} />
|
||||
<Route path="/settings/apps" element={<Apps />} />
|
||||
<Route path="/settings/plans" element={<Plans />} />
|
||||
<Route path="/settings/billing" element={<Billing />} />
|
||||
<Route path="/settings/feedback" element={<Feedback />} />
|
||||
<Route path="/utility/changelog" element={<Changelog />} />
|
||||
<Route path="/utility/roadmap" element={<Roadmap />} />
|
||||
<Route path="/utility/faqs" element={<Faqs />} />
|
||||
<Route path="/utility/empty-state" element={<EmptyState />} />
|
||||
<Route path="/utility/404" element={<PageNotFound />} />
|
||||
<Route path="/utility/knowledge-base" element={<KnowledgeBase />} />
|
||||
<Route path="/signin" element={<Signin />} />
|
||||
<Route path="/signup" element={<Signup />} />
|
||||
<Route path="/reset-password" element={<ResetPassword />} />
|
||||
<Route path="/onboarding-01" element={<Onboarding01 />} />
|
||||
<Route path="/onboarding-02" element={<Onboarding02 />} />
|
||||
<Route path="/onboarding-03" element={<Onboarding03 />} />
|
||||
<Route path="/onboarding-04" element={<Onboarding04 />} />
|
||||
<Route path="/component/button" element={<ButtonPage />} />
|
||||
<Route path="/component/form" element={<FormPage />} />
|
||||
<Route path="/component/dropdown" element={<DropdownPage />} />
|
||||
<Route path="/component/alert" element={<AlertPage />} />
|
||||
<Route path="/component/modal" element={<ModalPage />} />
|
||||
<Route path="/component/pagination" element={<PaginationPage />} />
|
||||
<Route path="/component/tabs" element={<TabsPage />} />
|
||||
<Route path="/component/breadcrumb" element={<BreadcrumbPage />} />
|
||||
<Route path="/component/badge" element={<BadgePage />} />
|
||||
<Route path="/component/avatar" element={<AvatarPage />} />
|
||||
<Route path="/component/tooltip" element={<TooltipPage />} />
|
||||
<Route path="/component/accordion" element={<AccordionPage />} />
|
||||
<Route path="/component/icons" element={<IconsPage />} />
|
||||
<Route path="*" element={<PageNotFound />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
162
src/charts/BarChart01.jsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig, formatValue } from '../utils/Utils';
|
||||
|
||||
Chart.register(BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend);
|
||||
|
||||
function BarChart01({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
const legend = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: {
|
||||
top: 12,
|
||||
bottom: 16,
|
||||
left: 20,
|
||||
right: 20,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
maxTicksLimit: 5,
|
||||
callback: (value) => formatValue(value),
|
||||
},
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
displayFormats: {
|
||||
month: 'MMM YY',
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatValue(context.parsed.y),
|
||||
},
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
animation: {
|
||||
duration: 500,
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
plugins: [{
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(c, args, options) {
|
||||
const ul = legend.current;
|
||||
if (!ul) return;
|
||||
// Remove old legend items
|
||||
while (ul.firstChild) {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = c.options.plugins.legend.labels.generateLabels(c);
|
||||
items.forEach((item) => {
|
||||
const li = document.createElement('li');
|
||||
li.style.marginRight = tailwindConfig().theme.margin[4];
|
||||
// Button element
|
||||
const button = document.createElement('button');
|
||||
button.style.display = 'inline-flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.opacity = item.hidden ? '.3' : '';
|
||||
button.onclick = () => {
|
||||
c.setDatasetVisibility(item.datasetIndex, !c.isDatasetVisible(item.datasetIndex));
|
||||
c.update();
|
||||
};
|
||||
// Color box
|
||||
const box = document.createElement('span');
|
||||
box.style.display = 'block';
|
||||
box.style.width = tailwindConfig().theme.width[3];
|
||||
box.style.height = tailwindConfig().theme.height[3];
|
||||
box.style.borderRadius = tailwindConfig().theme.borderRadius.full;
|
||||
box.style.marginRight = tailwindConfig().theme.margin[2];
|
||||
box.style.borderWidth = '3px';
|
||||
box.style.borderColor = item.fillStyle;
|
||||
box.style.pointerEvents = 'none';
|
||||
// Label
|
||||
const labelContainer = document.createElement('span');
|
||||
labelContainer.style.display = 'flex';
|
||||
labelContainer.style.alignItems = 'center';
|
||||
const value = document.createElement('span');
|
||||
value.style.color = tailwindConfig().theme.colors.slate[800];
|
||||
value.style.fontSize = tailwindConfig().theme.fontSize['3xl'][0];
|
||||
value.style.lineHeight = tailwindConfig().theme.fontSize['3xl'][1].lineHeight;
|
||||
value.style.fontWeight = tailwindConfig().theme.fontWeight.bold;
|
||||
value.style.marginRight = tailwindConfig().theme.margin[2];
|
||||
value.style.pointerEvents = 'none';
|
||||
const label = document.createElement('span');
|
||||
label.style.color = tailwindConfig().theme.colors.slate[500];
|
||||
label.style.fontSize = tailwindConfig().theme.fontSize.sm[0];
|
||||
label.style.lineHeight = tailwindConfig().theme.fontSize.sm[1].lineHeight;
|
||||
const theValue = c.data.datasets[item.datasetIndex].data.reduce((a, b) => a + b, 0);
|
||||
const valueText = document.createTextNode(formatValue(theValue));
|
||||
const labelText = document.createTextNode(item.text);
|
||||
value.appendChild(valueText);
|
||||
label.appendChild(labelText);
|
||||
li.appendChild(button);
|
||||
button.appendChild(box);
|
||||
button.appendChild(labelContainer);
|
||||
labelContainer.appendChild(value);
|
||||
labelContainer.appendChild(label);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
},
|
||||
}],
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="px-5 py-3">
|
||||
<ul ref={legend} className="flex flex-wrap"></ul>
|
||||
</div>
|
||||
<div className="grow">
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default BarChart01;
|
||||
99
src/charts/BarChart02.jsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { formatValue } from '../utils/Utils';
|
||||
|
||||
Chart.register(BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend);
|
||||
|
||||
function BarChart02({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: {
|
||||
top: 12,
|
||||
bottom: 16,
|
||||
left: 20,
|
||||
right: 20,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
stacked: true,
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
},
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
maxTicksLimit: 5,
|
||||
callback: (value) => formatValue(value),
|
||||
},
|
||||
},
|
||||
x: {
|
||||
stacked: true,
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
displayFormats: {
|
||||
month: 'MMM YY',
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
autoSkipPadding: 48,
|
||||
maxRotation: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatValue(context.parsed.y),
|
||||
},
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
animation: {
|
||||
duration: 200,
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
);
|
||||
}
|
||||
|
||||
export default BarChart02;
|
||||
156
src/charts/BarChart03.jsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig, formatThousands } from '../utils/Utils';
|
||||
|
||||
Chart.register(BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend);
|
||||
|
||||
function BarChart03({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
const legend = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: {
|
||||
top: 12,
|
||||
bottom: 16,
|
||||
left: 20,
|
||||
right: 20,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
stacked: true,
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
},
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
maxTicksLimit: 5,
|
||||
callback: (value) => formatThousands(value),
|
||||
},
|
||||
},
|
||||
x: {
|
||||
stacked: true,
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
displayFormats: {
|
||||
month: 'MMM',
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
autoSkipPadding: 48,
|
||||
maxRotation: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatThousands(context.parsed.y),
|
||||
},
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
animation: {
|
||||
duration: 500,
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
plugins: [{
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(c, args, options) {
|
||||
const ul = legend.current
|
||||
if (!ul) return
|
||||
// Remove old legend items
|
||||
while (ul.firstChild) {
|
||||
ul.firstChild.remove()
|
||||
}
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = c.options.plugins.legend.labels.generateLabels(c)
|
||||
items.forEach((item) => {
|
||||
const li = document.createElement('li')
|
||||
li.style.marginRight = tailwindConfig().theme.margin[3];
|
||||
// Button element
|
||||
const button = document.createElement('button')
|
||||
button.style.display = 'inline-flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.opacity = item.hidden ? '.3' : '';
|
||||
button.onclick = () => {
|
||||
c.setDatasetVisibility(item.datasetIndex, !c.isDatasetVisible(item.datasetIndex))
|
||||
c.update()
|
||||
};
|
||||
// Color box
|
||||
const box = document.createElement('span')
|
||||
box.style.display = 'block';
|
||||
box.style.width = tailwindConfig().theme.width[3];
|
||||
box.style.height = tailwindConfig().theme.height[3];
|
||||
box.style.borderRadius = tailwindConfig().theme.borderRadius.full;
|
||||
box.style.marginRight = tailwindConfig().theme.margin[2];
|
||||
box.style.borderWidth = '3px';
|
||||
box.style.borderColor = item.fillStyle;
|
||||
box.style.pointerEvents = 'none';
|
||||
// Label
|
||||
const label = document.createElement('span')
|
||||
label.style.color = tailwindConfig().theme.colors.slate[500];
|
||||
label.style.fontSize = tailwindConfig().theme.fontSize.sm[0];
|
||||
label.style.lineHeight = tailwindConfig().theme.fontSize.sm[1].lineHeight;
|
||||
const labelText = document.createTextNode(item.text)
|
||||
label.appendChild(labelText)
|
||||
li.appendChild(button)
|
||||
button.appendChild(box)
|
||||
button.appendChild(label)
|
||||
ul.appendChild(li)
|
||||
})
|
||||
},
|
||||
}],
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="px-5 py-4">
|
||||
<div className="grow mb-1">
|
||||
<ul ref={legend} className="flex flex-wrap"></ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow">
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default BarChart03;
|
||||
149
src/charts/BarChart04.jsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig, formatThousands } from '../utils/Utils';
|
||||
|
||||
Chart.register(BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend);
|
||||
|
||||
function BarChart04({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
const legend = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
layout: {
|
||||
padding: {
|
||||
top: 12,
|
||||
bottom: 16,
|
||||
left: 20,
|
||||
right: 20,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
displayFormats: {
|
||||
month: 'MMM',
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
maxTicksLimit: 3,
|
||||
align: 'end',
|
||||
callback: (value) => formatThousands(value),
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatThousands(context.parsed.x),
|
||||
},
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
animation: {
|
||||
duration: 500,
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
plugins: [{
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(c, args, options) {
|
||||
const ul = legend.current
|
||||
if (!ul) return
|
||||
// Remove old legend items
|
||||
while (ul.firstChild) {
|
||||
ul.firstChild.remove()
|
||||
}
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = c.options.plugins.legend.labels.generateLabels(c)
|
||||
items.forEach((item) => {
|
||||
const li = document.createElement('li')
|
||||
li.style.marginRight = tailwindConfig().theme.margin[4]
|
||||
// Button element
|
||||
const button = document.createElement('button')
|
||||
button.style.display = 'inline-flex'
|
||||
button.style.alignItems = 'center'
|
||||
button.style.opacity = item.hidden ? '.3' : ''
|
||||
button.onclick = () => {
|
||||
c.setDatasetVisibility(item.datasetIndex, !c.isDatasetVisible(item.datasetIndex))
|
||||
c.update()
|
||||
}
|
||||
// Color box
|
||||
const box = document.createElement('span')
|
||||
box.style.display = 'block'
|
||||
box.style.width = tailwindConfig().theme.width[3]
|
||||
box.style.height = tailwindConfig().theme.height[3]
|
||||
box.style.borderRadius = tailwindConfig().theme.borderRadius.full
|
||||
box.style.marginRight = tailwindConfig().theme.margin[2]
|
||||
box.style.borderWidth = '3px'
|
||||
box.style.borderColor = item.fillStyle
|
||||
box.style.pointerEvents = 'none'
|
||||
// Label
|
||||
const label = document.createElement('span')
|
||||
label.style.color = tailwindConfig().theme.colors.slate[500]
|
||||
label.style.fontSize = tailwindConfig().theme.fontSize.sm[0]
|
||||
label.style.lineHeight = tailwindConfig().theme.fontSize.sm[1].lineHeight
|
||||
const labelText = document.createTextNode(item.text)
|
||||
label.appendChild(labelText)
|
||||
li.appendChild(button)
|
||||
button.appendChild(box)
|
||||
button.appendChild(label)
|
||||
ul.appendChild(li)
|
||||
})
|
||||
},
|
||||
}],
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="px-5 py-4">
|
||||
<ul ref={legend} className="flex flex-wrap"></ul>
|
||||
</div>
|
||||
<div className="grow">
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default BarChart04;
|
||||
157
src/charts/BarChart05.jsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig, formatValue } from '../utils/Utils';
|
||||
|
||||
Chart.register(BarController, BarElement, LinearScale, TimeScale, Tooltip, Legend);
|
||||
|
||||
function BarChart05({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
const legend = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: {
|
||||
top: 12,
|
||||
bottom: 16,
|
||||
left: 20,
|
||||
right: 20,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
maxTicksLimit: 5,
|
||||
callback: (value) => formatValue(value),
|
||||
},
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
displayFormats: {
|
||||
month: 'MMM YY',
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatValue(context.parsed.y),
|
||||
},
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
animation: {
|
||||
duration: 500,
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(c, args, options) {
|
||||
const ul = legend.current;
|
||||
if (!ul) return;
|
||||
// Remove old legend items
|
||||
while (ul.firstChild) {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = c.options.plugins.legend.labels.generateLabels(c);
|
||||
items.forEach((item) => {
|
||||
const li = document.createElement('li');
|
||||
li.style.marginRight = tailwindConfig().theme.margin[4];
|
||||
// Button element
|
||||
const button = document.createElement('button');
|
||||
button.style.display = 'inline-flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.opacity = item.hidden ? '.3' : '';
|
||||
button.onclick = () => {
|
||||
c.setDatasetVisibility(item.datasetIndex, !c.isDatasetVisible(item.datasetIndex));
|
||||
c.update();
|
||||
};
|
||||
// Color box
|
||||
const box = document.createElement('span');
|
||||
box.style.display = 'block';
|
||||
box.style.width = tailwindConfig().theme.width[3];
|
||||
box.style.height = tailwindConfig().theme.height[3];
|
||||
box.style.borderRadius = tailwindConfig().theme.borderRadius.full;
|
||||
box.style.marginRight = tailwindConfig().theme.margin[2];
|
||||
box.style.borderWidth = '3px';
|
||||
box.style.borderColor = item.fillStyle;
|
||||
box.style.pointerEvents = 'none';
|
||||
// Label
|
||||
const label = document.createElement('span');
|
||||
label.style.color = tailwindConfig().theme.colors.slate[500];
|
||||
label.style.fontSize = tailwindConfig().theme.fontSize.sm[0];
|
||||
label.style.lineHeight = tailwindConfig().theme.fontSize.sm[1].lineHeight;
|
||||
const labelText = document.createTextNode(item.text);
|
||||
label.appendChild(labelText);
|
||||
li.appendChild(button);
|
||||
button.appendChild(box);
|
||||
button.appendChild(label);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="px-5 py-3">
|
||||
<div className="flex flex-wrap justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<div className="text-3xl font-bold text-slate-800 mr-2">$1,347.09</div>
|
||||
<div className="text-sm">Net</div>
|
||||
</div>
|
||||
<div className="grow ml-2">
|
||||
<ul ref={legend} className="flex flex-wrap justify-end"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow">
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default BarChart05;
|
||||
164
src/charts/BarChart06.jsx
Normal file
@@ -0,0 +1,164 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, BarController, BarElement, LinearScale, CategoryScale, Tooltip, Legend,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig, formatValue } from '../utils/Utils';
|
||||
|
||||
// Import images
|
||||
import revolutIcon from '../images/company-icon-01.svg';
|
||||
import hsbcIcon from '../images/company-icon-02.svg';
|
||||
import qontoIcon from '../images/company-icon-03.svg';
|
||||
import n26Icon from '../images/company-icon-04.svg';
|
||||
|
||||
Chart.register(BarController, BarElement, LinearScale, CategoryScale, Tooltip, Legend);
|
||||
const images = [revolutIcon, hsbcIcon, qontoIcon, n26Icon];
|
||||
|
||||
function BarChart06({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
const legend = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
layout: {
|
||||
padding: {
|
||||
top: 12,
|
||||
bottom: 16,
|
||||
left: 72,
|
||||
right: 20,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
drawTicks: false,
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
maxTicksLimit: 3,
|
||||
align: 'end',
|
||||
callback: (value) => formatValue(value),
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatValue(context.parsed.x),
|
||||
},
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
animation: {
|
||||
duration: 500,
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(c, args, options) {
|
||||
const ul = legend.current;
|
||||
if (!ul) return;
|
||||
// Remove old legend items
|
||||
while (ul.firstChild) {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = c.options.plugins.legend.labels.generateLabels(c);
|
||||
items.forEach((item) => {
|
||||
const li = document.createElement('li');
|
||||
li.style.marginRight = tailwindConfig().theme.margin[4];
|
||||
// Button element
|
||||
const button = document.createElement('button');
|
||||
button.style.display = 'inline-flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.opacity = item.hidden ? '.3' : '';
|
||||
button.onclick = () => {
|
||||
c.setDatasetVisibility(item.datasetIndex, !c.isDatasetVisible(item.datasetIndex));
|
||||
c.update();
|
||||
};
|
||||
// Color box
|
||||
const box = document.createElement('span');
|
||||
box.style.display = 'block';
|
||||
box.style.width = tailwindConfig().theme.width[3];
|
||||
box.style.height = tailwindConfig().theme.height[3];
|
||||
box.style.borderRadius = tailwindConfig().theme.borderRadius.full;
|
||||
box.style.marginRight = tailwindConfig().theme.margin[2];
|
||||
box.style.borderWidth = '3px';
|
||||
box.style.borderColor = item.fillStyle;
|
||||
box.style.pointerEvents = 'none';
|
||||
// Label
|
||||
const label = document.createElement('span');
|
||||
label.style.color = tailwindConfig().theme.colors.slate[500];
|
||||
label.style.fontSize = tailwindConfig().theme.fontSize.sm[0];
|
||||
label.style.lineHeight = tailwindConfig().theme.fontSize.sm[1].lineHeight;
|
||||
const labelText = document.createTextNode(item.text);
|
||||
label.appendChild(labelText);
|
||||
li.appendChild(button);
|
||||
button.appendChild(box);
|
||||
button.appendChild(label);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
},
|
||||
afterDraw(c) {
|
||||
const xAxis = c.scales.x;
|
||||
const yAxis = c.scales.y;
|
||||
yAxis.ticks.forEach((value, index) => {
|
||||
const y = yAxis.getPixelForTick(index);
|
||||
const image = new Image();
|
||||
image.src = images[index];
|
||||
c.ctx.drawImage(image, xAxis.left - 52, y - 18);
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="px-5 py-4">
|
||||
<ul ref={legend} className="flex flex-wrap"></ul>
|
||||
</div>
|
||||
<div className="grow">
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default BarChart06;
|
||||
42
src/charts/ChartjsConfig.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
// Import Chart.js
|
||||
import { Chart, Tooltip } from 'chart.js';
|
||||
// Import Tailwind config
|
||||
import { tailwindConfig } from '../utils/Utils';
|
||||
|
||||
Chart.register(Tooltip);
|
||||
|
||||
// Define Chart.js default settings
|
||||
Chart.defaults.font.family = '"Inter", sans-serif';
|
||||
Chart.defaults.font.weight = '500';
|
||||
Chart.defaults.color = tailwindConfig().theme.colors.slate[400];
|
||||
Chart.defaults.scale.grid.color = tailwindConfig().theme.colors.slate[100];
|
||||
Chart.defaults.plugins.tooltip.titleColor = tailwindConfig().theme.colors.slate[800];
|
||||
Chart.defaults.plugins.tooltip.bodyColor = tailwindConfig().theme.colors.slate[800];
|
||||
Chart.defaults.plugins.tooltip.backgroundColor = tailwindConfig().theme.colors.white;
|
||||
Chart.defaults.plugins.tooltip.borderWidth = 1;
|
||||
Chart.defaults.plugins.tooltip.borderColor = tailwindConfig().theme.colors.slate[200];
|
||||
Chart.defaults.plugins.tooltip.displayColors = false;
|
||||
Chart.defaults.plugins.tooltip.mode = 'nearest';
|
||||
Chart.defaults.plugins.tooltip.intersect = false;
|
||||
Chart.defaults.plugins.tooltip.position = 'nearest';
|
||||
Chart.defaults.plugins.tooltip.caretSize = 0;
|
||||
Chart.defaults.plugins.tooltip.caretPadding = 20;
|
||||
Chart.defaults.plugins.tooltip.cornerRadius = 4;
|
||||
Chart.defaults.plugins.tooltip.padding = 8;
|
||||
|
||||
// Register Chart.js plugin to add a bg option for chart area
|
||||
Chart.register({
|
||||
id: 'chartAreaPlugin',
|
||||
// eslint-disable-next-line object-shorthand
|
||||
beforeDraw: (chart) => {
|
||||
if (chart.config.options.chartArea && chart.config.options.chartArea.backgroundColor) {
|
||||
const ctx = chart.canvas.getContext('2d');
|
||||
const { chartArea } = chart;
|
||||
ctx.save();
|
||||
ctx.fillStyle = chart.config.options.chartArea.backgroundColor;
|
||||
// eslint-disable-next-line max-len
|
||||
ctx.fillRect(chartArea.left, chartArea.top, chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
|
||||
ctx.restore();
|
||||
}
|
||||
},
|
||||
});
|
||||
114
src/charts/DoughnutChart.jsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, DoughnutController, ArcElement, TimeScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig } from '../utils/Utils';
|
||||
|
||||
Chart.register(DoughnutController, ArcElement, TimeScale, Tooltip);
|
||||
|
||||
function DoughnutChart({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
const legend = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: data,
|
||||
options: {
|
||||
cutout: '80%',
|
||||
layout: {
|
||||
padding: 24,
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
animation: {
|
||||
duration: 500,
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
plugins: [{
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(c, args, options) {
|
||||
const ul = legend.current;
|
||||
if (!ul) return;
|
||||
// Remove old legend items
|
||||
while (ul.firstChild) {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = c.options.plugins.legend.labels.generateLabels(c);
|
||||
items.forEach((item) => {
|
||||
const li = document.createElement('li');
|
||||
li.style.margin = tailwindConfig().theme.margin[1];
|
||||
// Button element
|
||||
const button = document.createElement('button');
|
||||
button.classList.add('btn-xs');
|
||||
button.style.backgroundColor = tailwindConfig().theme.colors.white;
|
||||
button.style.borderWidth = tailwindConfig().theme.borderWidth[1];
|
||||
button.style.borderColor = tailwindConfig().theme.colors.slate[200];
|
||||
button.style.color = tailwindConfig().theme.colors.slate[500];
|
||||
button.style.boxShadow = tailwindConfig().theme.boxShadow.md;
|
||||
button.style.opacity = item.hidden ? '.3' : '';
|
||||
button.onclick = () => {
|
||||
c.toggleDataVisibility(item.index, !item.index);
|
||||
c.update();
|
||||
};
|
||||
// Color box
|
||||
const box = document.createElement('span');
|
||||
box.style.display = 'block';
|
||||
box.style.width = tailwindConfig().theme.width[2];
|
||||
box.style.height = tailwindConfig().theme.height[2];
|
||||
box.style.backgroundColor = item.fillStyle;
|
||||
box.style.borderRadius = tailwindConfig().theme.borderRadius.sm;
|
||||
box.style.marginRight = tailwindConfig().theme.margin[1];
|
||||
box.style.pointerEvents = 'none';
|
||||
// Label
|
||||
const label = document.createElement('span');
|
||||
label.style.display = 'flex';
|
||||
label.style.alignItems = 'center';
|
||||
const labelText = document.createTextNode(item.text);
|
||||
label.appendChild(labelText);
|
||||
li.appendChild(button);
|
||||
button.appendChild(box);
|
||||
button.appendChild(label);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
},
|
||||
}],
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="grow flex flex-col justify-center">
|
||||
<div>
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
</div>
|
||||
<div className="px-5 pt-2 pb-6">
|
||||
<ul ref={legend} className="flex flex-wrap justify-center -m-1"></ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DoughnutChart;
|
||||
76
src/charts/LineChart01.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig, formatValue } from '../utils/Utils';
|
||||
|
||||
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip);
|
||||
|
||||
function LineChart01({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
chartArea: {
|
||||
backgroundColor: tailwindConfig().theme.colors.slate[50],
|
||||
},
|
||||
layout: {
|
||||
padding: 20,
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
display: false,
|
||||
beginAtZero: true,
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
},
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatValue(context.parsed.y),
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
);
|
||||
}
|
||||
|
||||
export default LineChart01;
|
||||
153
src/charts/LineChart02.jsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig, formatValue } from '../utils/Utils';
|
||||
|
||||
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip);
|
||||
|
||||
function LineChart02({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
const legend = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: 20,
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
beginAtZero: true,
|
||||
},
|
||||
ticks: {
|
||||
maxTicksLimit: 5,
|
||||
callback: (value) => formatValue(value),
|
||||
},
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
displayFormats: {
|
||||
month: 'MMM YY',
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
autoSkipPadding: 48,
|
||||
maxRotation: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatValue(context.parsed.y),
|
||||
},
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
plugins: [{
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(c, args, options) {
|
||||
const ul = legend.current;
|
||||
if (!ul) return;
|
||||
// Remove old legend items
|
||||
while (ul.firstChild) {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = c.options.plugins.legend.labels.generateLabels(c);
|
||||
items.slice(0, 2).forEach((item) => {
|
||||
const li = document.createElement('li');
|
||||
li.style.marginLeft = tailwindConfig().theme.margin[3];
|
||||
// Button element
|
||||
const button = document.createElement('button');
|
||||
button.style.display = 'inline-flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.opacity = item.hidden ? '.3' : '';
|
||||
button.onclick = () => {
|
||||
c.setDatasetVisibility(item.datasetIndex, !c.isDatasetVisible(item.datasetIndex));
|
||||
c.update();
|
||||
};
|
||||
// Color box
|
||||
const box = document.createElement('span');
|
||||
box.style.display = 'block';
|
||||
box.style.width = tailwindConfig().theme.width[3];
|
||||
box.style.height = tailwindConfig().theme.height[3];
|
||||
box.style.borderRadius = tailwindConfig().theme.borderRadius.full;
|
||||
box.style.marginRight = tailwindConfig().theme.margin[2];
|
||||
box.style.borderWidth = '3px';
|
||||
box.style.borderColor = c.data.datasets[item.datasetIndex].borderColor;
|
||||
box.style.pointerEvents = 'none';
|
||||
// Label
|
||||
const label = document.createElement('span');
|
||||
label.style.color = tailwindConfig().theme.colors.slate[500];
|
||||
label.style.fontSize = tailwindConfig().theme.fontSize.sm[0];
|
||||
label.style.lineHeight = tailwindConfig().theme.fontSize.sm[1].lineHeight;
|
||||
const labelText = document.createTextNode(item.text);
|
||||
label.appendChild(labelText);
|
||||
li.appendChild(button);
|
||||
button.appendChild(box);
|
||||
button.appendChild(label);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
},
|
||||
}],
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="px-5 py-3">
|
||||
<div className="flex flex-wrap justify-between items-end">
|
||||
<div className="flex items-start">
|
||||
<div className="text-3xl font-bold text-slate-800 mr-2">$1,482</div>
|
||||
<div className="text-sm font-semibold text-white px-1.5 bg-amber-500 rounded-full">-22%</div>
|
||||
</div>
|
||||
<div className="grow ml-2 mb-1">
|
||||
<ul ref={legend} className="flex flex-wrap justify-end"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Chart built with Chart.js 3 */}
|
||||
<div className="grow">
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default LineChart02;
|
||||
88
src/charts/LineChart03.jsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { formatThousands } from '../utils/Utils';
|
||||
|
||||
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip);
|
||||
|
||||
function LineChart03({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: 20,
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
callback: (value) => formatThousands(value),
|
||||
},
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
displayFormats: {
|
||||
month: 'MMM YY',
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
autoSkipPadding: 48,
|
||||
maxRotation: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatThousands(context.parsed.y),
|
||||
},
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
);
|
||||
}
|
||||
|
||||
export default LineChart03;
|
||||
78
src/charts/LineChart04.jsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig, formatThousands } from '../utils/Utils';
|
||||
|
||||
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip);
|
||||
|
||||
function LineChart04({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
chartArea: {
|
||||
backgroundColor: tailwindConfig().theme.colors.slate[50],
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
display: false,
|
||||
beginAtZero: true,
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
},
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatThousands(context.parsed.y),
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
},
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
);
|
||||
}
|
||||
|
||||
export default LineChart04;
|
||||
157
src/charts/LineChart05.jsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig } from '../utils/Utils';
|
||||
|
||||
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip);
|
||||
|
||||
function LineChart05({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
const legend = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: 20,
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
maxTicksLimit: 7,
|
||||
callback: (value) => `${value}%`,
|
||||
},
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
displayFormats: {
|
||||
month: 'MMM YY',
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
autoSkipPadding: 48,
|
||||
maxRotation: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => `${context.parsed.y}%`,
|
||||
},
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(c, args, options) {
|
||||
const ul = legend.current;
|
||||
if (!ul) return;
|
||||
// Remove old legend items
|
||||
while (ul.firstChild) {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = c.options.plugins.legend.labels.generateLabels(c);
|
||||
items.forEach((item) => {
|
||||
const li = document.createElement('li');
|
||||
li.style.marginLeft = tailwindConfig().theme.margin[3];
|
||||
// Button element
|
||||
const button = document.createElement('button');
|
||||
button.style.display = 'inline-flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.opacity = item.hidden ? '.3' : '';
|
||||
button.onclick = () => {
|
||||
c.setDatasetVisibility(item.datasetIndex, !c.isDatasetVisible(item.datasetIndex));
|
||||
c.update();
|
||||
};
|
||||
// Color box
|
||||
const box = document.createElement('span');
|
||||
box.style.display = 'block';
|
||||
box.style.width = tailwindConfig().theme.width[3];
|
||||
box.style.height = tailwindConfig().theme.height[3];
|
||||
box.style.borderRadius = tailwindConfig().theme.borderRadius.full;
|
||||
box.style.marginRight = tailwindConfig().theme.margin[2];
|
||||
box.style.borderWidth = '3px';
|
||||
box.style.borderColor = c.data.datasets[item.datasetIndex].borderColor;
|
||||
box.style.pointerEvents = 'none';
|
||||
// Label
|
||||
const label = document.createElement('span');
|
||||
label.style.color = tailwindConfig().theme.colors.slate[500];
|
||||
label.style.fontSize = tailwindConfig().theme.fontSize.sm[0];
|
||||
label.style.lineHeight = tailwindConfig().theme.fontSize.sm[1].lineHeight;
|
||||
const labelText = document.createTextNode(item.text);
|
||||
label.appendChild(labelText);
|
||||
li.appendChild(button);
|
||||
button.appendChild(box);
|
||||
button.appendChild(label);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="px-5 py-3">
|
||||
<div className="flex flex-wrap justify-between items-end">
|
||||
<div className="flex items-center">
|
||||
<div className="text-3xl font-bold text-slate-800 mr-2">244.7%</div>
|
||||
<div className="text-sm">
|
||||
<span className="font-medium text-slate-800">17.4%</span> AVG
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow ml-2 mb-1">
|
||||
<ul ref={legend} className="flex flex-wrap justify-end" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Chart built with Chart.js 3 */}
|
||||
<div className="grow">
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default LineChart05;
|
||||
94
src/charts/LineChart06.jsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { formatValue } from '../utils/Utils';
|
||||
|
||||
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip);
|
||||
|
||||
function LineChart06({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: {
|
||||
top: 12,
|
||||
bottom: 16,
|
||||
left: 20,
|
||||
right: 20,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
maxTicksLimit: 7,
|
||||
callback: (value) => formatValue(value),
|
||||
},
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
displayFormats: {
|
||||
month: 'MMM YY',
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
autoSkipPadding: 48,
|
||||
maxRotation: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatValue(context.parsed.y),
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
);
|
||||
}
|
||||
|
||||
export default LineChart06;
|
||||
86
src/charts/LineChart07.jsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, LineController, LineElement, Filler, PointElement, LinearScale, CategoryScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { formatValue } from '../utils/Utils';
|
||||
|
||||
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, CategoryScale, Tooltip);
|
||||
|
||||
function LineChart07({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: {
|
||||
top: 12,
|
||||
bottom: 16,
|
||||
left: 20,
|
||||
right: 20,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
maxTicksLimit: 7,
|
||||
callback: (value) => formatValue(value),
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
autoSkipPadding: 48,
|
||||
maxRotation: 0,
|
||||
align: 'end',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatValue(context.parsed.y),
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
},
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
);
|
||||
}
|
||||
|
||||
export default LineChart07;
|
||||
84
src/charts/LineChart08.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { formatValue } from '../utils/Utils';
|
||||
|
||||
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip);
|
||||
|
||||
function LineChart08({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: {
|
||||
top: 16,
|
||||
bottom: 16,
|
||||
left: 20,
|
||||
right: 20,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
drawTicks: false,
|
||||
},
|
||||
ticks: {
|
||||
maxTicksLimit: 2,
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
},
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatValue(context.parsed.y),
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
},
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
);
|
||||
}
|
||||
|
||||
export default LineChart08;
|
||||
69
src/charts/LineChart09.jsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { formatValue } from '../utils/Utils';
|
||||
|
||||
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip);
|
||||
|
||||
function LineChart09({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
display: false,
|
||||
beginAtZero: true,
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'MM-DD-YYYY',
|
||||
unit: 'month',
|
||||
},
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
title: () => false, // Disable tooltip title
|
||||
label: (context) => formatValue(context.parsed.y),
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
},
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
);
|
||||
}
|
||||
|
||||
export default LineChart09;
|
||||
117
src/charts/PieChart.jsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, PieController, ArcElement, TimeScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig } from '../utils/Utils';
|
||||
|
||||
Chart.register(PieController, ArcElement, TimeScale, Tooltip);
|
||||
|
||||
function PieChart({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
const legend = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'pie',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: {
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
left: 24,
|
||||
right: 24,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
animation: {
|
||||
duration: 200,
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(c, args, options) {
|
||||
const ul = legend.current;
|
||||
if (!ul) return;
|
||||
// Remove old legend items
|
||||
while (ul.firstChild) {
|
||||
ul.firstChild.remove();
|
||||
}
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = c.options.plugins.legend.labels.generateLabels(c);
|
||||
items.forEach((item) => {
|
||||
const li = document.createElement('li');
|
||||
li.style.margin = tailwindConfig().theme.margin[1.5];
|
||||
// Button element
|
||||
const button = document.createElement('button');
|
||||
button.style.display = 'inline-flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.style.opacity = item.hidden ? '.3' : '';
|
||||
button.onclick = () => {
|
||||
c.toggleDataVisibility(item.index, !item.index);
|
||||
c.update();
|
||||
};
|
||||
// Color box
|
||||
const box = document.createElement('span');
|
||||
box.style.display = 'block';
|
||||
box.style.width = tailwindConfig().theme.width[3];
|
||||
box.style.height = tailwindConfig().theme.height[3];
|
||||
box.style.borderRadius = tailwindConfig().theme.borderRadius.full;
|
||||
box.style.marginRight = tailwindConfig().theme.margin[1.5];
|
||||
box.style.borderWidth = '3px';
|
||||
box.style.borderColor = item.fillStyle;
|
||||
box.style.pointerEvents = 'none';
|
||||
// Label
|
||||
const label = document.createElement('span');
|
||||
label.style.color = tailwindConfig().theme.colors.slate[500];
|
||||
label.style.fontSize = tailwindConfig().theme.fontSize.sm[0];
|
||||
label.style.lineHeight = tailwindConfig().theme.fontSize.sm[1].lineHeight;
|
||||
const labelText = document.createTextNode(item.text);
|
||||
label.appendChild(labelText);
|
||||
li.appendChild(button);
|
||||
button.appendChild(box);
|
||||
button.appendChild(label);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="grow flex flex-col justify-center">
|
||||
<div>
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
</div>
|
||||
<div className="px-5 py-4">
|
||||
<ul ref={legend} className="flex flex-wrap justify-center -m-1" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PieChart;
|
||||
113
src/charts/PolarChart.jsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, PolarAreaController, RadialLinearScale, Tooltip, Legend,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig } from '../utils/Utils';
|
||||
|
||||
Chart.register(PolarAreaController, RadialLinearScale, Tooltip, Legend);
|
||||
|
||||
function PolarChart({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
const legend = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'polarArea',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: 24,
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
animation: {
|
||||
duration: 500,
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
plugins: [{
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(c, args, options) {
|
||||
const ul = legend.current
|
||||
if (!ul) return
|
||||
// Remove old legend items
|
||||
while (ul.firstChild) {
|
||||
ul.firstChild.remove()
|
||||
}
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = c.options.plugins.legend.labels.generateLabels(c)
|
||||
items.forEach((item) => {
|
||||
const li = document.createElement('li')
|
||||
li.style.margin = tailwindConfig().theme.margin[1]
|
||||
// Button element
|
||||
const button = document.createElement('button')
|
||||
button.classList.add('btn-xs')
|
||||
button.style.backgroundColor = tailwindConfig().theme.colors.white
|
||||
button.style.borderWidth = tailwindConfig().theme.borderWidth[1]
|
||||
button.style.borderColor = tailwindConfig().theme.colors.slate[200]
|
||||
button.style.color = tailwindConfig().theme.colors.slate[500]
|
||||
button.style.boxShadow = tailwindConfig().theme.boxShadow.md
|
||||
button.style.opacity = item.hidden ? '.3' : ''
|
||||
button.onclick = () => {
|
||||
c.toggleDataVisibility(item.index, !item.index)
|
||||
c.update()
|
||||
}
|
||||
// Color box
|
||||
const box = document.createElement('span')
|
||||
box.style.display = 'block'
|
||||
box.style.width = tailwindConfig().theme.width[2]
|
||||
box.style.height = tailwindConfig().theme.height[2]
|
||||
box.style.backgroundColor = item.fillStyle
|
||||
box.style.borderRadius = tailwindConfig().theme.borderRadius.sm
|
||||
box.style.marginRight = tailwindConfig().theme.margin[1]
|
||||
box.style.pointerEvents = 'none'
|
||||
// Label
|
||||
const label = document.createElement('span')
|
||||
label.style.display = 'flex'
|
||||
label.style.alignItems = 'center'
|
||||
const labelText = document.createTextNode(item.text)
|
||||
label.appendChild(labelText)
|
||||
li.appendChild(button)
|
||||
button.appendChild(box)
|
||||
button.appendChild(label)
|
||||
ul.appendChild(li)
|
||||
})
|
||||
},
|
||||
}],
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="grow flex flex-col justify-center">
|
||||
<div>
|
||||
<canvas ref={canvas} width={width} height={height}></canvas>
|
||||
</div>
|
||||
<div className="px-5 pt-2 pb-6">
|
||||
<ul ref={legend} className="flex flex-wrap justify-center -m-1"></ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PolarChart;
|
||||
120
src/charts/RealtimeChart.jsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Chart, LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip,
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
// Import utilities
|
||||
import { tailwindConfig, formatValue } from '../utils/Utils';
|
||||
|
||||
Chart.register(LineController, LineElement, Filler, PointElement, LinearScale, TimeScale, Tooltip);
|
||||
|
||||
function RealtimeChart({
|
||||
data,
|
||||
width,
|
||||
height
|
||||
}) {
|
||||
|
||||
const canvas = useRef(null);
|
||||
const chartValue = useRef(null);
|
||||
const chartDeviation = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ctx = canvas.current;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
layout: {
|
||||
padding: 20,
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
grid: {
|
||||
drawBorder: false,
|
||||
},
|
||||
suggestedMin: 30,
|
||||
suggestedMax: 80,
|
||||
ticks: {
|
||||
maxTicksLimit: 5,
|
||||
callback: (value) => formatValue(value),
|
||||
},
|
||||
},
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
parser: 'hh:mm:ss',
|
||||
unit: 'second',
|
||||
tooltipFormat: 'MMM DD, H:mm:ss a',
|
||||
displayFormats: {
|
||||
second: 'H:mm:ss',
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
autoSkipPadding: 48,
|
||||
maxRotation: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
titleFont: {
|
||||
weight: '600',
|
||||
},
|
||||
callbacks: {
|
||||
label: (context) => formatValue(context.parsed.y),
|
||||
},
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'nearest',
|
||||
},
|
||||
animation: false,
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
},
|
||||
});
|
||||
return () => chart.destroy();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data]);
|
||||
|
||||
// Update header values
|
||||
useEffect(() => {
|
||||
const currentValue = data.datasets[0].data[data.datasets[0].data.length - 1];
|
||||
const previousValue = data.datasets[0].data[data.datasets[0].data.length - 2];
|
||||
const diff = ((currentValue - previousValue) / previousValue) * 100;
|
||||
chartValue.current.innerHTML = data.datasets[0].data[data.datasets[0].data.length - 1];
|
||||
if (diff < 0) {
|
||||
chartDeviation.current.style.backgroundColor = tailwindConfig().theme.colors.yellow[500];
|
||||
} else {
|
||||
chartDeviation.current.style.backgroundColor = tailwindConfig().theme.colors.emerald[500];
|
||||
}
|
||||
chartDeviation.current.innerHTML = `${diff > 0 ? '+' : ''}${diff.toFixed(2)}%`;
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="px-5 py-3">
|
||||
<div className="flex items-start">
|
||||
<div className="text-3xl font-bold text-slate-800 mr-2 tabular-nums">$<span ref={chartValue}>57.81</span></div>
|
||||
<div ref={chartDeviation} className="text-sm font-semibold text-white px-1.5 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow">
|
||||
<canvas ref={canvas} data={data} width={width} height={height}></canvas>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default RealtimeChart;
|
||||
26
src/components/AccordionBasic.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
function AccordionBasic(props) {
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="px-5 py-4 rounded-sm border border-slate-200">
|
||||
<button
|
||||
className="flex items-center justify-between w-full group mb-1"
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<div className="text-sm text-slate-800 font-medium">{props.title}</div>
|
||||
<svg className={`w-8 h-8 shrink-0 fill-current text-slate-400 group-hover:text-slate-500 ml-3 ${open && 'rotate-180'}`} viewBox="0 0 32 32">
|
||||
<path d="M16 20l-5.4-5.4 1.4-1.4 4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className={`text-sm ${!open && 'hidden'}`}>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccordionBasic;
|
||||
73
src/components/AccordionTableItem.jsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
function AccordionTableItem(props) {
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<tbody className="text-sm">
|
||||
<tr>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="flex items-center text-slate-800">
|
||||
<div className="w-10 h-10 shrink-0 flex items-center justify-center bg-slate-100 rounded-full mr-2 sm:mr-3">
|
||||
<img className="rounded-full ml-1" src={props.image} width="40" height="40" alt={props.customer} />
|
||||
</div>
|
||||
<div className="font-medium text-slate-800">{props.customer}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-left font-medium text-emerald-500">{props.total}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="inline-flex font-medium bg-amber-100 text-amber-600 rounded-full text-center px-2.5 py-0.5">{props.status}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-center">{props.items}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-left">{props.location}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 shrink-0 mr-2" viewBox="0 0 16 16">
|
||||
<path d="M4.3 4.5c1.9-1.9 5.1-1.9 7 0 .7.7 1.2 1.7 1.4 2.7l2-.3c-.2-1.5-.9-2.8-1.9-3.8C10.1.4 5.7.4 2.9 3.1L.7.9 0 7.3l6.4-.7-2.1-2.1zM15.6 8.7l-6.4.7 2.1 2.1c-1.9 1.9-5.1 1.9-7 0-.7-.7-1.2-1.7-1.4-2.7l-2 .3c.2 1.5.9 2.8 1.9 3.8 1.4 1.4 3.1 2 4.9 2 1.8 0 3.6-.7 4.9-2l2.2 2.2.8-6.4z" />
|
||||
</svg>
|
||||
<div>{props.type}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap w-px">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className={`text-slate-400 hover:text-slate-500 transform ${open && 'rotate-180'}`}
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen(!open)}
|
||||
aria-controls={`description-${props.id}`}
|
||||
>
|
||||
<span className="sr-only">Menu</span>
|
||||
<svg className="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<path d="M16 20l-5.4-5.4 1.4-1.4 4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/*
|
||||
Example of content revealing when clicking the button on the right side:
|
||||
Note that you must set a "colSpan" attribute on the <td> element,
|
||||
and it should match the number of columns in your table
|
||||
*/}
|
||||
<tr id={`description-${props.id}`} role="region" className={`${!open && 'hidden'}`}>
|
||||
<td colSpan="10" className="px-2 first:pl-5 last:pr-5 py-3">
|
||||
<div className="flex items-center bg-slate-50 p-3 -mt-3">
|
||||
<svg className="w-4 h-4 shrink-0 fill-current text-slate-400 mr-2">
|
||||
<path d="M1 16h3c.3 0 .5-.1.7-.3l11-11c.4-.4.4-1 0-1.4l-3-3c-.4-.4-1-.4-1.4 0l-11 11c-.2.2-.3.4-.3.7v3c0 .6.4 1 1 1zm1-3.6l10-10L13.6 4l-10 10H2v-1.6z" />
|
||||
</svg>
|
||||
<div className="italic">{props.description}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccordionTableItem;
|
||||
66
src/components/AccordionTableRichItem.jsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
function AccordionTableRichItem(props) {
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<tbody className="text-sm">
|
||||
<tr>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="flex items-center text-slate-800">
|
||||
<div className="w-10 h-10 shrink-0 flex items-center justify-center bg-slate-100 rounded-full mr-2 sm:mr-3">
|
||||
<img className="rounded-full ml-1" src={props.image} width="40" height="40" alt={props.customer} />
|
||||
</div>
|
||||
<div className="font-medium text-slate-800">{props.customer}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-left">{props.email}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-left">{props.location}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-left">{props.date}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-left text-emerald-500 font-medium">{props.amount}</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap w-px">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className={`text-slate-400 hover:text-slate-500 transform ${open && 'rotate-180'}`}
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen(!open)}
|
||||
aria-controls={`description-${props.id}`}
|
||||
>
|
||||
<span className="sr-only">Menu</span>
|
||||
<svg className="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<path d="M16 20l-5.4-5.4 1.4-1.4 4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/*
|
||||
Example of content revealing when clicking the button on the right side:
|
||||
Note that you must set a "colSpan" attribute on the <td> element,
|
||||
and it should match the number of columns in your table
|
||||
*/}
|
||||
<tr id={`description-${props.id}`} role="region" className={`${!open && 'hidden'}`}>
|
||||
<td colSpan="10" className="px-2 first:pl-5 last:pr-5 py-3">
|
||||
<div className="bg-slate-50 p-3 -mt-3">
|
||||
<div className="text-sm mb-3">
|
||||
<div className="font-medium text-slate-800 mb-1">{props.descriptionTitle}</div>
|
||||
<div>{props.descriptionBody}</div>
|
||||
</div>
|
||||
<button className="btn-xs bg-indigo-500 hover:bg-indigo-600 text-white">Approve</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccordionTableRichItem;
|
||||
79
src/components/Banner.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
|
||||
function Banner({
|
||||
children,
|
||||
className,
|
||||
type,
|
||||
open,
|
||||
setOpen
|
||||
}) {
|
||||
|
||||
const typeIcon = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
);
|
||||
case 'error':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
);
|
||||
case 'success':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const typeColor = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'bg-amber-500';
|
||||
case 'error':
|
||||
return 'bg-rose-500';
|
||||
case 'success':
|
||||
return 'bg-emerald-500';
|
||||
default:
|
||||
return 'bg-indigo-500';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{open &&
|
||||
<div className={className}>
|
||||
<div className={`px-4 py-2 rounded-sm text-sm text-white ${typeColor(type)}`}>
|
||||
<div className="flex w-full justify-between items-start">
|
||||
<div className="flex">
|
||||
{typeIcon(type)}
|
||||
<div className="font-medium">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<button className="opacity-70 hover:opacity-80 ml-3 mt-[3px]" onClick={() => setOpen(false)}>
|
||||
<div className="sr-only">Close</div>
|
||||
<svg className="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Banner;
|
||||
79
src/components/Banner2.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
|
||||
function Banner2({
|
||||
children,
|
||||
className,
|
||||
type,
|
||||
open,
|
||||
setOpen
|
||||
}) {
|
||||
|
||||
const typeIcon = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
);
|
||||
case 'error':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
);
|
||||
case 'success':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const typeColor = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'bg-amber-100 border-amber-200 text-amber-600';
|
||||
case 'error':
|
||||
return 'bg-rose-100 border-rose-200 text-rose-600';
|
||||
case 'success':
|
||||
return 'bg-emerald-100 border-emerald-200 text-emerald-600';
|
||||
default:
|
||||
return 'bg-indigo-100 border-indigo-200 text-indigo-500';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{open &&
|
||||
<div className={className}>
|
||||
<div className={`px-4 py-2 rounded-sm text-sm border ${typeColor(type)}`}>
|
||||
<div className="flex w-full justify-between items-start">
|
||||
<div className="flex">
|
||||
{typeIcon(type)}
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<button className="opacity-70 hover:opacity-80 ml-3 mt-[3px]" onClick={() => setOpen(false)}>
|
||||
<div className="sr-only">Close</div>
|
||||
<svg className="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Banner2;
|
||||
116
src/components/DateSelect.jsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function DateSelect() {
|
||||
|
||||
const options = [
|
||||
{
|
||||
id: 0,
|
||||
period: 'Today'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
period: 'Last 7 Days'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
period: 'Last Month'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
period: 'Last 12 Months'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
period: 'All Time'
|
||||
}
|
||||
];
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [selected, setSelected] = useState(2);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
ref={trigger}
|
||||
className="btn justify-between min-w-44 bg-white border-slate-200 hover:border-slate-300 text-slate-500 hover:text-slate-600"
|
||||
aria-label="Select date range"
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<svg className="w-4 h-4 fill-current text-slate-500 shrink-0 mr-2" viewBox="0 0 16 16">
|
||||
<path d="M15 2h-2V0h-2v2H9V0H7v2H5V0H3v2H1a1 1 0 00-1 1v12a1 1 0 001 1h14a1 1 0 001-1V3a1 1 0 00-1-1zm-1 12H2V6h12v8z" />
|
||||
</svg>
|
||||
<span>{options[selected].period}</span>
|
||||
</span>
|
||||
<svg className="shrink-0 ml-1 fill-current text-slate-400" width="11" height="7" viewBox="0 0 11 7">
|
||||
<path d="M5.4 6.8L0 1.4 1.4 0l4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
<Transition
|
||||
show={dropdownOpen}
|
||||
tag="div"
|
||||
className="z-10 absolute top-full right-0 w-full bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1"
|
||||
enter="transition ease-out duration-100 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-100"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<div
|
||||
ref={dropdown}
|
||||
className="font-medium text-sm text-slate-600"
|
||||
onFocus={() => setDropdownOpen(true)}
|
||||
onBlur={() => setDropdownOpen(false)}
|
||||
>
|
||||
{
|
||||
options.map(option => {
|
||||
return (
|
||||
<button
|
||||
key={option.id}
|
||||
tabIndex="0"
|
||||
className={`flex items-center w-full hover:bg-slate-50 py-1 px-3 cursor-pointer ${option.id === selected && 'text-indigo-500'}`}
|
||||
onClick={() => { setSelected(option.id); setDropdownOpen(false); }}
|
||||
>
|
||||
<svg className={`shrink-0 mr-2 fill-current text-indigo-500 ${option.id !== selected && 'invisible'}`} width="12" height="9" viewBox="0 0 12 9">
|
||||
<path d="M10.28.28L3.989 6.575 1.695 4.28A1 1 0 00.28 5.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28.28z" />
|
||||
</svg>
|
||||
<span>{option.period}</span>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DateSelect;
|
||||
38
src/components/Datepicker.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import Flatpickr from 'react-flatpickr';
|
||||
|
||||
function Datepicker({
|
||||
align
|
||||
}) {
|
||||
|
||||
const options = {
|
||||
mode: 'range',
|
||||
static: true,
|
||||
monthSelectorType: 'static',
|
||||
dateFormat: 'M j, Y',
|
||||
defaultDate: [new Date().setDate(new Date().getDate() - 6), new Date()],
|
||||
prevArrow: '<svg class="fill-current" width="7" height="11" viewBox="0 0 7 11"><path d="M5.4 10.8l1.4-1.4-4-4 4-4L5.4 0 0 5.4z" /></svg>',
|
||||
nextArrow: '<svg class="fill-current" width="7" height="11" viewBox="0 0 7 11"><path d="M1.4 10.8L0 9.4l4-4-4-4L1.4 0l5.4 5.4z" /></svg>',
|
||||
onReady: (selectedDates, dateStr, instance) => {
|
||||
instance.element.value = dateStr.replace('to', '-');
|
||||
const customClass = (align) ? align : '';
|
||||
instance.calendarContainer.classList.add(`flatpickr-${customClass}`);
|
||||
},
|
||||
onChange: (selectedDates, dateStr, instance) => {
|
||||
instance.element.value = dateStr.replace('to', '-');
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Flatpickr className="form-input pl-9 text-slate-500 hover:text-slate-600 font-medium focus:border-slate-300 w-60" options={options} />
|
||||
<div className="absolute inset-0 right-auto flex items-center pointer-events-none">
|
||||
<svg className="w-4 h-4 fill-current text-slate-500 ml-3" viewBox="0 0 16 16">
|
||||
<path d="M15 2h-2V0h-2v2H9V0H7v2H5V0H3v2H1a1 1 0 00-1 1v12a1 1 0 001 1h14a1 1 0 001-1V3a1 1 0 00-1-1zm-1 12H2V6h12v8z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Datepicker;
|
||||
113
src/components/DropdownClassic.jsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function DropdownClassic() {
|
||||
|
||||
const options = [
|
||||
{
|
||||
id: 0,
|
||||
period: 'Today'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
period: 'Last 7 Days'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
period: 'Last Month'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
period: 'Last 12 Months'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
period: 'All Time'
|
||||
}
|
||||
];
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [selected, setSelected] = useState(2);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex">
|
||||
<button
|
||||
ref={trigger}
|
||||
className="btn justify-between min-w-44 bg-white border-slate-200 hover:border-slate-300 text-slate-500 hover:text-slate-600"
|
||||
aria-label="Select date range"
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<span>{options[selected].period}</span>
|
||||
</span>
|
||||
<svg className="shrink-0 ml-1 fill-current text-slate-400" width="11" height="7" viewBox="0 0 11 7">
|
||||
<path d="M5.4 6.8L0 1.4 1.4 0l4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
<Transition
|
||||
show={dropdownOpen}
|
||||
tag="div"
|
||||
className="z-10 absolute top-full left-0 w-full bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1"
|
||||
enter="transition ease-out duration-100 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-100"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<div
|
||||
ref={dropdown}
|
||||
className="font-medium text-sm text-slate-600"
|
||||
onFocus={() => setDropdownOpen(true)}
|
||||
onBlur={() => setDropdownOpen(false)}
|
||||
>
|
||||
{
|
||||
options.map(option => {
|
||||
return (
|
||||
<button
|
||||
key={option.id}
|
||||
tabIndex="0"
|
||||
className={`flex items-center w-full hover:bg-slate-50 py-1 px-3 cursor-pointer ${option.id === selected && 'text-indigo-500'}`}
|
||||
onClick={() => { setSelected(option.id); setDropdownOpen(false); }}
|
||||
>
|
||||
<svg className={`shrink-0 mr-2 fill-current text-indigo-500 ${option.id !== selected && 'invisible'}`} width="12" height="9" viewBox="0 0 12 9">
|
||||
<path d="M10.28.28L3.989 6.575 1.695 4.28A1 1 0 00.28 5.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28.28z" />
|
||||
</svg>
|
||||
<span>{option.period}</span>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropdownClassic;
|
||||
75
src/components/DropdownEditMenu.jsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function DropdownEditMenu({
|
||||
children,
|
||||
align,
|
||||
...rest
|
||||
}) {
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div {...rest}>
|
||||
<button
|
||||
ref={trigger}
|
||||
className={`bg-white text-slate-400 hover:text-slate-500 rounded-full ${dropdownOpen && 'bg-slate-100 text-slate-500'}`}
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<span className="sr-only">Menu</span>
|
||||
<svg className="w-8 h-8 fill-current" viewBox="0 0 32 32">
|
||||
<circle cx="16" cy="16" r="2" />
|
||||
<circle cx="10" cy="16" r="2" />
|
||||
<circle cx="22" cy="16" r="2" />
|
||||
</svg>
|
||||
</button>
|
||||
<Transition
|
||||
show={dropdownOpen}
|
||||
tag="div"
|
||||
className={`origin-top-right z-10 absolute top-full min-w-36 bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1 ${align === 'right' ? 'right-0' : 'left-0'}`}
|
||||
enter="transition ease-out duration-200 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<ul
|
||||
ref={dropdown}
|
||||
onFocus={() => setDropdownOpen(true)}
|
||||
onBlur={() => setDropdownOpen(false)}
|
||||
>
|
||||
{children}
|
||||
</ul>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropdownEditMenu;
|
||||
115
src/components/DropdownFilter.jsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function DropdownFilter({
|
||||
align
|
||||
}) {
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex">
|
||||
<button
|
||||
ref={trigger}
|
||||
className="btn bg-white border-slate-200 hover:border-slate-300 text-slate-500 hover:text-slate-600"
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<span className="sr-only">Filter</span><wbr />
|
||||
<svg className="w-4 h-4 fill-current" viewBox="0 0 16 16">
|
||||
<path d="M9 15H7a1 1 0 010-2h2a1 1 0 010 2zM11 11H5a1 1 0 010-2h6a1 1 0 010 2zM13 7H3a1 1 0 010-2h10a1 1 0 010 2zM15 3H1a1 1 0 010-2h14a1 1 0 010 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
<Transition
|
||||
show={dropdownOpen}
|
||||
tag="div"
|
||||
className={`origin-top-right z-10 absolute top-full min-w-56 bg-white border border-slate-200 pt-1.5 rounded shadow-lg overflow-hidden mt-1 ${align === 'right' ? 'right-0' : 'left-0'}`}
|
||||
enter="transition ease-out duration-200 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<div ref={dropdown}>
|
||||
<div className="text-xs font-semibold text-slate-400 uppercase pt-1.5 pb-2 px-4">Filters</div>
|
||||
<ul className="mb-4">
|
||||
<li className="py-1 px-3">
|
||||
<label className="flex items-center">
|
||||
<input type="checkbox" className="form-checkbox" />
|
||||
<span className="text-sm font-medium ml-2">Direct VS Indirect</span>
|
||||
</label>
|
||||
</li>
|
||||
<li className="py-1 px-3">
|
||||
<label className="flex items-center">
|
||||
<input type="checkbox" className="form-checkbox" />
|
||||
<span className="text-sm font-medium ml-2">Real Time Value</span>
|
||||
</label>
|
||||
</li>
|
||||
<li className="py-1 px-3">
|
||||
<label className="flex items-center">
|
||||
<input type="checkbox" className="form-checkbox" />
|
||||
<span className="text-sm font-medium ml-2">Top Channels</span>
|
||||
</label>
|
||||
</li>
|
||||
<li className="py-1 px-3">
|
||||
<label className="flex items-center">
|
||||
<input type="checkbox" className="form-checkbox" />
|
||||
<span className="text-sm font-medium ml-2">Sales VS Refunds</span>
|
||||
</label>
|
||||
</li>
|
||||
<li className="py-1 px-3">
|
||||
<label className="flex items-center">
|
||||
<input type="checkbox" className="form-checkbox" />
|
||||
<span className="text-sm font-medium ml-2">Last Order</span>
|
||||
</label>
|
||||
</li>
|
||||
<li className="py-1 px-3">
|
||||
<label className="flex items-center">
|
||||
<input type="checkbox" className="form-checkbox" />
|
||||
<span className="text-sm font-medium ml-2">Total Spent</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="py-2 px-3 border-t border-slate-200 bg-slate-50">
|
||||
<ul className="flex items-center justify-between">
|
||||
<li>
|
||||
<button className="btn-xs bg-white border-slate-200 hover:border-slate-300 text-slate-500 hover:text-slate-600">Clear</button>
|
||||
</li>
|
||||
<li>
|
||||
<button className="btn-xs bg-indigo-500 hover:bg-indigo-600 text-white" onClick={() => setDropdownOpen(false)} onBlur={() => setDropdownOpen(false)}>Apply</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropdownFilter;
|
||||
109
src/components/DropdownFull.jsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function DropdownFull() {
|
||||
|
||||
const options = [
|
||||
{
|
||||
id: 0,
|
||||
period: 'Most Popular'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
period: 'Newest'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
period: 'Lowest Price'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
period: 'Highest Price'
|
||||
}
|
||||
];
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [selected, setSelected] = useState(0);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex w-full">
|
||||
<button
|
||||
ref={trigger}
|
||||
className="btn w-full justify-between min-w-44 bg-white border-slate-200 hover:border-slate-300 text-slate-500 hover:text-slate-600"
|
||||
aria-label="Select date range"
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<span>{options[selected].period}</span>
|
||||
</span>
|
||||
<svg className="shrink-0 ml-1 fill-current text-slate-400" width="11" height="7" viewBox="0 0 11 7">
|
||||
<path d="M5.4 6.8L0 1.4 1.4 0l4 4 4-4 1.4 1.4z" />
|
||||
</svg>
|
||||
</button>
|
||||
<Transition
|
||||
show={dropdownOpen}
|
||||
tag="div"
|
||||
className="z-10 absolute top-full left-0 w-full bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1"
|
||||
enter="transition ease-out duration-100 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-100"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<div
|
||||
ref={dropdown}
|
||||
className="font-medium text-sm text-slate-600 divide-y divide-slate-200"
|
||||
onFocus={() => setDropdownOpen(true)}
|
||||
onBlur={() => setDropdownOpen(false)}
|
||||
>
|
||||
{
|
||||
options.map(option => {
|
||||
return (
|
||||
<button
|
||||
key={option.id}
|
||||
tabIndex="0"
|
||||
className={`flex items-center justify-between w-full hover:bg-slate-50 py-2 px-3 cursor-pointer ${option.id === selected && 'text-indigo-500'}`}
|
||||
onClick={() => { setSelected(option.id); setDropdownOpen(false); }}
|
||||
>
|
||||
<span>{option.period}</span>
|
||||
<svg className={`shrink-0 mr-2 fill-current text-indigo-500 ${option.id !== selected && 'invisible'}`} width="12" height="9" viewBox="0 0 12 9">
|
||||
<path d="M10.28.28L3.989 6.575 1.695 4.28A1 1 0 00.28 5.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28.28z" />
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropdownFull;
|
||||
111
src/components/DropdownHelp.jsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function DropdownHelp({
|
||||
align
|
||||
}) {
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex">
|
||||
<button
|
||||
ref={trigger}
|
||||
className={`w-8 h-8 flex items-center justify-center bg-slate-100 hover:bg-slate-200 transition duration-150 rounded-full ${dropdownOpen && 'bg-slate-200'}`}
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<span className="sr-only">Need help?</span>
|
||||
<svg className="w-4 h-4" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path className="fill-current text-slate-500" d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<Transition
|
||||
className={`origin-top-right z-10 absolute top-full min-w-44 bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1 ${align === 'right' ? 'right-0' : 'left-0'}`}
|
||||
show={dropdownOpen}
|
||||
enter="transition ease-out duration-200 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<div
|
||||
ref={dropdown}
|
||||
onFocus={() => setDropdownOpen(true)}
|
||||
onBlur={() => setDropdownOpen(false)}
|
||||
>
|
||||
<div className="text-xs font-semibold text-slate-400 uppercase pt-1.5 pb-2 px-4">Need help?</div>
|
||||
<ul>
|
||||
<li>
|
||||
<Link
|
||||
className="font-medium text-sm text-indigo-500 hover:text-indigo-600 flex items-center py-1 px-3"
|
||||
to="#0"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
<svg className="w-3 h-3 fill-current text-indigo-300 shrink-0 mr-2" viewBox="0 0 12 12">
|
||||
<rect y="3" width="12" height="9" rx="1" />
|
||||
<path d="M2 0h8v2H2z" />
|
||||
</svg>
|
||||
<span>Documentation</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
className="font-medium text-sm text-indigo-500 hover:text-indigo-600 flex items-center py-1 px-3"
|
||||
to="#0"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
<svg className="w-3 h-3 fill-current text-indigo-300 shrink-0 mr-2" viewBox="0 0 12 12">
|
||||
<path d="M10.5 0h-9A1.5 1.5 0 000 1.5v9A1.5 1.5 0 001.5 12h9a1.5 1.5 0 001.5-1.5v-9A1.5 1.5 0 0010.5 0zM10 7L8.207 5.207l-3 3-1.414-1.414 3-3L5 2h5v5z" />
|
||||
</svg>
|
||||
<span>Support Site</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
className="font-medium text-sm text-indigo-500 hover:text-indigo-600 flex items-center py-1 px-3"
|
||||
to="#0"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
<svg className="w-3 h-3 fill-current text-indigo-300 shrink-0 mr-2" viewBox="0 0 12 12">
|
||||
<path d="M11.854.146a.5.5 0 00-.525-.116l-11 4a.5.5 0 00-.015.934l4.8 1.921 1.921 4.8A.5.5 0 007.5 12h.008a.5.5 0 00.462-.329l4-11a.5.5 0 00-.116-.525z" />
|
||||
</svg>
|
||||
<span>Contact us</span>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DropdownHelp;
|
||||
106
src/components/DropdownNotifications.jsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function DropdownNotifications({
|
||||
align
|
||||
}) {
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex">
|
||||
<button
|
||||
ref={trigger}
|
||||
className={`w-8 h-8 flex items-center justify-center bg-slate-100 hover:bg-slate-200 transition duration-150 rounded-full ${dropdownOpen && 'bg-slate-200'}`}
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<span className="sr-only">Notifications</span>
|
||||
<svg className="w-4 h-4" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path className="fill-current text-slate-500" d="M6.5 0C2.91 0 0 2.462 0 5.5c0 1.075.37 2.074 1 2.922V12l2.699-1.542A7.454 7.454 0 006.5 11c3.59 0 6.5-2.462 6.5-5.5S10.09 0 6.5 0z" />
|
||||
<path className="fill-current text-slate-400" d="M16 9.5c0-.987-.429-1.897-1.147-2.639C14.124 10.348 10.66 13 6.5 13c-.103 0-.202-.018-.305-.021C7.231 13.617 8.556 14 10 14c.449 0 .886-.04 1.307-.11L15 16v-4h-.012C15.627 11.285 16 10.425 16 9.5z" />
|
||||
</svg>
|
||||
<div className="absolute top-0 right-0 w-2.5 h-2.5 bg-rose-500 border-2 border-white rounded-full"></div>
|
||||
</button>
|
||||
|
||||
<Transition
|
||||
className={`origin-top-right z-10 absolute top-full -mr-48 sm:mr-0 min-w-80 bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1 ${align === 'right' ? 'right-0' : 'left-0'}`}
|
||||
show={dropdownOpen}
|
||||
enter="transition ease-out duration-200 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<div
|
||||
ref={dropdown}
|
||||
onFocus={() => setDropdownOpen(true)}
|
||||
onBlur={() => setDropdownOpen(false)}
|
||||
>
|
||||
<div className="text-xs font-semibold text-slate-400 uppercase pt-1.5 pb-2 px-4">Notifications</div>
|
||||
<ul>
|
||||
<li className="border-b border-slate-200 last:border-0">
|
||||
<Link
|
||||
className="block py-2 px-4 hover:bg-slate-50"
|
||||
to="#0"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
<span className="block text-sm mb-2">📣 <span className="font-medium text-slate-800">Edit your information in a swipe</span> Sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim.</span>
|
||||
<span className="block text-xs font-medium text-slate-400">Feb 12, 2021</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li className="border-b border-slate-200 last:border-0">
|
||||
<Link
|
||||
className="block py-2 px-4 hover:bg-slate-50"
|
||||
to="#0"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
<span className="block text-sm mb-2">📣 <span className="font-medium text-slate-800">Edit your information in a swipe</span> Sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim.</span>
|
||||
<span className="block text-xs font-medium text-slate-400">Feb 9, 2021</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li className="border-b border-slate-200 last:border-0">
|
||||
<Link
|
||||
className="block py-2 px-4 hover:bg-slate-50"
|
||||
to="#0"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
<span className="block text-sm mb-2">🚀<span className="font-medium text-slate-800">Say goodbye to paper receipts!</span> Sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim.</span>
|
||||
<span className="block text-xs font-medium text-slate-400">Jan 24, 2020</span>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DropdownNotifications;
|
||||
100
src/components/DropdownProfile.jsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
import UserAvatar from '../images/user-avatar-32.png';
|
||||
|
||||
function DropdownProfile({
|
||||
align
|
||||
}) {
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex">
|
||||
<button
|
||||
ref={trigger}
|
||||
className="inline-flex justify-center items-center group"
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<img className="w-8 h-8 rounded-full" src={UserAvatar} width="32" height="32" alt="User" />
|
||||
<div className="flex items-center truncate">
|
||||
<span className="truncate ml-2 text-sm font-medium group-hover:text-slate-800">Acme Inc.</span>
|
||||
<svg className="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<Transition
|
||||
className={`origin-top-right z-10 absolute top-full min-w-44 bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1 ${align === 'right' ? 'right-0' : 'left-0'}`}
|
||||
show={dropdownOpen}
|
||||
enter="transition ease-out duration-200 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<div
|
||||
ref={dropdown}
|
||||
onFocus={() => setDropdownOpen(true)}
|
||||
onBlur={() => setDropdownOpen(false)}
|
||||
>
|
||||
<div className="pt-0.5 pb-2 px-3 mb-1 border-b border-slate-200">
|
||||
<div className="font-medium text-slate-800">Acme Inc.</div>
|
||||
<div className="text-xs text-slate-500 italic">Administrator</div>
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
<Link
|
||||
className="font-medium text-sm text-indigo-500 hover:text-indigo-600 flex items-center py-1 px-3"
|
||||
to="/settings"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
Settings
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
className="font-medium text-sm text-indigo-500 hover:text-indigo-600 flex items-center py-1 px-3"
|
||||
to="/signin"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
Sign Out
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DropdownProfile;
|
||||
90
src/components/DropdownSort.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function DropdownSort({
|
||||
align
|
||||
}) {
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex">
|
||||
<button
|
||||
ref={trigger}
|
||||
className="inline-flex justify-center items-center group"
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<div className="flex items-center truncate">
|
||||
<span className="truncate font-medium text-indigo-500 group-hover:text-indigo-600">Newest</span>
|
||||
<svg className="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<Transition
|
||||
className={`origin-top-right z-10 absolute top-full bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1 ${
|
||||
align === 'right' ? 'right-0' : 'left-0'
|
||||
}`}
|
||||
show={dropdownOpen}
|
||||
enter="transition ease-out duration-200 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<div ref={dropdown} onFocus={() => setDropdownOpen(true)} onBlur={() => setDropdownOpen(false)}>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
className="font-medium text-sm text-slate-600 hover:text-slate-800 flex items-center py-1 px-3"
|
||||
href="#0"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
Oldest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="font-medium text-sm text-slate-600 hover:text-slate-800 flex items-center py-1 px-3"
|
||||
href="#0"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
Popular
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropdownSort;
|
||||
110
src/components/DropdownSwitch.jsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
import DropdownImage from '../images/user-avatar-32.png';
|
||||
import DropdownImage01 from '../images/channel-01.png';
|
||||
import DropdownImage02 from '../images/channel-02.png';
|
||||
import DropdownImage03 from '../images/channel-03.png';
|
||||
|
||||
function DropdownSwitch({
|
||||
align
|
||||
}) {
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
ref={trigger}
|
||||
className="grow flex items-center truncate"
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<img className="w-8 h-8 rounded-full mr-2" src={DropdownImage} width="32" height="32" alt="Group 01" />
|
||||
<div className="truncate">
|
||||
<span className="text-sm font-medium">Acme Inc.</span>
|
||||
</div>
|
||||
<svg className="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</button>
|
||||
<Transition
|
||||
className={`origin-top-right z-10 absolute top-full min-w-60 bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1 ${align === 'right' ? 'right-0' : 'left-0'}`}
|
||||
show={dropdownOpen}
|
||||
enter="transition ease-out duration-200 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<ul
|
||||
ref={dropdown}
|
||||
onFocus={() => setDropdownOpen(true)}
|
||||
onBlur={() => setDropdownOpen(false)}
|
||||
>
|
||||
<li>
|
||||
<a className="font-medium text-sm text-slate-600 hover:text-slate-800 block py-1.5 px-3" href="#0" onClick={() => setDropdownOpen(false)}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="grow flex items-center truncate">
|
||||
<img className="w-7 h-7 rounded-full mr-2" src={DropdownImage01} width="28" height="28" alt="Channel 01" />
|
||||
<div className="truncate">Acme Inc.</div>
|
||||
</div>
|
||||
<svg className="w-3 h-3 shrink-0 fill-current text-indigo-500 ml-1" viewBox="0 0 12 12">
|
||||
<path d="M10.28 1.28L3.989 7.575 1.695 5.28A1 1 0 00.28 6.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 1.28z" />
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="font-medium text-sm text-slate-600 hover:text-slate-800 block py-1.5 px-3" href="#0" onClick={() => setDropdownOpen(false)}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="grow flex items-center truncate">
|
||||
<img className="w-7 h-7 rounded-full mr-2" src={DropdownImage02} width="28" height="28" alt="Channel 02" />
|
||||
<div className="truncate">Acme Limited</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="font-medium text-sm text-slate-600 hover:text-slate-800 block py-1.5 px-3" href="#0" onClick={() => setDropdownOpen(false)}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="grow flex items-center truncate">
|
||||
<img className="w-7 h-7 rounded-full mr-2" src={DropdownImage03} width="28" height="28" alt="Channel 03" />
|
||||
<div className="truncate">Acme Srl</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DropdownSwitch;
|
||||
90
src/components/DropdownTransaction.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function DropdownTransaction({
|
||||
align
|
||||
}) {
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const trigger = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!dropdown.current) return;
|
||||
if (!dropdownOpen || dropdown.current.contains(target) || trigger.current.contains(target)) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!dropdownOpen || keyCode !== 27) return;
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex">
|
||||
<button
|
||||
ref={trigger}
|
||||
className="inline-flex justify-center items-center group"
|
||||
aria-haspopup="true"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
aria-expanded={dropdownOpen}
|
||||
>
|
||||
<div className="flex items-center truncate">
|
||||
<span className="truncate font-medium text-indigo-500 group-hover:text-indigo-600">My Personal Account</span>
|
||||
<svg className="w-3 h-3 shrink-0 ml-1 fill-current text-slate-400" viewBox="0 0 12 12">
|
||||
<path d="M5.9 11.4L.5 6l1.4-1.4 4 4 4-4L11.3 6z" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<Transition
|
||||
className={`origin-top-right z-10 absolute top-full min-w-44 bg-white border border-slate-200 py-1.5 rounded shadow-lg overflow-hidden mt-1 ${
|
||||
align === 'right' ? 'right-0' : 'left-0'
|
||||
}`}
|
||||
show={dropdownOpen}
|
||||
enter="transition ease-out duration-200 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
<div ref={dropdown} onFocus={() => setDropdownOpen(true)} onBlur={() => setDropdownOpen(false)}>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
className="font-medium text-sm text-slate-600 hover:text-slate-800 flex items-center py-1 px-3"
|
||||
href="#0"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
Business Account
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className="font-medium text-sm text-slate-600 hover:text-slate-800 flex items-center py-1 px-3"
|
||||
href="#0"
|
||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
||||
>
|
||||
Family Account
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropdownTransaction;
|
||||
80
src/components/ModalAction.jsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function ModalAction({
|
||||
children,
|
||||
id,
|
||||
modalOpen,
|
||||
setModalOpen
|
||||
}) {
|
||||
|
||||
const modalContent = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!modalOpen || modalContent.current.contains(target)) return
|
||||
setModalOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!modalOpen || keyCode !== 27) return;
|
||||
setModalOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modal backdrop */}
|
||||
<Transition
|
||||
className="fixed inset-0 bg-slate-900 bg-opacity-30 z-50 transition-opacity"
|
||||
show={modalOpen}
|
||||
enter="transition ease-out duration-200"
|
||||
enterStart="opacity-0"
|
||||
enterEnd="opacity-100"
|
||||
leave="transition ease-out duration-100"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{/* Modal dialog */}
|
||||
<Transition
|
||||
id={id}
|
||||
className="fixed inset-0 z-50 overflow-hidden flex items-center my-4 justify-center transform px-4 sm:px-6"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
show={modalOpen}
|
||||
enter="transition ease-in-out duration-200"
|
||||
enterStart="opacity-0 translate-y-4"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-in-out duration-200"
|
||||
leaveStart="opacity-100 translate-y-0"
|
||||
leaveEnd="opacity-0 translate-y-4"
|
||||
>
|
||||
<div ref={modalContent} className="bg-white rounded shadow-lg overflow-auto max-w-lg w-full max-h-full">
|
||||
<div className="p-6">
|
||||
<div className="relative">
|
||||
{/* Close button */}
|
||||
<button className="absolute top-0 right-0 text-slate-400 hover:text-slate-500" onClick={(e) => { e.stopPropagation(); setModalOpen(false); }}>
|
||||
<div className="sr-only">Close</div>
|
||||
<svg className="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalAction;
|
||||
82
src/components/ModalBasic.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function ModalBasic({
|
||||
children,
|
||||
id,
|
||||
title,
|
||||
modalOpen,
|
||||
setModalOpen
|
||||
}) {
|
||||
|
||||
const modalContent = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!modalOpen || modalContent.current.contains(target)) return
|
||||
setModalOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!modalOpen || keyCode !== 27) return;
|
||||
setModalOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modal backdrop */}
|
||||
<Transition
|
||||
className="fixed inset-0 bg-slate-900 bg-opacity-30 z-50 transition-opacity"
|
||||
show={modalOpen}
|
||||
enter="transition ease-out duration-200"
|
||||
enterStart="opacity-0"
|
||||
enterEnd="opacity-100"
|
||||
leave="transition ease-out duration-100"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{/* Modal dialog */}
|
||||
<Transition
|
||||
id={id}
|
||||
className="fixed inset-0 z-50 overflow-hidden flex items-center my-4 justify-center transform px-4 sm:px-6"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
show={modalOpen}
|
||||
enter="transition ease-in-out duration-200"
|
||||
enterStart="opacity-0 translate-y-4"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-in-out duration-200"
|
||||
leaveStart="opacity-100 translate-y-0"
|
||||
leaveEnd="opacity-0 translate-y-4"
|
||||
>
|
||||
<div ref={modalContent} className="bg-white rounded shadow-lg overflow-auto max-w-lg w-full max-h-full">
|
||||
{/* Modal header */}
|
||||
<div className="px-5 py-3 border-b border-slate-200">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="font-semibold text-slate-800">{title}</div>
|
||||
<button className="text-slate-400 hover:text-slate-500" onClick={(e) => { e.stopPropagation(); setModalOpen(false); }}>
|
||||
<div className="sr-only">Close</div>
|
||||
<svg className="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalBasic;
|
||||
69
src/components/ModalBlank.jsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function ModalBlank({
|
||||
children,
|
||||
id,
|
||||
modalOpen,
|
||||
setModalOpen
|
||||
}) {
|
||||
|
||||
const modalContent = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!modalOpen || modalContent.current.contains(target)) return
|
||||
setModalOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!modalOpen || keyCode !== 27) return;
|
||||
setModalOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modal backdrop */}
|
||||
<Transition
|
||||
className="fixed inset-0 bg-slate-900 bg-opacity-30 z-50 transition-opacity"
|
||||
show={modalOpen}
|
||||
enter="transition ease-out duration-200"
|
||||
enterStart="opacity-0"
|
||||
enterEnd="opacity-100"
|
||||
leave="transition ease-out duration-100"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{/* Modal dialog */}
|
||||
<Transition
|
||||
id={id}
|
||||
className="fixed inset-0 z-50 overflow-hidden flex items-center my-4 justify-center transform px-4 sm:px-6"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
show={modalOpen}
|
||||
enter="transition ease-in-out duration-200"
|
||||
enterStart="opacity-0 translate-y-4"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-in-out duration-200"
|
||||
leaveStart="opacity-100 translate-y-0"
|
||||
leaveEnd="opacity-0 translate-y-4"
|
||||
>
|
||||
<div ref={modalContent} className="bg-white rounded shadow-lg overflow-auto max-w-lg w-full max-h-full">
|
||||
{children}
|
||||
</div>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalBlank;
|
||||
84
src/components/ModalCookies.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function ModalCookies({
|
||||
children,
|
||||
id,
|
||||
title,
|
||||
modalOpen,
|
||||
setModalOpen
|
||||
}) {
|
||||
|
||||
const modalContent = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!modalOpen || modalContent.current.contains(target)) return
|
||||
setModalOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!modalOpen || keyCode !== 27) return;
|
||||
setModalOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modal backdrop */}
|
||||
<Transition
|
||||
className="fixed inset-0 bg-slate-900 bg-opacity-30 z-50 transition-opacity"
|
||||
show={modalOpen}
|
||||
enter="transition ease-out duration-200"
|
||||
enterStart="opacity-0"
|
||||
enterEnd="opacity-100"
|
||||
leave="transition ease-out duration-100"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{/* Modal dialog */}
|
||||
<Transition
|
||||
id={id}
|
||||
className="fixed inset-0 z-50 overflow-hidden flex items-center my-4 justify-center transform px-4 sm:px-6"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
show={modalOpen}
|
||||
enter="transition ease-in-out duration-200"
|
||||
enterStart="opacity-0 translate-y-4"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-in-out duration-200"
|
||||
leaveStart="opacity-100 translate-y-0"
|
||||
leaveEnd="opacity-0 translate-y-4"
|
||||
>
|
||||
<div ref={modalContent} className="bg-white rounded shadow-lg overflow-auto max-w-lg w-full max-h-full">
|
||||
<div className="p-5">
|
||||
{/* Modal header */}
|
||||
<div className="mb-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-lg font-semibold text-slate-800">{title}</div>
|
||||
<button className="text-slate-400 hover:text-slate-500" onClick={(e) => { e.stopPropagation(); setModalOpen(false); }}>
|
||||
<div className="sr-only">Close</div>
|
||||
<svg className="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalCookies;
|
||||
197
src/components/ModalSearch.jsx
Normal file
@@ -0,0 +1,197 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function ModalSearch({
|
||||
id,
|
||||
searchId,
|
||||
modalOpen,
|
||||
setModalOpen
|
||||
}) {
|
||||
|
||||
const modalContent = useRef(null);
|
||||
const searchInput = useRef(null);
|
||||
|
||||
// close on click outside
|
||||
useEffect(() => {
|
||||
const clickHandler = ({ target }) => {
|
||||
if (!modalOpen || modalContent.current.contains(target)) return
|
||||
setModalOpen(false);
|
||||
};
|
||||
document.addEventListener('click', clickHandler);
|
||||
return () => document.removeEventListener('click', clickHandler);
|
||||
});
|
||||
|
||||
// close if the esc key is pressed
|
||||
useEffect(() => {
|
||||
const keyHandler = ({ keyCode }) => {
|
||||
if (!modalOpen || keyCode !== 27) return;
|
||||
setModalOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', keyHandler);
|
||||
return () => document.removeEventListener('keydown', keyHandler);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
modalOpen && searchInput.current.focus();
|
||||
}, [modalOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modal backdrop */}
|
||||
<Transition
|
||||
className="fixed inset-0 bg-slate-900 bg-opacity-30 z-50 transition-opacity"
|
||||
show={modalOpen}
|
||||
enter="transition ease-out duration-200"
|
||||
enterStart="opacity-0"
|
||||
enterEnd="opacity-100"
|
||||
leave="transition ease-out duration-100"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{/* Modal dialog */}
|
||||
<Transition
|
||||
id={id}
|
||||
className="fixed inset-0 z-50 overflow-hidden flex items-start top-20 mb-4 justify-center transform px-4 sm:px-6"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
show={modalOpen}
|
||||
enter="transition ease-in-out duration-200"
|
||||
enterStart="opacity-0 translate-y-4"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-in-out duration-200"
|
||||
leaveStart="opacity-100 translate-y-0"
|
||||
leaveEnd="opacity-0 translate-y-4"
|
||||
>
|
||||
<div ref={modalContent} className="bg-white overflow-auto max-w-2xl w-full max-h-full rounded shadow-lg">
|
||||
{/* Search form */}
|
||||
<form className="border-b border-slate-200">
|
||||
<div className="relative">
|
||||
<label htmlFor={searchId} className="sr-only">Search</label>
|
||||
<input id={searchId} className="w-full border-0 focus:ring-transparent placeholder-slate-400 appearance-none py-3 pl-10 pr-4" type="search" placeholder="Search Anything…" ref={searchInput} />
|
||||
<button className="absolute inset-0 right-auto group" type="submit" aria-label="Search">
|
||||
<svg className="w-4 h-4 shrink-0 fill-current text-slate-400 group-hover:text-slate-500 ml-4 mr-2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 14c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7zM7 2C4.243 2 2 4.243 2 7s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5z" />
|
||||
<path d="M15.707 14.293L13.314 11.9a8.019 8.019 0 01-1.414 1.414l2.393 2.393a.997.997 0 001.414 0 .999.999 0 000-1.414z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="py-4 px-2">
|
||||
{/* Recent searches */}
|
||||
<div className="mb-3 last:mb-0">
|
||||
<div className="text-xs font-semibold text-slate-400 uppercase px-2 mb-2">Recent searches</div>
|
||||
<ul className="text-sm">
|
||||
<li>
|
||||
<Link
|
||||
className="flex items-center p-2 text-slate-800 hover:text-white hover:bg-indigo-500 rounded group"
|
||||
to="#0"
|
||||
onClick={() => setModalOpen(!modalOpen)}
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Form Builder - 23 hours on-demand video</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
className="flex items-center p-2 text-slate-800 hover:text-white hover:bg-indigo-500 rounded group"
|
||||
to="#0"
|
||||
onClick={() => setModalOpen(!modalOpen)}
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Access Mosaic on mobile and TV</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
className="flex items-center p-2 text-slate-800 hover:text-white hover:bg-indigo-500 rounded group"
|
||||
to="#0"
|
||||
onClick={() => setModalOpen(!modalOpen)}
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Product Update - Q4 2021</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
className="flex items-center p-2 text-slate-800 hover:text-white hover:bg-indigo-500 rounded group"
|
||||
to="#0"
|
||||
onClick={() => setModalOpen(!modalOpen)}
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Master Digital Marketing Strategy course</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
className="flex items-center p-2 text-slate-800 hover:text-white hover:bg-indigo-500 rounded group"
|
||||
to="#0"
|
||||
onClick={() => setModalOpen(!modalOpen)}
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Dedicated forms for products</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
className="flex items-center p-2 text-slate-800 hover:text-white hover:bg-indigo-500 rounded group"
|
||||
to="#0"
|
||||
onClick={() => setModalOpen(!modalOpen)}
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M15.707 14.293v.001a1 1 0 01-1.414 1.414L11.185 12.6A6.935 6.935 0 017 14a7.016 7.016 0 01-5.173-2.308l-1.537 1.3L0 8l4.873 1.12-1.521 1.285a4.971 4.971 0 008.59-2.835l1.979.454a6.971 6.971 0 01-1.321 3.157l3.107 3.112zM14 6L9.127 4.88l1.521-1.28a4.971 4.971 0 00-8.59 2.83L.084 5.976a6.977 6.977 0 0112.089-3.668l1.537-1.3L14 6z" />
|
||||
</svg>
|
||||
<span>Product Update - Q4 2021</span>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{/* Recent pages */}
|
||||
<div className="mb-3 last:mb-0">
|
||||
<div className="text-xs font-semibold text-slate-400 uppercase px-2 mb-2">Recent pages</div>
|
||||
<ul className="text-sm">
|
||||
<li>
|
||||
<Link
|
||||
className="flex items-center p-2 text-slate-800 hover:text-white hover:bg-indigo-500 rounded group"
|
||||
to="#0"
|
||||
onClick={() => setModalOpen(!modalOpen)}
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M14 0H2c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h8l5-5V1c0-.6-.4-1-1-1zM3 2h10v8H9v4H3V2z" />
|
||||
</svg>
|
||||
<span><span className="font-medium text-slate-800 group-hover:text-white">Messages</span> - Conversation / … / Mike Mills</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
className="flex items-center p-2 text-slate-800 hover:text-white hover:bg-indigo-500 rounded group"
|
||||
to="#0"
|
||||
onClick={() => setModalOpen(!modalOpen)}
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current text-slate-400 group-hover:text-white group-hover:text-opacity-50 shrink-0 mr-3" viewBox="0 0 16 16">
|
||||
<path d="M14 0H2c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h8l5-5V1c0-.6-.4-1-1-1zM3 2h10v8H9v4H3V2z" />
|
||||
</svg>
|
||||
<span><span className="font-medium text-slate-800 group-hover:text-white">Messages</span> - Conversation / … / Eva Patrick</span>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalSearch;
|
||||
69
src/components/Notification.jsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
|
||||
function Notification({
|
||||
children,
|
||||
className,
|
||||
type,
|
||||
open,
|
||||
setOpen
|
||||
}) {
|
||||
|
||||
const typeIcon = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current text-amber-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
);
|
||||
case 'error':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current text-rose-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
);
|
||||
case 'success':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current text-emerald-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current text-indigo-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{open &&
|
||||
<div className={className}>
|
||||
<div className="inline-flex flex-col max-w-lg px-4 py-2 rounded-sm text-sm bg-white shadow-lg border border-slate-200 text-slate-600">
|
||||
<div className="flex w-full justify-between items-start">
|
||||
<div className="flex">
|
||||
{typeIcon(type)}
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<button className="opacity-70 hover:opacity-80 ml-3 mt-[3px]" onClick={() => setOpen(false)}>
|
||||
<div className="sr-only">Close</div>
|
||||
<svg className="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-right mt-1">
|
||||
<a className="font-medium text-indigo-500 hover:text-indigo-600" href="#0">Action -></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Notification;
|
||||
23
src/components/PaginationClassic.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
function PaginationClassic() {
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
||||
<nav className="mb-4 sm:mb-0 sm:order-1" role="navigation" aria-label="Navigation">
|
||||
<ul className="flex justify-center">
|
||||
<li className="ml-3 first:ml-0">
|
||||
<a className="btn bg-white border-slate-200 text-slate-300 cursor-not-allowed" href="#0" disabled><- Previous</a>
|
||||
</li>
|
||||
<li className="ml-3 first:ml-0">
|
||||
<a className="btn bg-white border-slate-200 hover:border-slate-300 text-indigo-500" href="#0">Next -></a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="text-sm text-slate-500 text-center sm:text-left">
|
||||
Showing <span className="font-medium text-slate-600">1</span> to <span className="font-medium text-slate-600">10</span> of <span className="font-medium text-slate-600">467</span> results
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PaginationClassic;
|
||||
45
src/components/PaginationNumeric.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
|
||||
function PaginationNumeric() {
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<nav className="flex" role="navigation" aria-label="Navigation">
|
||||
<div className="mr-2">
|
||||
<span className="inline-flex items-center justify-center rounded leading-5 px-2.5 py-2 bg-white border border-slate-200 text-slate-300">
|
||||
<span className="sr-only">Previous</span><wbr />
|
||||
<svg className="h-4 w-4 fill-current" viewBox="0 0 16 16">
|
||||
<path d="M9.4 13.4l1.4-1.4-4-4 4-4-1.4-1.4L4 8z" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<ul className="inline-flex text-sm font-medium -space-x-px shadow-sm">
|
||||
<li>
|
||||
<span className="inline-flex items-center justify-center rounded-l leading-5 px-3.5 py-2 bg-white border border-slate-200 text-indigo-500">1</span>
|
||||
</li>
|
||||
<li>
|
||||
<a className="inline-flex items-center justify-center leading-5 px-3.5 py-2 bg-white hover:bg-indigo-500 border border-slate-200 text-slate-600 hover:text-white" href="#0">2</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="inline-flex items-center justify-center leading-5 px-3.5 py-2 bg-white hover:bg-indigo-500 border border-slate-200 text-slate-600 hover:text-white" href="#0">3</a>
|
||||
</li>
|
||||
<li>
|
||||
<span className="inline-flex items-center justify-center leading-5 px-3.5 py-2 bg-white border border-slate-200 text-slate-400">…</span>
|
||||
</li>
|
||||
<li>
|
||||
<a className="inline-flex items-center justify-center rounded-r leading-5 px-3.5 py-2 bg-white hover:bg-indigo-500 border border-slate-200 text-slate-600 hover:text-white" href="#0">9</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="ml-2">
|
||||
<a href="#0" className="inline-flex items-center justify-center rounded leading-5 px-2.5 py-2 bg-white hover:bg-indigo-500 border border-slate-200 text-slate-600 hover:text-white shadow-sm">
|
||||
<span className="sr-only">Next</span><wbr />
|
||||
<svg className="h-4 w-4 fill-current" viewBox="0 0 16 16">
|
||||
<path d="M6.6 13.4L5.2 12l4-4-4-4 1.4-1.4L12 8z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PaginationNumeric;
|
||||
37
src/components/PaginationNumeric2.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
|
||||
function PaginationNumeric2() {
|
||||
return (
|
||||
<div>
|
||||
<nav className="flex justify-between" role="navigation" aria-label="Navigation">
|
||||
<div className="flex-1 mr-2">
|
||||
<a className="btn bg-white border-slate-200 text-slate-300 cursor-not-allowed" href="#0" disabled><-<span className="hidden sm:inline"> Previous</span></a>
|
||||
</div>
|
||||
<div className="grow text-center">
|
||||
<ul className="inline-flex text-sm font-medium -space-x-px">
|
||||
<li>
|
||||
<span className="inline-flex items-center justify-center rounded-full leading-5 px-2 py-2 bg-white border border-slate-200 text-indigo-500 shadow-sm"><span className="w-5">1</span></span>
|
||||
</li>
|
||||
<li>
|
||||
<a className="inline-flex items-center justify-center leading-5 px-2 py-2 text-slate-600 hover:text-indigo-500 border border-transparent" href="#0"><span className="w-5">2</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="inline-flex items-center justify-center leading-5 px-2 py-2 text-slate-600 hover:text-indigo-500 border border-transparent" href="#0"><span className="w-5">3</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<span className="inline-flex items-center justify-center leading-5 px-2 py-2 text-slate-400">…</span>
|
||||
</li>
|
||||
<li>
|
||||
<a className="inline-flex items-center justify-center rounded-r leading-5 px-2 py-2 text-slate-600 hover:text-indigo-500 border border-transparent" href="#0"><span className="w-5">9</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex-1 text-right ml-2">
|
||||
<a className="btn bg-white border-slate-200 hover:border-slate-300 text-indigo-500" href="#0"><span className="hidden sm:inline">Next </span>-></a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PaginationNumeric2;
|
||||
79
src/components/Toast.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
|
||||
function Toast({
|
||||
children,
|
||||
className,
|
||||
type,
|
||||
open,
|
||||
setOpen
|
||||
}) {
|
||||
|
||||
const typeIcon = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
);
|
||||
case 'error':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
);
|
||||
case 'success':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const typeColor = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'bg-amber-500';
|
||||
case 'error':
|
||||
return 'bg-rose-500';
|
||||
case 'success':
|
||||
return 'bg-emerald-500';
|
||||
default:
|
||||
return 'bg-indigo-500';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{open &&
|
||||
<div className={className}>
|
||||
<div className={`inline-flex min-w-80 px-4 py-2 rounded-sm text-sm text-white ${typeColor(type)}`}>
|
||||
<div className="flex w-full justify-between items-start">
|
||||
<div className="flex">
|
||||
{typeIcon(type)}
|
||||
<div className="font-medium">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<button className="opacity-70 hover:opacity-80 ml-3 mt-[3px]" onClick={() => setOpen(false)}>
|
||||
<div className="sr-only">Close</div>
|
||||
<svg className="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Toast;
|
||||
79
src/components/Toast2.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
|
||||
function Toast2({
|
||||
children,
|
||||
className,
|
||||
type,
|
||||
open,
|
||||
setOpen
|
||||
}) {
|
||||
|
||||
const typeIcon = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
);
|
||||
case 'error':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
);
|
||||
case 'success':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const typeColor = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'bg-amber-100 border-amber-200 text-amber-600';
|
||||
case 'error':
|
||||
return 'bg-rose-100 border-rose-200 text-rose-600';
|
||||
case 'success':
|
||||
return 'bg-emerald-100 border-emerald-200 text-emerald-600';
|
||||
default:
|
||||
return 'bg-indigo-100 border-indigo-200 text-indigo-500';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{open &&
|
||||
<div className={className}>
|
||||
<div className={`inline-flex min-w-80 px-4 py-2 rounded-sm text-sm border ${typeColor(type)}`}>
|
||||
<div className="flex w-full justify-between items-start">
|
||||
<div className="flex">
|
||||
{typeIcon(type)}
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<button className="opacity-70 hover:opacity-80 ml-3 mt-[3px]" onClick={() => setOpen(false)}>
|
||||
<div className="sr-only">Close</div>
|
||||
<svg className="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Toast2;
|
||||
66
src/components/Toast3.jsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
|
||||
function Toast3({
|
||||
children,
|
||||
className,
|
||||
type,
|
||||
open,
|
||||
setOpen
|
||||
}) {
|
||||
|
||||
const typeIcon = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current text-amber-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
);
|
||||
case 'error':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current text-rose-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
);
|
||||
case 'success':
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current text-emerald-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<svg className="w-4 h-4 shrink-0 fill-current text-indigo-500 mt-[3px] mr-3" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{open &&
|
||||
<div className={className}>
|
||||
<div className="inline-flex min-w-80 px-4 py-2 rounded-sm text-sm bg-white shadow-lg border border-slate-200 text-slate-600">
|
||||
<div className="flex w-full justify-between items-start">
|
||||
<div className="flex">
|
||||
{typeIcon(type)}
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<button className="opacity-70 hover:opacity-80 ml-3 mt-[3px]" onClick={() => setOpen(false)}>
|
||||
<div className="sr-only">Close</div>
|
||||
<svg className="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Toast3;
|
||||
90
src/components/Tooltip.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React, { useState } from 'react';
|
||||
import Transition from '../utils/Transition';
|
||||
|
||||
function Tooltip({
|
||||
children,
|
||||
className,
|
||||
bg,
|
||||
size,
|
||||
position,
|
||||
}) {
|
||||
|
||||
const [tooltipOpen, setTooltipOpen] = useState(false);
|
||||
|
||||
const positionOuterClasses = (position) => {
|
||||
switch (position) {
|
||||
case 'right':
|
||||
return 'left-full top-1/2 transform -translate-y-1/2';
|
||||
case 'left':
|
||||
return 'right-full top-1/2 transform -translate-y-1/2';
|
||||
case 'bottom':
|
||||
return 'top-full left-1/2 transform -translate-x-1/2';
|
||||
default:
|
||||
return 'bottom-full left-1/2 transform -translate-x-1/2';
|
||||
}
|
||||
}
|
||||
|
||||
const sizeClasses = (size) => {
|
||||
switch (size) {
|
||||
case 'lg':
|
||||
return 'min-w-72 p-3';
|
||||
case 'md':
|
||||
return 'min-w-56 p-3';
|
||||
case 'sm':
|
||||
return 'min-w-44 p-2';
|
||||
default:
|
||||
return 'p-2';
|
||||
}
|
||||
};
|
||||
|
||||
const positionInnerClasses = (position) => {
|
||||
switch (position) {
|
||||
case 'right':
|
||||
return 'ml-2';
|
||||
case 'left':
|
||||
return 'mr-2';
|
||||
case 'bottom':
|
||||
return 'mt-2';
|
||||
default:
|
||||
return 'mb-2';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative ${className}`}
|
||||
onMouseEnter={() => setTooltipOpen(true)}
|
||||
onMouseLeave={() => setTooltipOpen(false)}
|
||||
onFocus={() => setTooltipOpen(true)}
|
||||
onBlur={() => setTooltipOpen(false)}
|
||||
>
|
||||
<button
|
||||
className="block"
|
||||
aria-haspopup="true"
|
||||
aria-expanded={tooltipOpen}
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<svg className="w-4 h-4 fill-current text-slate-400" viewBox="0 0 16 16">
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className={`z-10 absolute ${positionOuterClasses(position)}`}>
|
||||
<Transition
|
||||
show={tooltipOpen}
|
||||
tag="div"
|
||||
className={`rounded overflow-hidden ${bg === 'dark' ? 'bg-slate-800' : 'bg-white border border-slate-200 shadow-lg'} ${sizeClasses(size)} ${positionInnerClasses(position)}`}
|
||||
enter="transition ease-out duration-200 transform"
|
||||
enterStart="opacity-0 -translate-y-2"
|
||||
enterEnd="opacity-100 translate-y-0"
|
||||
leave="transition ease-out duration-200"
|
||||
leaveStart="opacity-100"
|
||||
leaveEnd="opacity-0"
|
||||
>
|
||||
{children}
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tooltip;
|
||||
228
src/css/additional-styles/flatpickr.scss
Normal file
@@ -0,0 +1,228 @@
|
||||
// Import base styles
|
||||
@import '~flatpickr/dist/flatpickr.min.css';
|
||||
|
||||
// Customise flatpickr
|
||||
$calendarPadding: 24px;
|
||||
$daySize: 36px;
|
||||
$daysWidth: $daySize*7;
|
||||
|
||||
@keyframes fpFadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -8px, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.flatpickr-calendar {
|
||||
border: inherit;
|
||||
@apply rounded shadow-lg border border-slate-200 left-1/2;
|
||||
margin-left: - ($daysWidth + $calendarPadding*2)*0.5;
|
||||
padding: $calendarPadding;
|
||||
width: $daysWidth + $calendarPadding*2;
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
.flatpickr-calendar {
|
||||
@apply left-0 right-auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.flatpickr-right.flatpickr-calendar {
|
||||
@apply right-0 left-auto;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.animate.open {
|
||||
animation: fpFadeInDown 200ms ease-out;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.static {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
}
|
||||
|
||||
.flatpickr-calendar.static.open {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.flatpickr-days {
|
||||
width: $daysWidth;
|
||||
}
|
||||
|
||||
.dayContainer {
|
||||
width: $daysWidth;
|
||||
min-width: $daysWidth;
|
||||
max-width: $daysWidth;
|
||||
}
|
||||
|
||||
.flatpickr-day {
|
||||
@apply bg-slate-50 text-sm font-medium text-slate-600;
|
||||
max-width: $daySize;
|
||||
height: $daySize;
|
||||
line-height: $daySize;
|
||||
}
|
||||
|
||||
.flatpickr-day,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.flatpickr-day,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay,
|
||||
.flatpickr-day.selected.startRange,
|
||||
.flatpickr-day.startRange.startRange,
|
||||
.flatpickr-day.endRange.startRange,
|
||||
.flatpickr-day.selected.endRange,
|
||||
.flatpickr-day.startRange.endRange,
|
||||
.flatpickr-day.endRange.endRange,
|
||||
.flatpickr-day.selected.startRange.endRange,
|
||||
.flatpickr-day.startRange.startRange.endRange,
|
||||
.flatpickr-day.endRange.startRange.endRange {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.flatpickr-day.flatpickr-disabled,
|
||||
.flatpickr-day.flatpickr-disabled:hover,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay,
|
||||
.flatpickr-day.notAllowed,
|
||||
.flatpickr-day.notAllowed.prevMonthDay,
|
||||
.flatpickr-day.notAllowed.nextMonthDay {
|
||||
@apply text-slate-400;
|
||||
}
|
||||
|
||||
.rangeMode .flatpickr-day {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.flatpickr-day.selected,
|
||||
.flatpickr-day.startRange,
|
||||
.flatpickr-day.endRange,
|
||||
.flatpickr-day.selected.inRange,
|
||||
.flatpickr-day.startRange.inRange,
|
||||
.flatpickr-day.endRange.inRange,
|
||||
.flatpickr-day.selected:focus,
|
||||
.flatpickr-day.startRange:focus,
|
||||
.flatpickr-day.endRange:focus,
|
||||
.flatpickr-day.selected:hover,
|
||||
.flatpickr-day.startRange:hover,
|
||||
.flatpickr-day.endRange:hover,
|
||||
.flatpickr-day.selected.prevMonthDay,
|
||||
.flatpickr-day.startRange.prevMonthDay,
|
||||
.flatpickr-day.endRange.prevMonthDay,
|
||||
.flatpickr-day.selected.nextMonthDay,
|
||||
.flatpickr-day.startRange.nextMonthDay,
|
||||
.flatpickr-day.endRange.nextMonthDay {
|
||||
@apply bg-indigo-500 text-indigo-50;
|
||||
}
|
||||
|
||||
.flatpickr-day.inRange,
|
||||
.flatpickr-day.prevMonthDay.inRange,
|
||||
.flatpickr-day.nextMonthDay.inRange,
|
||||
.flatpickr-day.today.inRange,
|
||||
.flatpickr-day.prevMonthDay.today.inRange,
|
||||
.flatpickr-day.nextMonthDay.today.inRange,
|
||||
.flatpickr-day:hover,
|
||||
.flatpickr-day.prevMonthDay:hover,
|
||||
.flatpickr-day.nextMonthDay:hover,
|
||||
.flatpickr-day:focus,
|
||||
.flatpickr-day.prevMonthDay:focus,
|
||||
.flatpickr-day.nextMonthDay:focus,
|
||||
.flatpickr-day.today:hover,
|
||||
.flatpickr-day.today:focus {
|
||||
@apply bg-indigo-400 text-indigo-50;
|
||||
}
|
||||
|
||||
.flatpickr-day.inRange,
|
||||
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),
|
||||
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),
|
||||
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.flatpickr-months {
|
||||
align-items: center;
|
||||
margin-top: -8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month,
|
||||
.flatpickr-months .flatpickr-next-month {
|
||||
position: static;
|
||||
height: auto;
|
||||
@apply text-slate-600;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month svg,
|
||||
.flatpickr-months .flatpickr-next-month svg {
|
||||
width: 7px;
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month:hover,
|
||||
.flatpickr-months .flatpickr-next-month:hover,
|
||||
.flatpickr-months .flatpickr-prev-month:hover svg,
|
||||
.flatpickr-months .flatpickr-next-month:hover svg {
|
||||
fill: inherit;
|
||||
@apply text-slate-400;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-prev-month {
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-next-month {
|
||||
margin-right: -10px;
|
||||
}
|
||||
|
||||
.flatpickr-months .flatpickr-month {
|
||||
@apply text-slate-800;
|
||||
height: auto;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.flatpickr-current-month {
|
||||
@apply text-sm font-medium;
|
||||
position: static;
|
||||
height: auto;
|
||||
width: auto;
|
||||
left: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.flatpickr-current-month span.cur-month {
|
||||
@apply font-medium m-0;
|
||||
}
|
||||
|
||||
.flatpickr-current-month span.cur-month:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.flatpickr-current-month input.cur-year {
|
||||
font-weight: inherit;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.numInputWrapper:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.numInputWrapper span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.flatpickr-weekday {
|
||||
@apply text-slate-400 font-medium text-xs;
|
||||
}
|
||||
|
||||
.flatpickr-calendar.arrowTop::before,
|
||||
.flatpickr-calendar.arrowTop::after {
|
||||
display: none;
|
||||
}
|
||||
55
src/css/additional-styles/range-slider.scss
Normal file
@@ -0,0 +1,55 @@
|
||||
// Range slider
|
||||
$range-thumb-size: 36px;
|
||||
|
||||
input[type=range] {
|
||||
appearance: none;
|
||||
background: #ccc;
|
||||
border-radius: 3px;
|
||||
height: 6px;
|
||||
margin-top: ($range-thumb-size - 6px) * 0.5;
|
||||
margin-bottom: ($range-thumb-size - 6px) * 0.5;
|
||||
--thumb-size: #{$range-thumb-size};
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background-color: #000;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: $range-thumb-size;
|
||||
width: $range-thumb-size;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
background-color: #000;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: $range-thumb-size;
|
||||
width: $range-thumb-size;
|
||||
}
|
||||
|
||||
&::-ms-thumb {
|
||||
background-color: #000;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: $range-thumb-size;
|
||||
width: $range-thumb-size;
|
||||
}
|
||||
|
||||
&::-moz-focus-outer {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
11
src/css/additional-styles/theme.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-multiselect,
|
||||
.form-select,
|
||||
.form-checkbox,
|
||||
.form-radio {
|
||||
|
||||
&:focus {
|
||||
@apply ring-0;
|
||||
}
|
||||
}
|
||||
44
src/css/additional-styles/toggle-switch.scss
Normal file
@@ -0,0 +1,44 @@
|
||||
// Switch element
|
||||
.form-switch {
|
||||
@apply relative select-none;
|
||||
width: 44px;
|
||||
|
||||
label {
|
||||
@apply block overflow-hidden cursor-pointer h-6 rounded-full;
|
||||
|
||||
> span:first-child {
|
||||
@apply absolute block rounded-full;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
right: 50%;
|
||||
transition: all .15s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
|
||||
&:checked {
|
||||
|
||||
+ label {
|
||||
@apply bg-indigo-500;
|
||||
|
||||
> span:first-child {
|
||||
left: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
|
||||
+ label {
|
||||
@apply cursor-not-allowed bg-slate-100 border border-slate-200;
|
||||
|
||||
> span:first-child {
|
||||
@apply bg-slate-400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
106
src/css/additional-styles/utility-patterns.scss
Normal file
@@ -0,0 +1,106 @@
|
||||
// Typography
|
||||
.h1 {
|
||||
@apply text-4xl font-extrabold tracking-tighter;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
@apply text-3xl font-extrabold tracking-tighter;
|
||||
}
|
||||
|
||||
.h3 {
|
||||
@apply text-3xl font-extrabold;
|
||||
}
|
||||
|
||||
.h4 {
|
||||
@apply text-2xl font-extrabold tracking-tight;
|
||||
}
|
||||
|
||||
@screen md {
|
||||
.h1 {
|
||||
@apply text-5xl;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
@apply text-4xl;
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons
|
||||
.btn,
|
||||
.btn-lg,
|
||||
.btn-sm,
|
||||
.btn-xs {
|
||||
@apply font-medium text-sm inline-flex items-center justify-center border border-transparent rounded leading-5 shadow-sm transition duration-150 ease-in-out;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply px-3 py-2;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
@apply px-4 py-3;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
@apply px-2 py-1;
|
||||
}
|
||||
|
||||
.btn-xs {
|
||||
@apply px-2 py-0.5;
|
||||
}
|
||||
|
||||
// Forms
|
||||
input[type="search"]::-webkit-search-decoration,
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-results-button,
|
||||
input[type="search"]::-webkit-search-results-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-multiselect,
|
||||
.form-select,
|
||||
.form-checkbox,
|
||||
.form-radio {
|
||||
@apply text-sm text-slate-800 bg-white border;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-multiselect,
|
||||
.form-select,
|
||||
.form-checkbox {
|
||||
@apply rounded;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-multiselect,
|
||||
.form-select {
|
||||
@apply leading-5 py-2 px-3 border-slate-200 hover:border-slate-300 focus:border-indigo-300 shadow-sm;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea {
|
||||
@apply placeholder-slate-400;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
@apply pr-10;
|
||||
}
|
||||
|
||||
.form-checkbox,
|
||||
.form-radio {
|
||||
@apply text-indigo-500 border border-slate-300;
|
||||
}
|
||||
|
||||
/* Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
13
src/css/style.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=fallback');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
// Additional styles
|
||||
@import 'additional-styles/utility-patterns.scss';
|
||||
@import 'additional-styles/range-slider.scss';
|
||||
@import 'additional-styles/toggle-switch.scss';
|
||||
@import 'additional-styles/flatpickr.scss';
|
||||
@import 'additional-styles/theme.scss';
|
||||
|
||||
@tailwind utilities;
|
||||
67
src/css/tailwind.config.js
Normal file
@@ -0,0 +1,67 @@
|
||||
const plugin = require('tailwindcss/plugin');
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{js,jsx,ts,tsx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
boxShadow: {
|
||||
DEFAULT: '0 1px 3px 0 rgba(0, 0, 0, 0.08), 0 1px 2px 0 rgba(0, 0, 0, 0.02)',
|
||||
md: '0 4px 6px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -1px rgba(0, 0, 0, 0.02)',
|
||||
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.01)',
|
||||
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 10px 10px -5px rgba(0, 0, 0, 0.01)',
|
||||
},
|
||||
outline: {
|
||||
blue: '2px solid rgba(0, 112, 244, 0.5)',
|
||||
},
|
||||
fontFamily: {
|
||||
inter: ['Inter', 'sans-serif'],
|
||||
},
|
||||
fontSize: {
|
||||
xs: ['0.75rem', { lineHeight: '1.5' }],
|
||||
sm: ['0.875rem', { lineHeight: '1.5715' }],
|
||||
base: ['1rem', { lineHeight: '1.5', letterSpacing: '-0.01em' }],
|
||||
lg: ['1.125rem', { lineHeight: '1.5', letterSpacing: '-0.01em' }],
|
||||
xl: ['1.25rem', { lineHeight: '1.5', letterSpacing: '-0.01em' }],
|
||||
'2xl': ['1.5rem', { lineHeight: '1.33', letterSpacing: '-0.01em' }],
|
||||
'3xl': ['1.88rem', { lineHeight: '1.33', letterSpacing: '-0.01em' }],
|
||||
'4xl': ['2.25rem', { lineHeight: '1.25', letterSpacing: '-0.02em' }],
|
||||
'5xl': ['3rem', { lineHeight: '1.25', letterSpacing: '-0.02em' }],
|
||||
'6xl': ['3.75rem', { lineHeight: '1.2', letterSpacing: '-0.02em' }],
|
||||
},
|
||||
screens: {
|
||||
xs: '480px',
|
||||
},
|
||||
borderWidth: {
|
||||
3: '3px',
|
||||
},
|
||||
minWidth: {
|
||||
36: '9rem',
|
||||
44: '11rem',
|
||||
56: '14rem',
|
||||
60: '15rem',
|
||||
72: '18rem',
|
||||
80: '20rem',
|
||||
},
|
||||
maxWidth: {
|
||||
'8xl': '88rem',
|
||||
'9xl': '96rem',
|
||||
},
|
||||
zIndex: {
|
||||
60: '60',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
// eslint-disable-next-line global-require
|
||||
require('@tailwindcss/forms'),
|
||||
// add custom variant for expanding sidebar
|
||||
plugin(({ addVariant, e }) => {
|
||||
addVariant('sidebar-expanded', ({ modifySelectors, separator }) => {
|
||||
modifySelectors(({ className }) => `.sidebar-expanded .${e(`sidebar-expanded${separator}${className}`)}`);
|
||||
});
|
||||
}),
|
||||
],
|
||||
};
|
||||
15
src/favicon.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#41D1FF"/>
|
||||
<stop offset="1" stop-color="#BD34FE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFEA83"/>
|
||||
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
||||
<stop offset="1" stop-color="#FFA800"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
src/images/404-illustration.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="176" height="176" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" x="0" y="0" width="176" height="176" rx="88"/><rect id="e" x="142" y="211" width="118" height="118" rx="4"/><rect id="h" x="142" y="69" width="118" height="118" rx="4"/><rect id="j" x="0" y="0" width="118" height="118" rx="4"/><rect id="l" x="0" y="142" width="118" height="148" rx="4"/><linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="b"><stop stop-color="#F3F5F9" offset="0%"/><stop stop-color="#E2E8F0" offset="100%"/></linearGradient><linearGradient x1="50%" y1="100%" x2="50%" y2="30.355%" id="f"><stop stop-color="#F8FAFC" offset="0%"/><stop stop-color="#FFF" offset="100%"/></linearGradient><linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="m"><stop stop-color="#A5B4FC" offset="0%"/><stop stop-color="#818CF8" offset="100%"/></linearGradient><linearGradient x1="50%" y1="7.522%" x2="50%" y2="100%" id="n"><stop stop-color="#4338CA" offset="0%"/><stop stop-color="#6366F1" stop-opacity="0" offset="100%"/></linearGradient><filter x="-5.9%" y="-4.2%" width="111.9%" height="111.9%" filterUnits="objectBoundingBox" id="d"><feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0588235294 0 0 0 0 0.0901960784 0 0 0 0 0.164705882 0 0 0 0.06 0" in="shadowBlurOuter1"/></filter><filter x="-5.9%" y="-4.2%" width="111.9%" height="111.9%" filterUnits="objectBoundingBox" id="g"><feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0588235294 0 0 0 0 0.0901960784 0 0 0 0 0.164705882 0 0 0 0.06 0" in="shadowBlurOuter1"/></filter><filter x="-5.9%" y="-4.2%" width="111.9%" height="111.9%" filterUnits="objectBoundingBox" id="i"><feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0588235294 0 0 0 0 0.0901960784 0 0 0 0 0.164705882 0 0 0 0.06 0" in="shadowBlurOuter1"/></filter><filter x="-5.9%" y="-3.4%" width="111.9%" height="109.5%" filterUnits="objectBoundingBox" id="k"><feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.0588235294 0 0 0 0 0.0901960784 0 0 0 0 0.164705882 0 0 0 0.06 0" in="shadowBlurOuter1"/></filter></defs><g fill="none" fill-rule="evenodd"><mask id="c" fill="#fff"><use xlink:href="#a"/></mask><use fill="url(#b)" xlink:href="#a"/><g mask="url(#c)"><g transform="rotate(-20 -150.722 164.706)"><use fill="#000" filter="url(#d)" xlink:href="#e"/><rect stroke-opacity=".8" stroke="#CBD5E1" stroke-linejoin="square" fill="url(#f)" x="142.5" y="211.5" width="117" height="117" rx="4"/></g><g transform="rotate(-20 -150.722 164.706)"><use fill="#000" filter="url(#g)" xlink:href="#h"/><rect stroke-opacity=".8" stroke="#CBD5E1" stroke-linejoin="square" fill="url(#f)" x="142.5" y="69.5" width="117" height="117" rx="4"/></g><g transform="rotate(-20 -150.722 164.706)"><use fill="#000" filter="url(#i)" xlink:href="#j"/><rect stroke-opacity=".8" stroke="#CBD5E1" stroke-linejoin="square" fill="url(#f)" x=".5" y=".5" width="117" height="117" rx="4"/></g><g transform="rotate(-20 -150.722 164.706)"><use fill="#000" filter="url(#k)" xlink:href="#l"/><rect stroke-opacity=".8" stroke="#CBD5E1" stroke-linejoin="square" fill="url(#f)" x=".5" y="142.5" width="117" height="147" rx="4"/></g></g><g mask="url(#c)"><path fill="url(#m)" d="M43.103 0l42.97 86.267-42.97-13.48L.132 86.267z" transform="rotate(-51 93.327 26.607)"/><path fill="url(#n)" d="M85.694 85.504h-2.052l-40.54-12.717-.445.14V.895L43.103 0z" transform="rotate(-51 93.327 26.607)"/><path d="M115.34 104.24c10.516 7.682 22.413 13.456 34.883 17.051.683.203.858-.412.185-.628-6.138-1.912-11.97-4.83-17.498-8.092-5.542-3.269-10.787-6.87-16.107-10.326-2.145-1.415-3.683.349-1.463 1.995M113.053 118.196c19.255 21.87 38.637 43.728 57.93 65.59 1.058 1.199 1.536.775.49-.433-18.992-21.94-36.863-45.466-56.567-66.794-4-4.33-5.926-2.988-1.853 1.637" fill-opacity=".4" fill="#475569"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
34
src/images/announcement-icon.svg
Normal file
@@ -0,0 +1,34 @@
|
||||
<svg width="80" height="80" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="b">
|
||||
<stop stop-color="#F3F5F9" offset="0%" />
|
||||
<stop stop-color="#E2E8F0" offset="100%" />
|
||||
</linearGradient>
|
||||
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="e">
|
||||
<stop stop-color="#A5B4FC" offset="0%" />
|
||||
<stop stop-color="#818CF8" offset="100%" />
|
||||
</linearGradient>
|
||||
<linearGradient x1="50%" y1="24.537%" x2="50%" y2="100%" id="f">
|
||||
<stop stop-color="#4338CA" offset="0%" />
|
||||
<stop stop-color="#6366F1" stop-opacity="0" offset="100%" />
|
||||
</linearGradient>
|
||||
<rect id="a" x="0" y="0" width="80" height="80" rx="40" />
|
||||
<path id="d" d="M21.552 0l21.486 43.134-21.486-6.74-21.486 6.74z" />
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<mask id="c" fill="#fff">
|
||||
<use xlink:href="#a" />
|
||||
</mask>
|
||||
<use fill="url(#b)" xlink:href="#a" />
|
||||
<g mask="url(#c)">
|
||||
<g transform="rotate(-51 40.768 12.852)">
|
||||
<mask id="g" fill="#fff">
|
||||
<use xlink:href="#d" />
|
||||
</mask>
|
||||
<use fill="url(#e)" xlink:href="#d" />
|
||||
<path fill="url(#f)" mask="url(#g)" d="M21.329-8.887h27.236v51.639H21.329z" />
|
||||
</g>
|
||||
<path d="M55.837 47.371c5.258 3.84 11.206 6.728 17.441 8.526.342.1.43-.207.093-.314-3.07-.956-5.985-2.416-8.75-4.047-2.77-1.634-5.393-3.434-8.053-5.163-1.072-.707-1.842.175-.731.998M54.693 54.349c9.628 10.935 19.319 21.864 28.966 32.796.529.599.767.387.244-.217-9.496-10.97-18.431-22.733-28.284-33.397-2-2.166-2.962-1.495-.926.818" fill-opacity=".4" fill="#475569" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/images/applications-image-01.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src/images/applications-image-02.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/images/applications-image-03.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/images/applications-image-04.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/images/applications-image-05.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/images/applications-image-06.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
src/images/applications-image-07.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/images/applications-image-08.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/images/applications-image-09.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/images/applications-image-10.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/images/applications-image-11.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/images/applications-image-12.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
src/images/applications-image-13.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
src/images/applications-image-14.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/images/applications-image-15.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/images/applications-image-16.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/images/applications-image-17.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/images/applications-image-18.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src/images/applications-image-19.jpg
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
src/images/applications-image-20.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/images/applications-image-21.jpg
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/images/applications-image-22.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src/images/applications-image-23.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
src/images/applications-image-24.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
src/images/applications-image-25.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/images/applications-image-26.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/images/applications-image-27.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/images/applications-image-28.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/images/applications-image-29.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/images/applications-image-30.jpg
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
src/images/applications-image-31.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/images/applications-image-32.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/images/auth-decoration.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/images/auth-image.jpg
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
src/images/avatar-01.jpg
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src/images/avatar-02.jpg
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/images/avatar-03.jpg
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/images/avatar-04.jpg
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/images/avatar-05.jpg
Normal file
|
After Width: | Height: | Size: 3.5 KiB |