@adminlte/react 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +6 -4
- package/dist/index.d.ts +84 -9
- package/dist/index.js +7 -3
- package/dist/lib/chart-theme.js +120 -0
- package/dist/widget/chart.js +125 -0
- package/dist/widget/sparkline-chart.js +47 -36
- package/package.json +6 -5
- package/dist/widget/apex-chart.js +0 -40
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,66 @@ All notable changes to **@adminlte/react** are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.0] - 2026-10-01
|
|
9
|
+
|
|
10
|
+
### Changed — breaking
|
|
11
|
+
|
|
12
|
+
- **Charts now use [Chart.js](https://www.chartjs.org/) 4.5 (MIT) instead of ApexCharts.** From 5.2.0 ApexCharts is distributed under the proprietary *ApexCharts License*, which requires an OEM licence for any product that redistributes it — which is what a template library does. Chart.js is MIT, like everything else this package depends on.
|
|
13
|
+
- **`ApexChart` is removed; use the new `Chart` component.** It takes a Chart.js `type`, `data` and `options` (plus optional `plugins`, `height` — default `300` px —, `ariaLabel`, `fallback`, `className`, `style`). `data`/`options` are plain values, so a Server Component can pass them (callbacks such as tooltip formatters need a client component, as before).
|
|
14
|
+
- **`SparklineChart` keeps its props** (`id`, `data`) and gains optional `color` (default `#DCE6EC`), `type` (`'line' | 'bar'`), `height` (default `50`) and `ariaLabel`.
|
|
15
|
+
- **Peer dependency `apexcharts` → `chart.js` (`>=4.5.0`, optional).** Install `chart.js` if you use `Chart`/`SparklineChart`; drop `apexcharts` and its `apexcharts.css` link — Chart.js needs no stylesheet.
|
|
16
|
+
- Why 0.7.0 and not 1.0.0: the package is pre-1.0, where the minor version is the breaking-change channel (a `^0.6.x` range will not pick up 0.7.0), and nothing else in the public API changed.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **AdminLTE theme preset for Chart.js** — `applyChartTheme(Chart)`, with `readChartThemeColors()` and `withAlpha()` helpers, exported from the package and applied automatically by `Chart`. It writes `Chart.defaults` from Bootstrap's CSS variables (`--bs-body-font-family`, `--bs-body-color`, `--bs-secondary-color`, `--bs-border-color-translucent`, `--bs-body-bg`, `--bs-emphasis-color`): horizontal gridlines only, no axis borders, rounded bars, smooth lines, circular point-style legends and Bootstrap-style tooltips. A filled line dataset without its own `backgroundColor` gets a vertical gradient of its line colour. Call it yourself if you create Chart.js charts outside the component.
|
|
21
|
+
- **Live dark mode and RTL.** Mounted charts re-read the theme and re-render when `<html data-bs-theme>` or `<html dir>` changes (one shared `MutationObserver`), so the colour-mode toggle restyles axes, legends and tooltips immediately; with `dir="rtl"` legends and tooltips render right-to-left.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Docs → Plugins listed the jsVectorMap stylesheet as `jsvectormap/dist/css/jsvectormap.min.css`, a path that does not exist in jsvectormap 1.7; it is `jsvectormap/dist/jsvectormap.min.css`.
|
|
26
|
+
- Dashboard v2/v3 charts were hard to read in dark mode (dark tick labels and light row stripes on a dark card); the preset now takes its colours from the active colour mode.
|
|
27
|
+
|
|
28
|
+
### Migration
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
// 0.6.x
|
|
32
|
+
<ApexChart
|
|
33
|
+
id="sales"
|
|
34
|
+
series={[{ name: 'Sales', data: [30, 40, 35, 50] }]}
|
|
35
|
+
config={{ chart: { type: 'area', height: 300 }, colors: ['#0d6efd'], xaxis: { categories: ['Jan', 'Feb', 'Mar', 'Apr'] } }}
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
// 0.7.0
|
|
39
|
+
<Chart
|
|
40
|
+
id="sales"
|
|
41
|
+
type="line"
|
|
42
|
+
height={300}
|
|
43
|
+
data={{
|
|
44
|
+
labels: ['Jan', 'Feb', 'Mar', 'Apr'],
|
|
45
|
+
datasets: [{ label: 'Sales', data: [30, 40, 35, 50], borderColor: '#0d6efd', fill: 'origin' }],
|
|
46
|
+
}}
|
|
47
|
+
/>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| ApexCharts | Chart.js |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `chart.type: 'area'` | `type="line"` + dataset `fill: 'origin'` |
|
|
53
|
+
| `chart.type: 'bar'` (`plotOptions.bar.horizontal`) | `type="bar"` (`options.indexAxis: 'y'`) |
|
|
54
|
+
| `chart.type: 'donut'` / `'pie'` / `'radar'` / `'polarArea'` | `type="doughnut"` / `"pie"` / `"radar"` / `"polarArea"` |
|
|
55
|
+
| `series[i].name` / `.data` | `data.datasets[i].label` / `.data` |
|
|
56
|
+
| `xaxis.categories` / `labels` | `data.labels` |
|
|
57
|
+
| `colors` | per-dataset `borderColor` / `backgroundColor` |
|
|
58
|
+
| `chart.height` | `height` prop |
|
|
59
|
+
| `legend.show: false` | `options.plugins.legend.display: false` |
|
|
60
|
+
| `tooltip.y.formatter` | `options.plugins.tooltip.callbacks.label` |
|
|
61
|
+
|
|
62
|
+
### Notes
|
|
63
|
+
|
|
64
|
+
- The demo's chart code shrank: the lazily-loaded chart chunk is **68.3 kB gzipped (Chart.js) vs 275.0 kB (ApexCharts)**; all demo JS 587.8 kB vs 789.1 kB gzipped.
|
|
65
|
+
- Every chart on the three dashboards was re-created with the same data and series: Dashboard v1 area chart + 3 sparklines, Dashboard v2 area chart, donut and 7 table sparklines, Dashboard v3 line and grouped bar charts.
|
|
66
|
+
- Verified: library type-check, lint (0 errors, 13 pre-existing warnings), 23 unit tests (4 new for the theme preset) and build (35 client modules in `src/` and `dist/`); demo type-check, production build and the `EXPORT=true` subpath export; all 67 Playwright route-smoke + a11y tests; and a Playwright crawl of all 64 exported routes at `/themes/next-react/` in light and dark mode, plus the chart pages in RTL — no console or page errors, every chart canvas painted.
|
|
67
|
+
|
|
8
68
|
## [0.6.1] - 2026-08-19
|
|
9
69
|
|
|
10
70
|
### Fixed
|
|
@@ -240,6 +300,7 @@ Next.js App Router and React Server Components.
|
|
|
240
300
|
- Heavy third-party libraries are never bundled eagerly — they load via dynamic
|
|
241
301
|
`import()` inside the components that use them.
|
|
242
302
|
|
|
303
|
+
[0.7.0]: https://github.com/ColorlibHQ/adminlte-react/compare/v0.6.1...v0.7.0
|
|
243
304
|
[0.6.1]: https://github.com/ColorlibHQ/adminlte-react/compare/v0.6.0...v0.6.1
|
|
244
305
|
[0.6.0]: https://github.com/ColorlibHQ/adminlte-react/compare/v0.5.0...v0.6.0
|
|
245
306
|
[0.5.0]: https://github.com/ColorlibHQ/adminlte-react/compare/v0.4.0...v0.5.0
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
A modern React/Next.js component library for building admin dashboards and web applications with AdminLTE 4 styling and Bootstrap 5.3 — built for the Next.js App Router and React Server Components by [Colorlib](https://colorlib.com).
|
|
11
11
|
|
|
12
|
-
**Status**: v0.
|
|
12
|
+
**Status**: v0.7.0 (Early Release, AdminLTE core 4.8.1) · **Live demo:** [adminlte.io/themes/next-react](https://adminlte.io/themes/next-react/)
|
|
13
13
|
|
|
14
14
|
<p align="center">
|
|
15
15
|
<a href="https://adminlte.io/themes/next-react/">
|
|
@@ -58,7 +58,7 @@ A modern React/Next.js component library for building admin dashboards and web a
|
|
|
58
58
|
- ♿ **Accessible** - Semantic markup, ARIA landmarks, keyboard navigation
|
|
59
59
|
- 🎯 **TypeScript First** - Fully typed props, hooks, and the menu data model
|
|
60
60
|
- 📱 **Responsive** - Mobile-first Bootstrap 5.3 design with an off-canvas sidebar
|
|
61
|
-
- 🔌 **Lazy plugins** -
|
|
61
|
+
- 🔌 **Lazy plugins** - Chart.js, Tabulator, Quill, Flatpickr, Tom Select & jsVectorMap load on demand
|
|
62
62
|
|
|
63
63
|
## Installation
|
|
64
64
|
|
|
@@ -89,7 +89,7 @@ npm install bootstrap bootstrap-icons
|
|
|
89
89
|
Install only the libraries for the lazy components you use:
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
npm install
|
|
92
|
+
npm install chart.js # <Chart>, <SparklineChart>
|
|
93
93
|
npm install tabulator-tables # <Datatable>
|
|
94
94
|
npm install quill # <Editor>
|
|
95
95
|
npm install flatpickr # <InputFlatpickr>
|
|
@@ -250,7 +250,7 @@ export default function Dashboard() {
|
|
|
250
250
|
- **`Timeline`** - Chronological timeline
|
|
251
251
|
- **`NavMessages`** / **`NavNotifications`** / **`NavTasks`** - Topbar dropdown widgets
|
|
252
252
|
- **`DirectChat`** - Chat widget with contacts pane
|
|
253
|
-
- **`
|
|
253
|
+
- **`Chart`** / **`SparklineChart`** - [Chart.js](https://www.chartjs.org/) charts (MIT, dynamic import) with the AdminLTE theme preset (`applyChartTheme`) applied — follows dark mode and RTL live
|
|
254
254
|
- **`WorldMap`** - jsVectorMap world map (dynamic import)
|
|
255
255
|
- **`CommandPalette`** - ⌘K searchable navigator (+ `flattenMenuToCommands` helper)
|
|
256
256
|
- **`Accordion`** / **`Tabs`** - Collapsible panels & tab/pill switcher (state-driven, no Bootstrap JS)
|
|
@@ -471,6 +471,8 @@ Need advanced features, more pages, and dedicated support? Explore Colorlib's co
|
|
|
471
471
|
|
|
472
472
|
MIT © [Colorlib](https://colorlib.com)
|
|
473
473
|
|
|
474
|
+
Charts are drawn with [Chart.js](https://www.chartjs.org/) (MIT licence), an optional peer dependency you install yourself — every chart-related dependency of this package is MIT-licensed.
|
|
475
|
+
|
|
474
476
|
## Resources
|
|
475
477
|
|
|
476
478
|
- [AdminLTE Documentation](https://adminlte.io)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import react__default, { AnchorHTMLAttributes, ReactNode, Key } from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import react__default, { AnchorHTMLAttributes, ReactNode, CSSProperties, Key } from 'react';
|
|
3
|
+
import { ChartType, DefaultDataPoint, ChartData, ChartOptions, Plugin, Chart as Chart$1 } from 'chart.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Bootstrap color theme variants.
|
|
@@ -457,18 +457,93 @@ interface DirectChatProps {
|
|
|
457
457
|
}
|
|
458
458
|
declare function DirectChat({ badge, contacts, messages, collapsible, removable, onRemove, className, }: DirectChatProps): react.JSX.Element | null;
|
|
459
459
|
|
|
460
|
-
interface
|
|
461
|
-
id
|
|
462
|
-
|
|
463
|
-
|
|
460
|
+
interface ChartProps<TType extends ChartType = ChartType, TData = DefaultDataPoint<TType>, TLabel = unknown> {
|
|
461
|
+
/** DOM id of the chart container. */
|
|
462
|
+
id?: string;
|
|
463
|
+
/** Chart.js chart type: `line`, `bar`, `doughnut`, `pie`, `radar`, `polarArea`, `scatter`, `bubble`… */
|
|
464
|
+
type: TType;
|
|
465
|
+
/** Chart.js `data` (labels + datasets). Plain data, so it can come from a Server Component. */
|
|
466
|
+
data: ChartData<TType, TData, TLabel>;
|
|
467
|
+
/** Chart.js `options`. Merged over the AdminLTE theme preset (`Chart.defaults`). */
|
|
468
|
+
options?: ChartOptions<TType>;
|
|
469
|
+
/** Inline Chart.js plugins for this chart only. */
|
|
470
|
+
plugins?: Plugin<TType>[];
|
|
471
|
+
/** Height of the chart area — a number is pixels. Default `300`. */
|
|
472
|
+
height?: number | string;
|
|
473
|
+
/** Accessible name for the chart (`aria-label` on the canvas). */
|
|
474
|
+
ariaLabel?: string;
|
|
475
|
+
/** Fallback content inside `<canvas>` (e.g. a data table for screen readers). */
|
|
476
|
+
fallback?: ReactNode;
|
|
477
|
+
className?: string;
|
|
478
|
+
style?: CSSProperties;
|
|
464
479
|
}
|
|
465
|
-
|
|
480
|
+
/**
|
|
481
|
+
* Chart.js chart, lazy-loaded. `chart.js` is an optional peer dependency — install it
|
|
482
|
+
* (`npm install chart.js`); it is imported on mount, so pages without charts never load it.
|
|
483
|
+
*
|
|
484
|
+
* The AdminLTE theme preset (`applyChartTheme`) is applied automatically and follows the
|
|
485
|
+
* colour mode and document direction live. Charts resize with their container and are
|
|
486
|
+
* destroyed on unmount.
|
|
487
|
+
*/
|
|
488
|
+
declare function Chart<TType extends ChartType = ChartType, TData = DefaultDataPoint<TType>, TLabel = unknown>({ id, type, data, options, plugins, height, ariaLabel, fallback, className, style, }: ChartProps<TType, TData, TLabel>): react.JSX.Element;
|
|
466
489
|
|
|
467
490
|
interface SparklineChartProps {
|
|
468
491
|
id: string;
|
|
469
492
|
data: number[];
|
|
493
|
+
/** Line (and area) colour. Default `#DCE6EC`. */
|
|
494
|
+
color?: string;
|
|
495
|
+
/** `line` draws a filled area sparkline (default); `bar` draws mini columns. */
|
|
496
|
+
type?: 'line' | 'bar';
|
|
497
|
+
/** Height in pixels. Default `50`. */
|
|
498
|
+
height?: number;
|
|
499
|
+
/** Accessible name for the sparkline. */
|
|
500
|
+
ariaLabel?: string;
|
|
470
501
|
}
|
|
471
|
-
|
|
502
|
+
/**
|
|
503
|
+
* Tiny axis-less trend chart (Chart.js, lazy-loaded — install `chart.js`).
|
|
504
|
+
*/
|
|
505
|
+
declare function SparklineChart({ id, data, color, type, height, ariaLabel, }: SparklineChartProps): react.JSX.Element;
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* AdminLTE theme preset for Chart.js.
|
|
509
|
+
*
|
|
510
|
+
* `applyChartTheme(Chart)` writes the AdminLTE look into `Chart.defaults`: the body font,
|
|
511
|
+
* colours read from Bootstrap's CSS variables (so light/dark mode and custom themes follow),
|
|
512
|
+
* subtle horizontal gridlines only, rounded bars, smooth lines, circular point-style legends
|
|
513
|
+
* and Bootstrap-style tooltips. It is idempotent — `<Chart>` calls it again whenever
|
|
514
|
+
* `<html data-bs-theme>` or `<html dir>` changes, then updates every live chart.
|
|
515
|
+
*
|
|
516
|
+
* It takes the Chart.js constructor as an argument (no static import), so this module stays
|
|
517
|
+
* safe to import from Server Components and never pulls `chart.js` into a bundle by itself.
|
|
518
|
+
*/
|
|
519
|
+
type ChartStatic = typeof Chart$1;
|
|
520
|
+
interface ChartThemeColors {
|
|
521
|
+
/** Body font family (`--bs-body-font-family`). */
|
|
522
|
+
fontFamily: string;
|
|
523
|
+
/** Primary text colour (`--bs-body-color`) — legend labels. */
|
|
524
|
+
text: string;
|
|
525
|
+
/** Muted text colour (`--bs-secondary-color`) — axis ticks. */
|
|
526
|
+
muted: string;
|
|
527
|
+
/** Gridline / border colour (`--bs-border-color-translucent`). */
|
|
528
|
+
grid: string;
|
|
529
|
+
/** Surface colour (`--bs-body-bg`) — doughnut segment separators, point borders. */
|
|
530
|
+
surface: string;
|
|
531
|
+
/** Tooltip background (`--bs-emphasis-color`). */
|
|
532
|
+
tooltipBg: string;
|
|
533
|
+
/** Tooltip text (`--bs-body-bg`). */
|
|
534
|
+
tooltipText: string;
|
|
535
|
+
/** `true` when the document is right-to-left. */
|
|
536
|
+
rtl: boolean;
|
|
537
|
+
}
|
|
538
|
+
/** Reads the current theme colours from the document's Bootstrap CSS variables. */
|
|
539
|
+
declare function readChartThemeColors(root?: Element): ChartThemeColors;
|
|
540
|
+
/**
|
|
541
|
+
* Returns `color` with the given alpha. Accepts `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb()` and
|
|
542
|
+
* `rgba()`; anything else (named colours, gradients) is returned unchanged.
|
|
543
|
+
*/
|
|
544
|
+
declare function withAlpha(color: string, alpha: number): string;
|
|
545
|
+
/** Applies the AdminLTE preset to `Chart.defaults`. Safe to call repeatedly. */
|
|
546
|
+
declare function applyChartTheme(Chart: ChartStatic, colors?: ChartThemeColors): void;
|
|
472
547
|
|
|
473
548
|
interface WorldMapProps {
|
|
474
549
|
id: string;
|
|
@@ -1006,4 +1081,4 @@ declare function useTreeviewAnimation(isOpen: boolean, speed?: number): UseTreev
|
|
|
1006
1081
|
|
|
1007
1082
|
declare function useSortable(enabled?: boolean): void;
|
|
1008
1083
|
|
|
1009
|
-
export { Accordion, type AccordionItem, type AccordionProps, Alert,
|
|
1084
|
+
export { Accordion, type AccordionItem, type AccordionProps, Alert, AppContent, type AppContentProps, AuthLayout, type AuthLayoutProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, type AvatarStatus, Badge, type BadgeProps, type BootstrapTheme, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, type BreakpointSize, Button, Callout, Card, Carousel, type CarouselProps, type CarouselSlide, Chart, type ChartProps, type ChartThemeColors, type ColorMode, ColorModeProvider, ColorModeToggle, type CommandItem, CommandPalette, CommandPaletteProvider, type ComponentSize, DashboardLayout, type DashboardLayoutProps, Datatable, DescriptionBlock, DirectChat, type DirectChatContact, type DirectChatMessage, Dropdown, type DropdownItem, type DropdownProps, Editor, Footer, InfoBox, Input, InputColor, InputFile, InputFlatpickr, InputSwitch, InputTomSelect, type LinkComponent, type LinkProps, LinkProvider, ListGroup, ListGroupItem, type ListGroupItemProps, type ListGroupProps, type MenuGroup, type MenuHeader, type MenuItem, type MenuNode, Modal, NavMessages, NavNotifications, NavTasks, Offcanvas, type OffcanvasProps, OffcanvasTrigger, type OffcanvasTriggerProps, Pagination, type PaginationProps, ProfileCard, Progress, ProgressGroup, Ratings, Select, Sidebar, SidebarBrand, SidebarNav, SidebarNavItem, SidebarOverlay, SidebarProvider, SmallBox, SparklineChart, type SparklineChartProps, Spinner, type SpinnerProps, type Step, Stepper, type StepperProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Textarea, Timeline, Toast, type ToastProps, Tooltip, type TooltipProps, Topbar, type TopbarUser, WorldMap, applyChartTheme, flattenMenuToCommands, readChartThemeColors, useCardWidget, useColorModeContext, useCommandPalette, useDirectChat, useFullscreen, useLinkComponent, usePushMenu, useSidebarContext, useSortable, useTreeviewAnimation, withAlpha };
|
package/dist/index.js
CHANGED
|
@@ -28,8 +28,9 @@ import { NavMessages } from "./widget/nav-messages.js";
|
|
|
28
28
|
import { NavNotifications } from "./widget/nav-notifications.js";
|
|
29
29
|
import { NavTasks } from "./widget/nav-tasks.js";
|
|
30
30
|
import { DirectChat } from "./widget/direct-chat.js";
|
|
31
|
-
import {
|
|
31
|
+
import { Chart } from "./widget/chart.js";
|
|
32
32
|
import { SparklineChart } from "./widget/sparkline-chart.js";
|
|
33
|
+
import { applyChartTheme, readChartThemeColors, withAlpha } from "./lib/chart-theme.js";
|
|
33
34
|
import { WorldMap } from "./widget/world-map.js";
|
|
34
35
|
import { CommandPalette } from "./widget/command-palette.js";
|
|
35
36
|
import { flattenMenuToCommands } from "./lib/flatten-menu.js";
|
|
@@ -69,7 +70,6 @@ import { useSortable } from "./hooks/use-sortable.js";
|
|
|
69
70
|
export {
|
|
70
71
|
Accordion,
|
|
71
72
|
Alert,
|
|
72
|
-
ApexChart,
|
|
73
73
|
AppContent,
|
|
74
74
|
AuthLayout,
|
|
75
75
|
Avatar,
|
|
@@ -80,6 +80,7 @@ export {
|
|
|
80
80
|
Callout,
|
|
81
81
|
Card,
|
|
82
82
|
Carousel,
|
|
83
|
+
Chart,
|
|
83
84
|
ColorModeProvider,
|
|
84
85
|
ColorModeToggle,
|
|
85
86
|
CommandPalette,
|
|
@@ -131,7 +132,9 @@ export {
|
|
|
131
132
|
Tooltip,
|
|
132
133
|
Topbar,
|
|
133
134
|
WorldMap,
|
|
135
|
+
applyChartTheme,
|
|
134
136
|
flattenMenuToCommands,
|
|
137
|
+
readChartThemeColors,
|
|
135
138
|
useCardWidget,
|
|
136
139
|
useColorModeContext,
|
|
137
140
|
useCommandPalette,
|
|
@@ -141,5 +144,6 @@ export {
|
|
|
141
144
|
usePushMenu,
|
|
142
145
|
useSidebarContext,
|
|
143
146
|
useSortable,
|
|
144
|
-
useTreeviewAnimation
|
|
147
|
+
useTreeviewAnimation,
|
|
148
|
+
withAlpha
|
|
145
149
|
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const FALLBACK = {
|
|
2
|
+
fontFamily: '"Source Sans 3", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
3
|
+
text: "#212529",
|
|
4
|
+
muted: "rgba(33, 37, 41, 0.75)",
|
|
5
|
+
grid: "rgba(0, 0, 0, 0.175)",
|
|
6
|
+
surface: "#fff",
|
|
7
|
+
tooltipBg: "#000",
|
|
8
|
+
tooltipText: "#fff",
|
|
9
|
+
rtl: false
|
|
10
|
+
};
|
|
11
|
+
function readChartThemeColors(root) {
|
|
12
|
+
if (typeof window === "undefined" || typeof document === "undefined") return FALLBACK;
|
|
13
|
+
const el = root ?? document.documentElement;
|
|
14
|
+
const css = getComputedStyle(el);
|
|
15
|
+
const v = (name, fallback) => css.getPropertyValue(name).trim() || fallback;
|
|
16
|
+
return {
|
|
17
|
+
fontFamily: v("--bs-body-font-family", FALLBACK.fontFamily),
|
|
18
|
+
text: v("--bs-body-color", FALLBACK.text),
|
|
19
|
+
muted: v("--bs-secondary-color", FALLBACK.muted),
|
|
20
|
+
grid: v("--bs-border-color-translucent", FALLBACK.grid),
|
|
21
|
+
surface: v("--bs-body-bg", FALLBACK.surface),
|
|
22
|
+
tooltipBg: v("--bs-emphasis-color", FALLBACK.tooltipBg),
|
|
23
|
+
tooltipText: v("--bs-body-bg", FALLBACK.tooltipText),
|
|
24
|
+
rtl: (document.documentElement.getAttribute("dir") || css.direction) === "rtl"
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function withAlpha(color, alpha) {
|
|
28
|
+
const c = color.trim();
|
|
29
|
+
const hex = /^#([0-9a-f]{3,8})$/i.exec(c);
|
|
30
|
+
if (hex) {
|
|
31
|
+
let h = hex[1];
|
|
32
|
+
if (h.length === 3 || h.length === 4) h = h.split("").map((x) => x + x).join("");
|
|
33
|
+
if (h.length !== 6 && h.length !== 8) return color;
|
|
34
|
+
const n = (i) => parseInt(h.slice(i, i + 2), 16);
|
|
35
|
+
return `rgba(${n(0)}, ${n(2)}, ${n(4)}, ${alpha})`;
|
|
36
|
+
}
|
|
37
|
+
const rgb = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i.exec(c);
|
|
38
|
+
if (rgb) return `rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, ${alpha})`;
|
|
39
|
+
return color;
|
|
40
|
+
}
|
|
41
|
+
function lineBackground(ctx) {
|
|
42
|
+
const raw = ctx.dataset?.borderColor;
|
|
43
|
+
const base = typeof raw === "string" ? raw : void 0;
|
|
44
|
+
if (!base) return "rgba(0, 0, 0, 0.1)";
|
|
45
|
+
if (ctx.type !== "dataset" || !ctx.dataset.fill) return base;
|
|
46
|
+
const { chart } = ctx;
|
|
47
|
+
const area = chart.chartArea;
|
|
48
|
+
if (!area) return withAlpha(base, 0.2);
|
|
49
|
+
const gradient = chart.ctx.createLinearGradient(0, area.top, 0, area.bottom);
|
|
50
|
+
gradient.addColorStop(0, withAlpha(base, 0.35));
|
|
51
|
+
gradient.addColorStop(1, withAlpha(base, 0.02));
|
|
52
|
+
return gradient;
|
|
53
|
+
}
|
|
54
|
+
function applyChartTheme(Chart, colors = readChartThemeColors()) {
|
|
55
|
+
const d = Chart.defaults;
|
|
56
|
+
d.font.family = colors.fontFamily;
|
|
57
|
+
d.font.size = 12;
|
|
58
|
+
d.color = colors.muted;
|
|
59
|
+
d.borderColor = colors.grid;
|
|
60
|
+
const scale = d.scale;
|
|
61
|
+
scale.grid.color = colors.grid;
|
|
62
|
+
scale.grid.drawTicks = false;
|
|
63
|
+
scale.border.display = false;
|
|
64
|
+
scale.ticks.padding = 8;
|
|
65
|
+
scale.ticks.color = colors.muted;
|
|
66
|
+
const category = d.scales.category;
|
|
67
|
+
if (category) category.grid = { ...category.grid || {}, display: false };
|
|
68
|
+
d.elements.line.tension = 0.4;
|
|
69
|
+
d.elements.line.borderWidth = 2.5;
|
|
70
|
+
d.elements.line.borderCapStyle = "round";
|
|
71
|
+
d.elements.point.radius = 0;
|
|
72
|
+
d.elements.point.hoverRadius = 5;
|
|
73
|
+
d.elements.point.hitRadius = 12;
|
|
74
|
+
d.elements.point.borderWidth = 2;
|
|
75
|
+
d.elements.point.hoverBorderWidth = 2;
|
|
76
|
+
d.elements.point.pointStyle = "circle";
|
|
77
|
+
d.elements.bar.borderRadius = 4;
|
|
78
|
+
d.elements.bar.borderSkipped = "start";
|
|
79
|
+
d.elements.arc.borderColor = colors.surface;
|
|
80
|
+
d.elements.arc.borderWidth = 2;
|
|
81
|
+
d.elements.arc.hoverOffset = 6;
|
|
82
|
+
const lineDefaults = d.datasets.line;
|
|
83
|
+
if (lineDefaults) {
|
|
84
|
+
lineDefaults.backgroundColor = lineBackground;
|
|
85
|
+
lineDefaults.pointHoverBackgroundColor = colors.surface;
|
|
86
|
+
}
|
|
87
|
+
const barDefaults = d.datasets.bar;
|
|
88
|
+
if (barDefaults) barDefaults.maxBarThickness = 28;
|
|
89
|
+
for (const type of ["line", "bar"]) {
|
|
90
|
+
const o = Chart.overrides[type];
|
|
91
|
+
if (o) o.interaction = { mode: "index", intersect: false };
|
|
92
|
+
}
|
|
93
|
+
const legend = d.plugins.legend;
|
|
94
|
+
legend.rtl = colors.rtl;
|
|
95
|
+
legend.labels.color = colors.text;
|
|
96
|
+
legend.labels.usePointStyle = true;
|
|
97
|
+
legend.labels.pointStyle = "circle";
|
|
98
|
+
legend.labels.boxWidth = 8;
|
|
99
|
+
legend.labels.boxHeight = 8;
|
|
100
|
+
legend.labels.padding = 16;
|
|
101
|
+
const tooltip = d.plugins.tooltip;
|
|
102
|
+
tooltip.rtl = colors.rtl;
|
|
103
|
+
tooltip.backgroundColor = withAlpha(colors.tooltipBg, 0.9);
|
|
104
|
+
tooltip.titleColor = colors.tooltipText;
|
|
105
|
+
tooltip.bodyColor = colors.tooltipText;
|
|
106
|
+
tooltip.footerColor = colors.tooltipText;
|
|
107
|
+
tooltip.borderWidth = 0;
|
|
108
|
+
tooltip.cornerRadius = 6;
|
|
109
|
+
tooltip.padding = 10;
|
|
110
|
+
tooltip.boxPadding = 4;
|
|
111
|
+
tooltip.caretSize = 5;
|
|
112
|
+
tooltip.usePointStyle = true;
|
|
113
|
+
tooltip.titleFont = { weight: "bold" };
|
|
114
|
+
tooltip.titleMarginBottom = 6;
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
applyChartTheme,
|
|
118
|
+
readChartThemeColors,
|
|
119
|
+
withAlpha
|
|
120
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { applyChartTheme } from "../lib/chart-theme.js";
|
|
5
|
+
const liveCharts = /* @__PURE__ */ new Map();
|
|
6
|
+
let ChartClass = null;
|
|
7
|
+
let observer = null;
|
|
8
|
+
function retheme() {
|
|
9
|
+
if (!ChartClass) return;
|
|
10
|
+
applyChartTheme(ChartClass);
|
|
11
|
+
liveCharts.forEach((freshOptions, chart) => {
|
|
12
|
+
chart.options = freshOptions();
|
|
13
|
+
chart.update("none");
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function track(chart, freshOptions) {
|
|
17
|
+
liveCharts.set(chart, freshOptions);
|
|
18
|
+
if (!observer && typeof MutationObserver !== "undefined") {
|
|
19
|
+
observer = new MutationObserver(retheme);
|
|
20
|
+
observer.observe(document.documentElement, {
|
|
21
|
+
attributes: true,
|
|
22
|
+
attributeFilter: ["data-bs-theme", "dir"]
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function untrack(chart) {
|
|
27
|
+
liveCharts.delete(chart);
|
|
28
|
+
if (liveCharts.size === 0 && observer) {
|
|
29
|
+
observer.disconnect();
|
|
30
|
+
observer = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function loadChart() {
|
|
34
|
+
if (!ChartClass) {
|
|
35
|
+
const mod = await import("chart.js/auto");
|
|
36
|
+
ChartClass = mod.default;
|
|
37
|
+
}
|
|
38
|
+
applyChartTheme(ChartClass);
|
|
39
|
+
return ChartClass;
|
|
40
|
+
}
|
|
41
|
+
function cloneData(data) {
|
|
42
|
+
try {
|
|
43
|
+
return structuredClone(data);
|
|
44
|
+
} catch {
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function cloneOptions(value) {
|
|
49
|
+
if (Array.isArray(value)) return value.map(cloneOptions);
|
|
50
|
+
if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const [k, v] of Object.entries(value)) out[k] = cloneOptions(v);
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function withBaseOptions(options) {
|
|
58
|
+
return { responsive: true, maintainAspectRatio: false, ...cloneOptions(options || {}) };
|
|
59
|
+
}
|
|
60
|
+
function Chart({
|
|
61
|
+
id,
|
|
62
|
+
type,
|
|
63
|
+
data,
|
|
64
|
+
options,
|
|
65
|
+
plugins,
|
|
66
|
+
height = 300,
|
|
67
|
+
ariaLabel,
|
|
68
|
+
fallback,
|
|
69
|
+
className,
|
|
70
|
+
style
|
|
71
|
+
}) {
|
|
72
|
+
const canvasRef = useRef(null);
|
|
73
|
+
const chartRef = useRef(null);
|
|
74
|
+
const optionsRef = useRef(options);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
optionsRef.current = options;
|
|
77
|
+
});
|
|
78
|
+
const dataKey = JSON.stringify(data);
|
|
79
|
+
const optionsKey = JSON.stringify(options ?? {});
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
let cancelled = false;
|
|
82
|
+
const canvas = canvasRef.current;
|
|
83
|
+
if (!canvas) return;
|
|
84
|
+
loadChart().then((ChartCtor) => {
|
|
85
|
+
if (cancelled || !canvasRef.current) return;
|
|
86
|
+
ChartCtor.getChart(canvas)?.destroy();
|
|
87
|
+
const chart = new ChartCtor(canvas, {
|
|
88
|
+
type,
|
|
89
|
+
data: cloneData(data),
|
|
90
|
+
options: withBaseOptions(options),
|
|
91
|
+
plugins
|
|
92
|
+
});
|
|
93
|
+
chartRef.current = chart;
|
|
94
|
+
track(chart, () => withBaseOptions(optionsRef.current));
|
|
95
|
+
}).catch((err) => console.error("Chart init error:", err));
|
|
96
|
+
return () => {
|
|
97
|
+
cancelled = true;
|
|
98
|
+
const chart = chartRef.current;
|
|
99
|
+
if (chart) {
|
|
100
|
+
untrack(chart);
|
|
101
|
+
chart.destroy();
|
|
102
|
+
chartRef.current = null;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}, [type]);
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const chart = chartRef.current;
|
|
108
|
+
if (!chart) return;
|
|
109
|
+
chart.data = cloneData(data);
|
|
110
|
+
chart.options = withBaseOptions(options);
|
|
111
|
+
chart.update();
|
|
112
|
+
}, [dataKey, optionsKey]);
|
|
113
|
+
return /* @__PURE__ */ jsx(
|
|
114
|
+
"div",
|
|
115
|
+
{
|
|
116
|
+
id,
|
|
117
|
+
className,
|
|
118
|
+
style: { position: "relative", height: typeof height === "number" ? `${height}px` : height, ...style },
|
|
119
|
+
children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, role: "img", "aria-label": ariaLabel ?? `${type} chart`, children: fallback })
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
Chart
|
|
125
|
+
};
|
|
@@ -1,42 +1,53 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { Chart } from "./chart.js";
|
|
5
|
+
import { withAlpha } from "../lib/chart-theme.js";
|
|
6
|
+
function SparklineChart({
|
|
7
|
+
id,
|
|
8
|
+
data,
|
|
9
|
+
color = "#DCE6EC",
|
|
10
|
+
type = "line",
|
|
11
|
+
height = 50,
|
|
12
|
+
ariaLabel = "Sparkline"
|
|
13
|
+
}) {
|
|
14
|
+
const chartData = useMemo(
|
|
15
|
+
() => ({
|
|
16
|
+
labels: data.map((_, i) => String(i + 1)),
|
|
17
|
+
datasets: [
|
|
18
|
+
type === "bar" ? { data, backgroundColor: color, borderRadius: 2, maxBarThickness: 8 } : {
|
|
19
|
+
data,
|
|
20
|
+
borderColor: color,
|
|
21
|
+
backgroundColor: withAlpha(color, 0.3),
|
|
22
|
+
borderWidth: 2,
|
|
23
|
+
fill: "origin",
|
|
24
|
+
tension: 0,
|
|
25
|
+
pointRadius: 0,
|
|
26
|
+
pointHoverRadius: 0
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}),
|
|
30
|
+
[data, color, type]
|
|
31
|
+
);
|
|
32
|
+
return /* @__PURE__ */ jsx(
|
|
33
|
+
Chart,
|
|
34
|
+
{
|
|
35
|
+
id,
|
|
36
|
+
type,
|
|
37
|
+
height,
|
|
38
|
+
ariaLabel,
|
|
39
|
+
data: chartData,
|
|
40
|
+
options: {
|
|
41
|
+
events: [],
|
|
42
|
+
layout: { padding: 1 },
|
|
43
|
+
plugins: { legend: { display: false }, tooltip: { enabled: false } },
|
|
44
|
+
scales: {
|
|
45
|
+
x: { display: false },
|
|
46
|
+
y: { display: false, min: 0 }
|
|
47
|
+
}
|
|
36
48
|
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return /* @__PURE__ */ jsx("div", { id, ref: containerRef });
|
|
49
|
+
}
|
|
50
|
+
);
|
|
40
51
|
}
|
|
41
52
|
export {
|
|
42
53
|
SparklineChart
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminlte/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "AdminLTE 4 React/Next.js component library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,10 +36,11 @@
|
|
|
36
36
|
"react",
|
|
37
37
|
"next",
|
|
38
38
|
"bootstrap",
|
|
39
|
-
"components"
|
|
39
|
+
"components",
|
|
40
|
+
"chartjs"
|
|
40
41
|
],
|
|
41
42
|
"peerDependencies": {
|
|
42
|
-
"
|
|
43
|
+
"chart.js": ">=4.5.0",
|
|
43
44
|
"flatpickr": ">=4.6.0",
|
|
44
45
|
"jsvectormap": ">=1.6.0",
|
|
45
46
|
"next": ">=14.0.0",
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
"tom-select": ">=2.3.0"
|
|
52
53
|
},
|
|
53
54
|
"peerDependenciesMeta": {
|
|
54
|
-
"
|
|
55
|
+
"chart.js": {
|
|
55
56
|
"optional": true
|
|
56
57
|
},
|
|
57
58
|
"flatpickr": {
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
"@types/react-dom": "^19.2.4",
|
|
85
86
|
"@types/sortablejs": "^1.15.9",
|
|
86
87
|
"admin-lte": "^4.8.1",
|
|
87
|
-
"
|
|
88
|
+
"chart.js": "^4.5.1",
|
|
88
89
|
"eslint": "^10.8.1",
|
|
89
90
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
90
91
|
"flatpickr": "^4.6.13",
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useRef } from "react";
|
|
4
|
-
function ApexChart({ id, series, config }) {
|
|
5
|
-
const containerRef = useRef(null);
|
|
6
|
-
const chartRef = useRef(null);
|
|
7
|
-
const seriesKey = JSON.stringify(series);
|
|
8
|
-
const configKey = JSON.stringify(config);
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
let cancelled = false;
|
|
11
|
-
const el = containerRef.current;
|
|
12
|
-
if (!el) return;
|
|
13
|
-
if (chartRef.current) {
|
|
14
|
-
chartRef.current.updateOptions({ ...config, series }, true, true);
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
const initChart = async () => {
|
|
18
|
-
const ApexCharts = (await import("apexcharts")).default;
|
|
19
|
-
if (cancelled || !containerRef.current) return;
|
|
20
|
-
const chart = new ApexCharts(el, { ...config, series });
|
|
21
|
-
chartRef.current = chart;
|
|
22
|
-
chart.render();
|
|
23
|
-
};
|
|
24
|
-
initChart().catch((err) => console.error("ApexChart init error:", err));
|
|
25
|
-
return () => {
|
|
26
|
-
cancelled = true;
|
|
27
|
-
};
|
|
28
|
-
}, [id, seriesKey, configKey]);
|
|
29
|
-
useEffect(
|
|
30
|
-
() => () => {
|
|
31
|
-
chartRef.current?.destroy();
|
|
32
|
-
chartRef.current = null;
|
|
33
|
-
},
|
|
34
|
-
[]
|
|
35
|
-
);
|
|
36
|
-
return /* @__PURE__ */ jsx("div", { id, ref: containerRef });
|
|
37
|
-
}
|
|
38
|
-
export {
|
|
39
|
-
ApexChart
|
|
40
|
-
};
|