@mbao01/common 0.9.1 → 0.9.3

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.
Files changed (27) hide show
  1. package/dist/types/components/Chart/stories/args/index.d.ts +13 -1
  2. package/dist/types/components/Chart/stories/args/scatterArgs.d.ts +2 -0
  3. package/dist/types/components/Chart/stories/args/scatterChartArgs.d.ts +2 -0
  4. package/dist/types/components/Chart/stories/examples/ScatterChart.d.ts +17 -0
  5. package/dist/types/components/KPICard/KPICard.d.ts +1 -1
  6. package/dist/types/components/KPICard/KPICardSkeleton.d.ts +11 -0
  7. package/dist/types/components/KPICard/index.d.ts +2 -0
  8. package/dist/types/components/KPICard/types.d.ts +0 -2
  9. package/dist/types/components/WidgetShell/WidgetShell.d.ts +20 -1
  10. package/dist/types/components/WidgetShell/WidgetShellSkeleton.d.ts +11 -0
  11. package/dist/types/components/WidgetShell/index.d.ts +3 -1
  12. package/dist/types/components/WidgetShell/types.d.ts +2 -11
  13. package/dist/types/index.d.ts +1 -0
  14. package/package.json +1 -1
  15. package/src/components/Chart/stories/args/index.ts +29 -0
  16. package/src/components/Chart/stories/args/scatterArgs.ts +79 -0
  17. package/src/components/Chart/stories/args/scatterChartArgs.ts +6 -0
  18. package/src/components/Chart/stories/examples/ScatterChart.tsx +263 -0
  19. package/src/components/KPICard/KPICard.tsx +0 -26
  20. package/src/components/KPICard/KPICardSkeleton.tsx +42 -0
  21. package/src/components/KPICard/index.ts +2 -0
  22. package/src/components/KPICard/types.ts +0 -2
  23. package/src/components/WidgetShell/WidgetShell.tsx +72 -70
  24. package/src/components/WidgetShell/WidgetShellSkeleton.tsx +47 -0
  25. package/src/components/WidgetShell/index.ts +3 -1
  26. package/src/components/WidgetShell/types.ts +2 -12
  27. package/src/index.ts +1 -0
@@ -1,4 +1,4 @@
1
- import { AreaProps, BarProps, LineProps, PieProps, RadarProps, RadialBarProps, XAxisProps, YAxisProps } from 'recharts';
1
+ import { AreaProps, BarProps, LineProps, PieProps, RadarProps, RadialBarProps, ScatterProps, XAxisProps, YAxisProps } from 'recharts';
2
2
  import { ArgTypes } from '@storybook/react-vite';
3
3
  import { CartesianChartProps, PolarChartProps } from 'recharts/types/util/types';
4
4
  import { Flatten, OmitSVGElement } from './types';
@@ -60,4 +60,16 @@ export declare const radarArgKey: {
60
60
  radarChart: "radarChart";
61
61
  };
62
62
  export declare const radarChartArgs: ArgTypes;
63
+ export type ScatterChartProps = {
64
+ scatter: OmitSVGElement<ScatterProps>;
65
+ scatterChart: CartesianChartProps;
66
+ } & AxisProps;
67
+ export type ScatterChartArgs = Partial<Flatten<ScatterChartProps>> & {};
68
+ export declare const scatterArgKey: {
69
+ scatter: "scatter";
70
+ scatterChart: "scatterChart";
71
+ xAxis: "xAxis";
72
+ yAxis: "yAxis";
73
+ };
74
+ export declare const scatterChartArgs: ArgTypes;
63
75
  export {};
@@ -0,0 +1,2 @@
1
+ import { ArgTypes } from '@storybook/react-vite';
2
+ export declare const scatterArgs: ArgTypes;
@@ -0,0 +1,2 @@
1
+ import { ArgTypes } from '@storybook/react-vite';
2
+ export declare const scatterChartArgs: ArgTypes;
@@ -0,0 +1,17 @@
1
+ import { ScatterChartProps } from '../args';
2
+ /**
3
+ * ## Parent
4
+ *
5
+ * The ScatterChart can be used within: `<Chart />`
6
+ *
7
+ * ## Children
8
+ *
9
+ * The ScatterChart can be used with the following child components: `<XAxis />`, `<YAxis />`, `<ZAxis />`,
10
+ * `<ReferenceArea />`, `<ReferenceDot />`, `<ReferenceLine />`, `<CartesianGrid />`,
11
+ * `<ChartLegend />`, `<ChartTooltip />`, `<Scatter />`, `<Customized />` or valid svg elements.
12
+ */
13
+ export declare const ScatterChartExample: (props: ScatterChartProps) => import("react/jsx-runtime").JSX.Element;
14
+ export declare const MultiSeriesScatterChartExample: (props: ScatterChartProps) => import("react/jsx-runtime").JSX.Element;
15
+ export declare const BubbleScatterChartExample: (props: ScatterChartProps) => import("react/jsx-runtime").JSX.Element;
16
+ export declare const WithLineScatterChartExample: (props: ScatterChartProps) => import("react/jsx-runtime").JSX.Element;
17
+ export declare const ShapesScatterChartExample: (props: ScatterChartProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { KPICardProps } from './types';
2
2
  declare const KPICard: {
3
- ({ title, value, change, description, chart, icon, loading, className, ...props }: KPICardProps): import("react/jsx-runtime").JSX.Element;
3
+ ({ title, value, change, description, chart, icon, className, ...props }: KPICardProps): import("react/jsx-runtime").JSX.Element;
4
4
  displayName: string;
5
5
  };
6
6
  export { KPICard };
@@ -0,0 +1,11 @@
1
+ export type KPICardSkeletonProps = React.HTMLAttributes<HTMLDivElement> & {
2
+ /** Whether to show the icon placeholder */
3
+ icon?: boolean;
4
+ /** Whether to show the chart placeholder */
5
+ chart?: boolean;
6
+ };
7
+ declare const KPICardSkeleton: {
8
+ ({ icon, chart, className, ...props }: KPICardSkeletonProps): import("react/jsx-runtime").JSX.Element;
9
+ displayName: string;
10
+ };
11
+ export { KPICardSkeleton };
@@ -1,2 +1,4 @@
1
1
  export { KPICard } from './KPICard';
2
+ export { KPICardSkeleton } from './KPICardSkeleton';
2
3
  export type { KPICardProps } from './types';
4
+ export type { KPICardSkeletonProps } from './KPICardSkeleton';
@@ -12,6 +12,4 @@ export type KPICardProps = HTMLAttributes<HTMLDivElement> & {
12
12
  chart?: ReactNode;
13
13
  /** Icon to show in the card */
14
14
  icon?: ReactNode;
15
- /** Loading state */
16
- loading?: boolean;
17
15
  };
@@ -1,6 +1,25 @@
1
1
  import { WidgetShellProps } from './types';
2
2
  declare const WidgetShell: {
3
- ({ state, title, description, children, className, errorContent, emptyContent, skeletonLines, onRetry, ...props }: WidgetShellProps): import("react/jsx-runtime").JSX.Element;
3
+ ({ title, description, action, children, className, ...props }: WidgetShellProps): import("react/jsx-runtime").JSX.Element;
4
4
  displayName: string;
5
+ Empty: {
6
+ ({ children }: {
7
+ children?: React.ReactNode;
8
+ }): string | number | bigint | boolean | Iterable<import('react').ReactNode> | Promise<string | number | bigint | boolean | import('react').ReactPortal | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>> | Iterable<import('react').ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element;
9
+ displayName: string;
10
+ };
11
+ Error: {
12
+ ({ children, onRetry, }: {
13
+ children?: React.ReactNode;
14
+ onRetry?: () => void;
15
+ }): string | number | bigint | boolean | Iterable<import('react').ReactNode> | Promise<string | number | bigint | boolean | import('react').ReactPortal | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>> | Iterable<import('react').ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element;
16
+ displayName: string;
17
+ };
18
+ Loading: {
19
+ ({ lines }: {
20
+ lines?: number;
21
+ }): import("react/jsx-runtime").JSX.Element;
22
+ displayName: string;
23
+ };
5
24
  };
6
25
  export { WidgetShell };
@@ -0,0 +1,11 @@
1
+ export type WidgetShellSkeletonProps = React.HTMLAttributes<HTMLDivElement> & {
2
+ /** Number of skeleton lines to show */
3
+ lines?: number;
4
+ /** Whether to show the header skeleton */
5
+ header?: boolean;
6
+ };
7
+ declare const WidgetShellSkeleton: {
8
+ ({ lines, header, className, ...props }: WidgetShellSkeletonProps): import("react/jsx-runtime").JSX.Element;
9
+ displayName: string;
10
+ };
11
+ export { WidgetShellSkeleton };
@@ -1,2 +1,4 @@
1
1
  export { WidgetShell } from './WidgetShell';
2
- export type { WidgetShellProps, WidgetShellState } from './types';
2
+ export { WidgetShellSkeleton } from './WidgetShellSkeleton';
3
+ export type { WidgetShellProps } from './types';
4
+ export type { WidgetShellSkeletonProps } from './WidgetShellSkeleton';
@@ -1,20 +1,11 @@
1
1
  import { HTMLAttributes, ReactNode } from 'react';
2
- export type WidgetShellState = "loading" | "error" | "empty" | "ready";
3
2
  export type WidgetShellProps = HTMLAttributes<HTMLDivElement> & {
4
- /** Current state of the widget */
5
- state?: WidgetShellState;
6
3
  /** Widget title */
7
4
  title?: ReactNode;
8
5
  /** Widget description */
9
6
  description?: ReactNode;
7
+ /** Content rendered in the top-right of the header (e.g. actions, icons, badges) */
8
+ action?: ReactNode;
10
9
  /** Content to show when state is "ready" */
11
10
  children: ReactNode;
12
- /** Custom content for error state */
13
- errorContent?: ReactNode;
14
- /** Custom content for empty state */
15
- emptyContent?: ReactNode;
16
- /** Number of skeleton lines to show in loading state */
17
- skeletonLines?: number;
18
- /** Callback when retry is clicked in error state */
19
- onRetry?: () => void;
20
11
  };
@@ -84,6 +84,7 @@ export * from './components/Widget';
84
84
  export * from './components/WidgetShell';
85
85
  /** molecules */
86
86
  export * from './components/Banner';
87
+ export * from './components/Drawer';
87
88
  export * from './components/Empty';
88
89
  export * from './components/Sidebar';
89
90
  export * from './components/Stepper';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mbao01/common",
3
3
  "private": false,
4
- "version": "0.9.1",
4
+ "version": "0.9.3",
5
5
  "type": "module",
6
6
  "author": "Ayomide Bakare",
7
7
  "license": "MIT",
@@ -5,6 +5,7 @@ import type {
5
5
  PieProps,
6
6
  RadarProps,
7
7
  RadialBarProps,
8
+ ScatterProps,
8
9
  XAxisProps,
9
10
  YAxisProps,
10
11
  } from "recharts";
@@ -24,6 +25,8 @@ import { radarArgs } from "./radarArgs";
24
25
  import { radarChartArgs as radarChartComponentArgs } from "./radarChartArgs";
25
26
  import { radialBarArgs } from "./radialBarArgs";
26
27
  import { radialBarChartArgs as radialBarChartComponentArgs } from "./radialBarChartArgs";
28
+ import { scatterArgs } from "./scatterArgs";
29
+ import { scatterChartArgs as scatterChartComponentArgs } from "./scatterChartArgs";
27
30
  import { OmitSVGElement } from "./types";
28
31
  import { xAxisArgs } from "./xAxisArgs";
29
32
  import { yAxisArgs } from "./yAxisArgs";
@@ -177,3 +180,29 @@ export const radarChartArgs: ArgTypes = {
177
180
  ...categorizeArgs(radarChartComponentArgs, radarArgKey.radarChart),
178
181
  ...categorizeArgs(radarArgs, radarArgKey.radar),
179
182
  };
183
+
184
+ /**
185
+ * Scatter chart
186
+ */
187
+ type ScatterArgKey = "scatter" | "scatterChart" | "xAxis" | "yAxis";
188
+
189
+ export type ScatterChartProps = {
190
+ scatter: OmitSVGElement<ScatterProps>;
191
+ scatterChart: CartesianChartProps;
192
+ } & AxisProps;
193
+
194
+ export type ScatterChartArgs = Partial<Flatten<ScatterChartProps>> & {};
195
+
196
+ export const scatterArgKey = {
197
+ scatter: "scatter",
198
+ scatterChart: "scatterChart",
199
+ xAxis: "xAxis",
200
+ yAxis: "yAxis",
201
+ } satisfies Record<string, ScatterArgKey>;
202
+
203
+ export const scatterChartArgs: ArgTypes = {
204
+ ...categorizeArgs(scatterChartComponentArgs, scatterArgKey.scatterChart),
205
+ ...categorizeArgs(xAxisArgs, scatterArgKey.xAxis),
206
+ ...categorizeArgs(yAxisArgs, scatterArgKey.yAxis),
207
+ ...categorizeArgs(scatterArgs, scatterArgKey.scatter),
208
+ };
@@ -0,0 +1,79 @@
1
+ import { type ArgTypes } from "@storybook/react-vite";
2
+ import { generalStyleArgs, hide, labelDescription, labelSummary } from "./stylesArgs";
3
+ import { animationArgs } from "./animationArgs";
4
+ import { dataKey, zAxisId } from "./cartesianSharedArgs";
5
+
6
+ export const scatterArgs: ArgTypes = {
7
+ ...generalStyleArgs,
8
+ ...animationArgs,
9
+ dataKey,
10
+ zAxisId,
11
+ hide,
12
+ name: {
13
+ description: `The name of data. This option will be used in tooltip and legend to represent a scatter.
14
+ If no value was set to this option, the value of dataKey will be used alternatively.`,
15
+ table: {
16
+ type: {
17
+ summary: "string | number",
18
+ },
19
+ category: "General",
20
+ },
21
+ control: "text",
22
+ },
23
+ shape: {
24
+ description:
25
+ "If a string set, specified symbol will be used to show scatter item. If ReactElement, the element will be cloned to render scatter item. If a function, it will be called to render scatter item.",
26
+ table: {
27
+ type: {
28
+ summary: "'circle' | 'cross' | 'diamond' | 'square' | 'star' | 'triangle' | 'wye' | ReactElement | Function",
29
+ },
30
+ defaultValue: {
31
+ summary: "'circle'",
32
+ },
33
+ category: "Style",
34
+ },
35
+ control: "select",
36
+ options: ["circle", "cross", "diamond", "square", "star", "triangle", "wye"],
37
+ },
38
+ line: {
39
+ description:
40
+ "If false set, line will not be drawn. If true set, line will be drawn which have the props calculated internally. If object set, line will be drawn which have the props merged by the internal calculated props and the option.",
41
+ table: {
42
+ type: {
43
+ summary: "boolean | Object | ReactElement | Function",
44
+ },
45
+ defaultValue: {
46
+ summary: "false",
47
+ },
48
+ category: "Style",
49
+ },
50
+ control: "boolean",
51
+ },
52
+ lineType: {
53
+ description: "The type of line in the scatter chart, 'joint' connects adjacent points, 'fitting' uses a fitting line.",
54
+ table: {
55
+ type: {
56
+ summary: "'joint' | 'fitting'",
57
+ },
58
+ defaultValue: {
59
+ summary: "'joint'",
60
+ },
61
+ category: "Style",
62
+ },
63
+ control: "radio",
64
+ options: ["joint", "fitting"],
65
+ },
66
+ label: {
67
+ description: labelDescription,
68
+ table: {
69
+ type: {
70
+ summary: labelSummary,
71
+ },
72
+ defaultValue: {
73
+ summary: "false",
74
+ },
75
+ category: "Style",
76
+ },
77
+ control: "boolean",
78
+ },
79
+ };
@@ -0,0 +1,6 @@
1
+ import { type ArgTypes } from "@storybook/react-vite";
2
+ import { categoricalChartArgs } from "./chartArgs";
3
+
4
+ export const scatterChartArgs: ArgTypes = {
5
+ ...categoricalChartArgs,
6
+ };
@@ -0,0 +1,263 @@
1
+ import { CartesianGrid, Scatter, ScatterChart, XAxis, YAxis, ZAxis } from "recharts";
2
+ import { Chart } from "../../Chart";
3
+ import {
4
+ ChartLegend,
5
+ ChartLegendContent,
6
+ ChartTooltip,
7
+ ChartTooltipContent,
8
+ } from "../../components";
9
+ import { type ChartConfig } from "../../types";
10
+ import { ScatterChartProps } from "../args";
11
+
12
+ const chartConfig = {
13
+ desktop: {
14
+ label: "Desktop",
15
+ color: "hsl(var(--chart-1))",
16
+ },
17
+ mobile: {
18
+ label: "Mobile",
19
+ color: "hsl(var(--chart-2))",
20
+ },
21
+ } satisfies ChartConfig;
22
+
23
+ /**
24
+ * ## Parent
25
+ *
26
+ * The ScatterChart can be used within: `<Chart />`
27
+ *
28
+ * ## Children
29
+ *
30
+ * The ScatterChart can be used with the following child components: `<XAxis />`, `<YAxis />`, `<ZAxis />`,
31
+ * `<ReferenceArea />`, `<ReferenceDot />`, `<ReferenceLine />`, `<CartesianGrid />`,
32
+ * `<ChartLegend />`, `<ChartTooltip />`, `<Scatter />`, `<Customized />` or valid svg elements.
33
+ */
34
+ export const ScatterChartExample = (props: ScatterChartProps) => {
35
+ return (
36
+ <Chart config={chartConfig} className="h-[300px] w-full">
37
+ <ScatterChart {...props.scatterChart}>
38
+ <CartesianGrid strokeDasharray="3 3" />
39
+ <XAxis {...props.xAxis} />
40
+ <YAxis {...props.yAxis} />
41
+ <ChartTooltip cursor={{ strokeDasharray: "3 3" }} content={<ChartTooltipContent />} />
42
+ <Scatter {...props.scatter} />
43
+ </ScatterChart>
44
+ </Chart>
45
+ );
46
+ };
47
+
48
+ export const MultiSeriesScatterChartExample = (props: ScatterChartProps) => {
49
+ const desktopData = [
50
+ { x: 100, y: 200 },
51
+ { x: 120, y: 100 },
52
+ { x: 170, y: 300 },
53
+ { x: 140, y: 250 },
54
+ { x: 150, y: 400 },
55
+ { x: 110, y: 280 },
56
+ ];
57
+
58
+ const mobileData = [
59
+ { x: 200, y: 260 },
60
+ { x: 240, y: 290 },
61
+ { x: 190, y: 340 },
62
+ { x: 198, y: 250 },
63
+ { x: 180, y: 280 },
64
+ { x: 210, y: 220 },
65
+ ];
66
+
67
+ return (
68
+ <Chart config={chartConfig} className="h-[300px] w-full">
69
+ <ScatterChart {...props.scatterChart}>
70
+ <CartesianGrid strokeDasharray="3 3" />
71
+ <XAxis {...props.xAxis} />
72
+ <YAxis {...props.yAxis} />
73
+ <ChartTooltip cursor={{ strokeDasharray: "3 3" }} content={<ChartTooltipContent />} />
74
+ <Scatter
75
+ {...props.scatter}
76
+ name="Desktop"
77
+ data={desktopData}
78
+ fill="var(--color-desktop)"
79
+ />
80
+ <Scatter
81
+ {...props.scatter}
82
+ name="Mobile"
83
+ data={mobileData}
84
+ fill="var(--color-mobile)"
85
+ />
86
+ <ChartLegend content={<ChartLegendContent />} />
87
+ </ScatterChart>
88
+ </Chart>
89
+ );
90
+ };
91
+
92
+ export const BubbleScatterChartExample = (props: ScatterChartProps) => {
93
+ const chartConfig = {
94
+ visitors: {
95
+ label: "Visitors",
96
+ color: "hsl(var(--chart-1))",
97
+ },
98
+ } satisfies ChartConfig;
99
+
100
+ return (
101
+ <Chart config={chartConfig} className="h-[300px] w-full">
102
+ <ScatterChart {...props.scatterChart}>
103
+ <CartesianGrid strokeDasharray="3 3" />
104
+ <XAxis
105
+ dataKey="hours"
106
+ name="Hours Spent"
107
+ type="number"
108
+ tickLine={false}
109
+ axisLine={false}
110
+ tickMargin={8}
111
+ {...props.xAxis}
112
+ />
113
+ <YAxis
114
+ dataKey="score"
115
+ name="Test Score"
116
+ type="number"
117
+ tickLine={false}
118
+ axisLine={false}
119
+ tickMargin={8}
120
+ {...props.yAxis}
121
+ />
122
+ <ZAxis dataKey="students" range={[60, 400]} name="Students" />
123
+ <ChartTooltip cursor={{ strokeDasharray: "3 3" }} content={<ChartTooltipContent />} />
124
+ <Scatter
125
+ name="Visitors"
126
+ fill="var(--color-visitors)"
127
+ {...props.scatter}
128
+ />
129
+ </ScatterChart>
130
+ </Chart>
131
+ );
132
+ };
133
+
134
+ export const WithLineScatterChartExample = (props: ScatterChartProps) => {
135
+ const chartConfig = {
136
+ performance: {
137
+ label: "Performance",
138
+ color: "hsl(var(--chart-1))",
139
+ },
140
+ } satisfies ChartConfig;
141
+
142
+ return (
143
+ <Chart config={chartConfig} className="h-[300px] w-full">
144
+ <ScatterChart {...props.scatterChart}>
145
+ <CartesianGrid strokeDasharray="3 3" />
146
+ <XAxis
147
+ dataKey="x"
148
+ name="Experience (years)"
149
+ type="number"
150
+ tickLine={false}
151
+ axisLine={false}
152
+ tickMargin={8}
153
+ {...props.xAxis}
154
+ />
155
+ <YAxis
156
+ dataKey="y"
157
+ name="Salary ($k)"
158
+ type="number"
159
+ tickLine={false}
160
+ axisLine={false}
161
+ tickMargin={8}
162
+ {...props.yAxis}
163
+ />
164
+ <ChartTooltip cursor={{ strokeDasharray: "3 3" }} content={<ChartTooltipContent />} />
165
+ <Scatter
166
+ name="Performance"
167
+ fill="var(--color-performance)"
168
+ line={{ stroke: "var(--color-performance)", strokeWidth: 2 }}
169
+ lineType="fitting"
170
+ {...props.scatter}
171
+ />
172
+ </ScatterChart>
173
+ </Chart>
174
+ );
175
+ };
176
+
177
+ export const ShapesScatterChartExample = (props: ScatterChartProps) => {
178
+ const chartConfig = {
179
+ series1: {
180
+ label: "Series A",
181
+ color: "hsl(var(--chart-1))",
182
+ },
183
+ series2: {
184
+ label: "Series B",
185
+ color: "hsl(var(--chart-2))",
186
+ },
187
+ series3: {
188
+ label: "Series C",
189
+ color: "hsl(var(--chart-3))",
190
+ },
191
+ } satisfies ChartConfig;
192
+
193
+ const series1Data = [
194
+ { x: 10, y: 30 },
195
+ { x: 30, y: 50 },
196
+ { x: 50, y: 70 },
197
+ { x: 70, y: 40 },
198
+ { x: 90, y: 60 },
199
+ ];
200
+
201
+ const series2Data = [
202
+ { x: 20, y: 80 },
203
+ { x: 40, y: 20 },
204
+ { x: 60, y: 50 },
205
+ { x: 80, y: 90 },
206
+ { x: 100, y: 30 },
207
+ ];
208
+
209
+ const series3Data = [
210
+ { x: 15, y: 45 },
211
+ { x: 35, y: 65 },
212
+ { x: 55, y: 25 },
213
+ { x: 75, y: 85 },
214
+ { x: 95, y: 55 },
215
+ ];
216
+
217
+ return (
218
+ <Chart config={chartConfig} className="h-[300px] w-full">
219
+ <ScatterChart {...props.scatterChart}>
220
+ <CartesianGrid strokeDasharray="3 3" />
221
+ <XAxis
222
+ dataKey="x"
223
+ type="number"
224
+ tickLine={false}
225
+ axisLine={false}
226
+ tickMargin={8}
227
+ {...props.xAxis}
228
+ />
229
+ <YAxis
230
+ dataKey="y"
231
+ type="number"
232
+ tickLine={false}
233
+ axisLine={false}
234
+ tickMargin={8}
235
+ {...props.yAxis}
236
+ />
237
+ <ChartTooltip cursor={{ strokeDasharray: "3 3" }} content={<ChartTooltipContent />} />
238
+ <Scatter
239
+ name="Series A"
240
+ data={series1Data}
241
+ fill="var(--color-series1)"
242
+ shape="circle"
243
+ {...props.scatter}
244
+ />
245
+ <Scatter
246
+ name="Series B"
247
+ data={series2Data}
248
+ fill="var(--color-series2)"
249
+ shape="diamond"
250
+ {...props.scatter}
251
+ />
252
+ <Scatter
253
+ name="Series C"
254
+ data={series3Data}
255
+ fill="var(--color-series3)"
256
+ shape="triangle"
257
+ {...props.scatter}
258
+ />
259
+ <ChartLegend content={<ChartLegendContent />} />
260
+ </ScatterChart>
261
+ </Chart>
262
+ );
263
+ };
@@ -1,6 +1,5 @@
1
1
  import type { KPICardProps } from "./types";
2
2
  import { cn } from "../../utilities";
3
- import { Skeleton } from "../Skeleton";
4
3
  import { TrendBadge } from "../TrendBadge";
5
4
 
6
5
  const KPICard = ({
@@ -10,34 +9,9 @@ const KPICard = ({
10
9
  description,
11
10
  chart,
12
11
  icon,
13
- loading = false,
14
12
  className,
15
13
  ...props
16
14
  }: KPICardProps) => {
17
- if (loading) {
18
- return (
19
- <div
20
- className={cn(
21
- "rounded-lg border bg-base-100 p-4 shadow-sm transition-shadow duration-300 hover:shadow-md",
22
- className
23
- )}
24
- {...props}
25
- >
26
- <div className="flex items-center justify-between">
27
- <Skeleton className="h-4 w-24 rounded" />
28
- {icon && <Skeleton className="size-8 rounded-md" />}
29
- </div>
30
- <div className="mt-4 flex items-end justify-between gap-4">
31
- <div className="flex flex-col gap-1.5">
32
- <Skeleton className="h-7 w-28 rounded" />
33
- <Skeleton className="h-3.5 w-20 rounded" />
34
- </div>
35
- <Skeleton className="h-8 w-20 rounded" />
36
- </div>
37
- </div>
38
- );
39
- }
40
-
41
15
  return (
42
16
  <div
43
17
  className={cn(
@@ -0,0 +1,42 @@
1
+ import { cn } from "../../utilities";
2
+ import { Skeleton } from "../Skeleton";
3
+
4
+ export type KPICardSkeletonProps = React.HTMLAttributes<HTMLDivElement> & {
5
+ /** Whether to show the icon placeholder */
6
+ icon?: boolean;
7
+ /** Whether to show the chart placeholder */
8
+ chart?: boolean;
9
+ };
10
+
11
+ const KPICardSkeleton = ({
12
+ icon = true,
13
+ chart = true,
14
+ className,
15
+ ...props
16
+ }: KPICardSkeletonProps) => {
17
+ return (
18
+ <div
19
+ className={cn(
20
+ "w-full overflow-hidden rounded-lg border bg-base-100 p-4 shadow-sm",
21
+ className
22
+ )}
23
+ {...props}
24
+ >
25
+ <div className="flex items-center justify-between">
26
+ <Skeleton className="h-4 w-24 rounded" />
27
+ {icon && <Skeleton className="size-8 rounded-md" />}
28
+ </div>
29
+ <div className="mt-4 flex items-end justify-between gap-4">
30
+ <div className="flex flex-col gap-1.5">
31
+ <Skeleton className="h-7 w-28 rounded" />
32
+ <Skeleton className="h-3.5 w-20 rounded" />
33
+ </div>
34
+ {chart && <Skeleton className="h-8 w-20 rounded" />}
35
+ </div>
36
+ </div>
37
+ );
38
+ };
39
+
40
+ KPICardSkeleton.displayName = "KPICardSkeleton";
41
+
42
+ export { KPICardSkeleton };
@@ -1,2 +1,4 @@
1
1
  export { KPICard } from "./KPICard";
2
+ export { KPICardSkeleton } from "./KPICardSkeleton";
2
3
  export type { KPICardProps } from "./types";
4
+ export type { KPICardSkeletonProps } from "./KPICardSkeleton";
@@ -13,6 +13,4 @@ export type KPICardProps = HTMLAttributes<HTMLDivElement> & {
13
13
  chart?: ReactNode;
14
14
  /** Icon to show in the card */
15
15
  icon?: ReactNode;
16
- /** Loading state */
17
- loading?: boolean;
18
16
  };
@@ -1,17 +1,14 @@
1
1
  import { AlertCircleIcon, InboxIcon, RefreshCwIcon } from "lucide-react";
2
- import { cn } from "../../utilities";
3
2
  import type { WidgetShellProps } from "./types";
3
+ import { cn } from "../../utilities";
4
+ import { Button } from "../Button";
4
5
 
5
6
  const WidgetShell = ({
6
- state = "ready",
7
7
  title,
8
8
  description,
9
+ action,
9
10
  children,
10
11
  className,
11
- errorContent,
12
- emptyContent,
13
- skeletonLines = 3,
14
- onRetry,
15
12
  ...props
16
13
  }: WidgetShellProps) => {
17
14
  return (
@@ -22,80 +19,85 @@ const WidgetShell = ({
22
19
  )}
23
20
  {...props}
24
21
  >
25
- {(title || description) && (
26
- <div className="border-b px-4 py-3">
27
- {title && (
28
- <h3 className="text-sm font-semibold">
29
- {state === "loading" ? (
30
- <span className="skeleton inline-block h-4 w-32 rounded" />
31
- ) : (
32
- title
33
- )}
34
- </h3>
35
- )}
36
- {description && (
37
- <p className="mt-0.5 text-xs text-base-content/60">
38
- {state === "loading" ? (
39
- <span className="skeleton inline-block h-3 w-48 rounded" />
40
- ) : (
41
- description
42
- )}
43
- </p>
44
- )}
22
+ {(title || description || action) && (
23
+ <div className="flex items-start justify-between border-b px-4 py-3">
24
+ <div className="min-w-0">
25
+ {title && <h3 className="text-sm font-semibold">{title}</h3>}
26
+ {description && <p className="mt-0.5 text-xs text-base-content/60">{description}</p>}
27
+ </div>
28
+ {action && <div className="shrink-0">{action}</div>}
45
29
  </div>
46
30
  )}
47
31
 
48
- <div className="p-4">
49
- {state === "loading" && (
50
- <div className="flex flex-col gap-3" role="status" aria-label="Loading">
51
- {Array.from({ length: skeletonLines }, (_, i) => (
52
- <span
53
- key={i}
54
- className="skeleton h-4 rounded"
55
- style={{ width: `${100 - i * 15}%` }}
56
- />
57
- ))}
58
- </div>
59
- )}
32
+ <div className="p-4">{children}</div>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ WidgetShell.displayName = "WidgetShell";
60
38
 
61
- {state === "error" &&
62
- (errorContent ?? (
63
- <div className="flex flex-col items-center justify-center gap-3 py-6 text-center">
64
- <AlertCircleIcon className="size-8 text-error/60" />
65
- <div>
66
- <p className="text-sm font-medium">Something went wrong</p>
67
- <p className="text-xs text-base-content/60">Failed to load data</p>
68
- </div>
69
- {onRetry && (
70
- <button
71
- type="button"
72
- onClick={onRetry}
73
- className="inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-xs font-medium transition-colors duration-200 hover:bg-base-200"
74
- >
75
- <RefreshCwIcon className="size-3" />
76
- Retry
77
- </button>
78
- )}
79
- </div>
80
- ))}
39
+ const WidgetShellEmpty = ({ children }: { children?: React.ReactNode }) => {
40
+ return (
41
+ children ?? (
42
+ <div className="flex flex-col items-center justify-center gap-3 py-6 text-center">
43
+ <InboxIcon className="size-8 text-base-content/30" />
44
+ <div>
45
+ <p className="text-sm font-medium">No data</p>
46
+ <p className="text-xs text-base-content/60">Nothing to display yet</p>
47
+ </div>
48
+ </div>
49
+ )
50
+ );
51
+ };
81
52
 
82
- {state === "empty" &&
83
- (emptyContent ?? (
84
- <div className="flex flex-col items-center justify-center gap-3 py-6 text-center">
85
- <InboxIcon className="size-8 text-base-content/30" />
86
- <div>
87
- <p className="text-sm font-medium">No data</p>
88
- <p className="text-xs text-base-content/60">Nothing to display yet</p>
89
- </div>
90
- </div>
91
- ))}
53
+ WidgetShellEmpty.displayName = "WidgetShellEmpty";
92
54
 
93
- {state === "ready" && children}
55
+ const WidgetShellError = ({
56
+ children,
57
+ onRetry,
58
+ }: {
59
+ children?: React.ReactNode;
60
+ onRetry?: () => void;
61
+ }) => {
62
+ return (
63
+ children ?? (
64
+ <div className="flex flex-col items-center justify-center gap-3 py-6 text-center">
65
+ <AlertCircleIcon className="size-8 text-error/60" />
66
+ <div>
67
+ <p className="text-sm font-medium">Something went wrong</p>
68
+ <p className="text-xs text-base-content/60">Failed to load data</p>
69
+ </div>
70
+ {onRetry && (
71
+ <Button
72
+ type="button"
73
+ onClick={onRetry}
74
+ className="inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-xs font-medium transition-colors duration-200 hover:bg-base-200"
75
+ >
76
+ <RefreshCwIcon className="size-3 shrink-0" />
77
+ Retry
78
+ </Button>
79
+ )}
94
80
  </div>
81
+ )
82
+ );
83
+ };
84
+
85
+ WidgetShellError.displayName = "WidgetShellError";
86
+
87
+ const WidgetShellLoading = ({ lines = 4 }: { lines?: number }) => {
88
+ return (
89
+ <div className="flex flex-col gap-3" role="status" aria-label="Loading">
90
+ {Array.from({ length: lines }, (_, i) => (
91
+ <span key={i} className="skeleton h-4 rounded" style={{ width: `${100 - i * 15}%` }} />
92
+ ))}
95
93
  </div>
96
94
  );
97
95
  };
98
96
 
99
- WidgetShell.displayName = "WidgetShell";
97
+ WidgetShellLoading.displayName = "WidgetShellLoading";
98
+
99
+ WidgetShell.Empty = WidgetShellEmpty;
100
+ WidgetShell.Error = WidgetShellError;
101
+ WidgetShell.Loading = WidgetShellLoading;
100
102
 
101
103
  export { WidgetShell };
@@ -0,0 +1,47 @@
1
+ import { cn } from "../../utilities";
2
+
3
+ export type WidgetShellSkeletonProps = React.HTMLAttributes<HTMLDivElement> & {
4
+ /** Number of skeleton lines to show */
5
+ lines?: number;
6
+ /** Whether to show the header skeleton */
7
+ header?: boolean;
8
+ };
9
+
10
+ const WidgetShellSkeleton = ({
11
+ lines = 3,
12
+ header = true,
13
+ className,
14
+ ...props
15
+ }: WidgetShellSkeletonProps) => {
16
+ return (
17
+ <div
18
+ className={cn(
19
+ "rounded-lg border bg-base-100 shadow-sm",
20
+ className
21
+ )}
22
+ {...props}
23
+ >
24
+ {header && (
25
+ <div className="border-b px-4 py-3">
26
+ <span className="skeleton inline-block h-4 w-32 rounded" />
27
+ <span className="skeleton mt-1.5 inline-block h-3 w-48 rounded" />
28
+ </div>
29
+ )}
30
+ <div className="p-4">
31
+ <div className="flex flex-col gap-3" role="status" aria-label="Loading">
32
+ {Array.from({ length: lines }, (_, i) => (
33
+ <span
34
+ key={i}
35
+ className="skeleton h-4 rounded"
36
+ style={{ width: `${100 - i * 15}%` }}
37
+ />
38
+ ))}
39
+ </div>
40
+ </div>
41
+ </div>
42
+ );
43
+ };
44
+
45
+ WidgetShellSkeleton.displayName = "WidgetShellSkeleton";
46
+
47
+ export { WidgetShellSkeleton };
@@ -1,2 +1,4 @@
1
1
  export { WidgetShell } from "./WidgetShell";
2
- export type { WidgetShellProps, WidgetShellState } from "./types";
2
+ export { WidgetShellSkeleton } from "./WidgetShellSkeleton";
3
+ export type { WidgetShellProps } from "./types";
4
+ export type { WidgetShellSkeletonProps } from "./WidgetShellSkeleton";
@@ -1,22 +1,12 @@
1
1
  import type { HTMLAttributes, ReactNode } from "react";
2
2
 
3
- export type WidgetShellState = "loading" | "error" | "empty" | "ready";
4
-
5
3
  export type WidgetShellProps = HTMLAttributes<HTMLDivElement> & {
6
- /** Current state of the widget */
7
- state?: WidgetShellState;
8
4
  /** Widget title */
9
5
  title?: ReactNode;
10
6
  /** Widget description */
11
7
  description?: ReactNode;
8
+ /** Content rendered in the top-right of the header (e.g. actions, icons, badges) */
9
+ action?: ReactNode;
12
10
  /** Content to show when state is "ready" */
13
11
  children: ReactNode;
14
- /** Custom content for error state */
15
- errorContent?: ReactNode;
16
- /** Custom content for empty state */
17
- emptyContent?: ReactNode;
18
- /** Number of skeleton lines to show in loading state */
19
- skeletonLines?: number;
20
- /** Callback when retry is clicked in error state */
21
- onRetry?: () => void;
22
12
  };
package/src/index.ts CHANGED
@@ -92,6 +92,7 @@ export * from "./components/WidgetShell";
92
92
 
93
93
  /** molecules */
94
94
  export * from "./components/Banner";
95
+ export * from "./components/Drawer";
95
96
  export * from "./components/Empty";
96
97
  export * from "./components/Sidebar";
97
98
  export * from "./components/Stepper";