lupine.components 1.1.31 → 1.1.33

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 (47) hide show
  1. package/package.json +1 -1
  2. package/src/component-pool/cascader/cascader.tsx +3 -0
  3. package/src/component-pool/charts/area-chart-demo.tsx +110 -0
  4. package/src/component-pool/charts/area-chart.tsx +251 -0
  5. package/src/component-pool/charts/bar-chart-demo.tsx +110 -0
  6. package/src/component-pool/charts/bar-chart.tsx +220 -0
  7. package/src/component-pool/charts/chart-utils.ts +181 -0
  8. package/src/component-pool/charts/column-chart-demo.tsx +114 -0
  9. package/src/component-pool/charts/column-chart.tsx +227 -0
  10. package/src/component-pool/charts/donut-chart-demo.tsx +115 -0
  11. package/src/component-pool/charts/donut-chart.tsx +28 -0
  12. package/src/component-pool/charts/gauge-chart-demo.tsx +105 -0
  13. package/src/component-pool/charts/gauge-chart.tsx +113 -0
  14. package/src/component-pool/charts/index.ts +19 -0
  15. package/src/component-pool/charts/line-chart-demo.tsx +113 -0
  16. package/src/component-pool/charts/line-chart.tsx +230 -0
  17. package/src/component-pool/charts/pie-chart-demo.tsx +139 -0
  18. package/src/component-pool/charts/pie-chart.tsx +125 -0
  19. package/src/component-pool/charts/radar-chart-demo.tsx +91 -0
  20. package/src/component-pool/charts/radar-chart.tsx +196 -0
  21. package/src/component-pool/charts/scatter-chart-demo.tsx +91 -0
  22. package/src/component-pool/charts/scatter-chart.tsx +234 -0
  23. package/src/component-pool/picker-helper.tsx +50 -19
  24. package/src/component-pool/radial-progress/radial-progress-demo.tsx +0 -1
  25. package/src/component-pool/svg-icons.ts +3 -1
  26. package/src/component-pool/tooltip/tooltip.tsx +1 -3
  27. package/src/components/action-sheet.tsx +14 -2
  28. package/src/components/button-demo.tsx +101 -98
  29. package/src/components/button-push-animation-demo.tsx +71 -83
  30. package/src/components/editable-label-demo.tsx +39 -36
  31. package/src/components/index.ts +1 -0
  32. package/src/components/input-number-demo.tsx +131 -163
  33. package/src/components/loading-spin-demo.tsx +37 -0
  34. package/src/components/loading-spin.tsx +59 -0
  35. package/src/components/menu-item-props.tsx +3 -0
  36. package/src/components/mobile-components/mobile-side-menu.tsx +26 -9
  37. package/src/components/notice-message-demo.tsx +21 -24
  38. package/src/components/notice-message.tsx +66 -17
  39. package/src/components/paging-link-demo.tsx +74 -90
  40. package/src/components/popup-menu-demo.tsx +128 -24
  41. package/src/components/popup-menu.tsx +454 -207
  42. package/src/components/toggle-switch-demo.tsx +227 -224
  43. package/src/demo/demo-frame-helper.tsx +242 -148
  44. package/src/demo/demo-registry.ts +22 -0
  45. package/src/demo/demo-render-page.tsx +2 -1
  46. package/src/demo/mock/demo-icons.ts +4 -0
  47. package/src/frames/responsive-frame.tsx +11 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lupine.components",
3
- "version": "1.1.31",
3
+ "version": "1.1.33",
4
4
  "license": "MIT",
5
5
  "author": "uuware.com",
6
6
  "homepage": "https://github.com/uuware/lupine.js",
@@ -12,6 +12,8 @@ export type CascaderProps = {
12
12
  defaultOpen?: boolean;
13
13
  showCircle?: boolean; // New prop for circular button styling
14
14
  children?: any;
15
+ key?: string;
16
+ onClick?: () => void;
15
17
  };
16
18
 
17
19
  const cascaderCss: CssProps = {
@@ -137,6 +139,7 @@ export const Cascader = (props: CascaderProps) => {
137
139
  if (!el) return;
138
140
 
139
141
  const nextState = !el.classList.contains('open');
142
+ if (props.onClick) props.onClick();
140
143
 
141
144
  if (nextState && props.group) {
142
145
  const others = document.querySelectorAll(`[data-cascader-group="${props.group}"]`);
@@ -0,0 +1,110 @@
1
+ import { AreaChart } from './area-chart';
2
+ import { DemoStory } from '../../demo/demo-types';
3
+ import { ChartData } from './chart-utils';
4
+
5
+ const multiSeriesData: ChartData = {
6
+ labels: ['2019', '2020', '2021', '2022', '2023', '2024'],
7
+ series: [
8
+ { name: 'Cloud Revenue', data: [15, 22, 35, 48, 65, 80] },
9
+ { name: 'Hardware Sales', data: [50, 48, 45, 40, 35, 30] },
10
+ ],
11
+ };
12
+
13
+ export const areaChartDemo: DemoStory<any> = {
14
+ id: 'areaChartDemo',
15
+ text: 'Area Chart',
16
+ args: {
17
+ title: 'Company Revenues',
18
+ width: '100%',
19
+ height: '350px',
20
+ showLegend: true,
21
+ curved: true,
22
+ },
23
+ argTypes: {
24
+ title: { control: 'text' },
25
+ width: { control: 'text' },
26
+ height: { control: 'text' },
27
+ showLegend: { control: 'boolean' },
28
+ curved: { control: 'boolean' },
29
+ },
30
+ render: (args: any) => {
31
+ const css = {
32
+ display: 'flex',
33
+ flexDirection: 'column',
34
+ gap: '48px',
35
+ padding: '24px',
36
+ alignItems: 'center',
37
+
38
+ '.demo-section': {
39
+ display: 'flex',
40
+ flexDirection: 'column',
41
+ alignItems: 'center',
42
+ gap: '24px',
43
+ width: '100%',
44
+ maxWidth: '800px',
45
+ },
46
+ '.section-title': {
47
+ fontSize: '20px',
48
+ fontWeight: 'bold',
49
+ color: 'var(--primary-color)',
50
+ marginBottom: '16px',
51
+ borderBottom: '2px solid var(--secondary-border-color)',
52
+ width: '100%',
53
+ paddingBottom: '8px',
54
+ },
55
+ '.chart-box': {
56
+ padding: '20px',
57
+ border: '1px solid var(--secondary-border-color)',
58
+ borderRadius: '8px',
59
+ backgroundColor: 'var(--primary-bg-color)',
60
+ width: '100%',
61
+ },
62
+ };
63
+
64
+ return (
65
+ <div css={css}>
66
+ <section class='demo-section'>
67
+ <div class='section-title'>Interactive Control</div>
68
+ <div class='chart-box'>
69
+ <AreaChart
70
+ data={multiSeriesData}
71
+ title={args.title}
72
+ width={args.width}
73
+ height={args.height}
74
+ showLegend={args.showLegend}
75
+ curved={args.curved}
76
+ yAxisFormatter={(val) => '$' + val + 'M'}
77
+ />
78
+ </div>
79
+ </section>
80
+
81
+ <section class='demo-section'>
82
+ <div class='section-title'>Straight Area Chart</div>
83
+ <div class='chart-box'>
84
+ <AreaChart
85
+ data={multiSeriesData}
86
+ title='Straight Area Comparison'
87
+ height='300px'
88
+ showLegend={true}
89
+ curved={false}
90
+ yAxisFormatter={(val) => '$' + val + 'M'}
91
+ />
92
+ </div>
93
+ </section>
94
+ </div>
95
+ );
96
+ },
97
+ code: `import { AreaChart } from 'lupine.components/component-pool/charts';
98
+
99
+ const data = {
100
+ labels: ['Q1', 'Q2', 'Q3'],
101
+ series: [{ name: 'Growth', data: [10, 25, 45] }]
102
+ };
103
+
104
+ <AreaChart
105
+ data={data}
106
+ title="Growth Area"
107
+ curved={true}
108
+ />
109
+ `,
110
+ };
@@ -0,0 +1,251 @@
1
+ import { bindGlobalStyle, getGlobalStylesId, HtmlVar } from 'lupine.components';
2
+ import { chartCommonCss, getChartColor, BasicChartProps } from './chart-utils';
3
+ import { Tooltip } from '../tooltip';
4
+
5
+ export type AreaChartProps = BasicChartProps & {
6
+ yAxisFormatter?: (value: number) => string;
7
+ curved?: boolean;
8
+ };
9
+
10
+ export const AreaChart = (props: AreaChartProps) => {
11
+ const globalCssId = getGlobalStylesId(chartCommonCss);
12
+ bindGlobalStyle(globalCssId, chartCommonCss);
13
+
14
+ const showLegend = props.showLegend !== false;
15
+ const series = props.data.series;
16
+ const labels = props.data.labels;
17
+
18
+ if (!series || series.length === 0 || labels.length === 0) {
19
+ return <div class='&-container'>No data</div>;
20
+ }
21
+
22
+ // Find max value for Y axis scale
23
+ let maxVal = 0;
24
+ series.forEach((s) => {
25
+ s.data.forEach((val) => {
26
+ if (val > maxVal) maxVal = val;
27
+ });
28
+ });
29
+
30
+ const tickCount = 5;
31
+ const order = Math.floor(Math.log10(maxVal || 1));
32
+ const magnitude = Math.pow(10, order);
33
+ const niceStep = Math.ceil(maxVal / magnitude / tickCount) * magnitude;
34
+ const niceMax = niceStep * tickCount;
35
+
36
+ const yTicks = Array.from({ length: tickCount + 1 }, (_, i) => parseFloat((i * niceStep).toPrecision(12)));
37
+
38
+ const renderChart = (viewBoxWidth: number, viewBoxHeight: number) => {
39
+ // Layout parameters
40
+ const padding = { top: 20, right: 80, bottom: 40, left: 60 };
41
+
42
+ const chartWidth = viewBoxWidth - padding.left - padding.right;
43
+ const chartHeight = viewBoxHeight - padding.top - padding.bottom;
44
+
45
+ // Calculate points considering a half step padding on left and right for area/line
46
+ const pointStep = chartWidth / Math.max(1, labels.length);
47
+ const dataLeftPadding = pointStep / 2;
48
+
49
+ const formatY = props.yAxisFormatter || ((val) => val.toString());
50
+
51
+ // Render Y Axis
52
+ const renderYAxis = () => {
53
+ return yTicks.map((val) => {
54
+ const y = padding.top + chartHeight - (val / niceMax) * chartHeight;
55
+ return (
56
+ <g>
57
+ <line
58
+ x1={padding.left}
59
+ y1={y}
60
+ x2={viewBoxWidth - padding.right}
61
+ y2={y}
62
+ stroke='var(--secondary-border-color)'
63
+ stroke-width='1'
64
+ stroke-dasharray='4 4'
65
+ />
66
+ <text x={padding.left - 15} y={y + 4} fill='var(--primary-color)' fontSize='12' text-anchor='end'>
67
+ {formatY(val)}
68
+ </text>
69
+ </g>
70
+ );
71
+ });
72
+ };
73
+
74
+ // Render X Axis
75
+ const renderXAxis = () => {
76
+ return labels.map((label, index) => {
77
+ const x = padding.left + dataLeftPadding + index * pointStep;
78
+ const y = viewBoxHeight - padding.bottom + 25;
79
+ return (
80
+ <text x={x} y={y} fill='var(--primary-color)' fontSize='12' text-anchor='middle'>
81
+ {label}
82
+ </text>
83
+ );
84
+ });
85
+ };
86
+
87
+ // Render Areas, Lines & Points
88
+ const renderAreasLinesAndPoints = () => {
89
+ const areas: any[] = [];
90
+ const lines: any[] = [];
91
+ const points: any[] = [];
92
+
93
+ // Render area in reverse order so first series is drawn last (on top) if they overlap
94
+ // Note: Better visualization is sometimes stacking, but basic is overlapping
95
+ series
96
+ .slice()
97
+ .reverse()
98
+ .forEach((s, revIndex) => {
99
+ const sIndex = series.length - 1 - revIndex;
100
+ const color = s.color || getChartColor(sIndex);
101
+
102
+ const coordinates = s.data.map((val, lIndex) => {
103
+ const x = padding.left + dataLeftPadding + lIndex * pointStep;
104
+ const y = padding.top + chartHeight - (val / niceMax) * chartHeight;
105
+ return { x, y, val, label: labels[lIndex] };
106
+ });
107
+
108
+ // Draw Path Base
109
+ let linePath = '';
110
+ if (props.curved && coordinates.length > 2) {
111
+ linePath = `M ${coordinates[0].x} ${coordinates[0].y} `;
112
+ for (let i = 0; i < coordinates.length - 1; i++) {
113
+ const current = coordinates[i];
114
+ const next = coordinates[i + 1];
115
+ const mx = (current.x + next.x) / 2;
116
+ linePath += `C ${mx} ${current.y}, ${mx} ${next.y}, ${next.x} ${next.y} `;
117
+ }
118
+ } else {
119
+ linePath = 'M ' + coordinates.map((c) => `${c.x} ${c.y}`).join(' L ');
120
+ }
121
+
122
+ const endX = coordinates[coordinates.length - 1].x;
123
+ const startX = coordinates[0].x;
124
+ const baseY = padding.top + chartHeight;
125
+
126
+ const areaPath = `${linePath} L ${endX} ${baseY} L ${startX} ${baseY} Z`;
127
+
128
+ areas.push(
129
+ <path
130
+ d={areaPath}
131
+ fill={color}
132
+ opacity='0.3' // Transparency for area
133
+ />
134
+ );
135
+
136
+ lines.push(<path d={linePath} fill='none' stroke={color} stroke-width='3' />);
137
+
138
+ // Draw interactive points overlay
139
+ coordinates.forEach((c) => {
140
+ const handleMouseEnter = (e: any) => {
141
+ Tooltip.show(
142
+ e,
143
+ <div>
144
+ <div style={{ fontWeight: 'bold' }}>{c.label}</div>
145
+ <div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
146
+ <div style={{ width: '10px', height: '10px', backgroundColor: color, borderRadius: '2px' }} />
147
+ <span>
148
+ {s.name}: {formatY(c.val)}
149
+ </span>
150
+ </div>
151
+ </div>,
152
+ { position: 'auto' }
153
+ );
154
+ e.target.setAttribute('r', '6');
155
+ };
156
+
157
+ const handleMouseLeave = (e: any) => {
158
+ Tooltip.hide();
159
+ e.target.setAttribute('r', '4');
160
+ };
161
+
162
+ points.push(
163
+ <circle
164
+ class='chart-element'
165
+ cx={c.x}
166
+ cy={c.y}
167
+ r='4'
168
+ fill='var(--primary-bg-color)'
169
+ stroke={color}
170
+ stroke-width='2'
171
+ onMouseEnter={handleMouseEnter}
172
+ onMouseLeave={handleMouseLeave}
173
+ style={{ transition: 'r 0.2s ease' }}
174
+ />
175
+ );
176
+ });
177
+ });
178
+
179
+ return { areas, lines, points };
180
+ };
181
+
182
+ const { areas, lines, points } = renderAreasLinesAndPoints();
183
+
184
+ return (
185
+ <svg
186
+ class='chart-svg'
187
+ style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', overflow: 'visible' }}
188
+ viewBox={`0 0 ${viewBoxWidth} ${viewBoxHeight}`}
189
+ preserveAspectRatio='none'
190
+ >
191
+ {/* Base Axis line */}
192
+ <line
193
+ x1={padding.left}
194
+ y1={padding.top + chartHeight}
195
+ x2={viewBoxWidth - padding.right}
196
+ y2={padding.top + chartHeight}
197
+ stroke='var(--secondary-color)'
198
+ stroke-width='2'
199
+ />
200
+
201
+ <g class='y-axis'>{renderYAxis()}</g>
202
+ <g class='x-axis'>{renderXAxis()}</g>
203
+ <g class='areas'>{areas}</g>
204
+ <g class='lines'>{lines}</g>
205
+ <g class='points'>{points}</g>
206
+ </svg>
207
+ );
208
+ };
209
+
210
+ const chartVar = new HtmlVar(renderChart(1000, 300));
211
+
212
+ const ref = {
213
+ globalCssId,
214
+ onLoad: async (el: Element) => {
215
+ const ro = new ResizeObserver((entries) => {
216
+ const { width, height } = entries[0].contentRect;
217
+ if (width > 50 && height > 50) {
218
+ chartVar.value = renderChart(width, height);
219
+ }
220
+ });
221
+ ro.observe(el);
222
+ (el as any)._ro = ro;
223
+ },
224
+ onUnload: async (el: Element) => {
225
+ if ((el as any)._ro) {
226
+ (el as any)._ro.disconnect();
227
+ }
228
+ },
229
+ };
230
+
231
+ const styleStr = `width: ${props.width || '100%'}; height: ${props.height || '100%'};`;
232
+
233
+ return (
234
+ <div ref={ref} class='&-container' style={styleStr}>
235
+ {props.title && <div class='chart-title'>{props.title}</div>}
236
+
237
+ <div style={{ flex: 1, position: 'relative', minHeight: 0 }}>{chartVar.node}</div>
238
+
239
+ {showLegend && (
240
+ <div class='chart-legend'>
241
+ {series.map((s, i) => (
242
+ <div class='legend-item'>
243
+ <div class='legend-color' style={{ backgroundColor: s.color || getChartColor(i) }} />
244
+ <div>{s.name}</div>
245
+ </div>
246
+ ))}
247
+ </div>
248
+ )}
249
+ </div>
250
+ );
251
+ };
@@ -0,0 +1,110 @@
1
+ import { BarChart } from './bar-chart';
2
+ import { DemoStory } from '../../demo/demo-types';
3
+ import { ChartData } from './chart-utils';
4
+
5
+ const singleSeriesData: ChartData = {
6
+ labels: ['United States', 'China', 'Japan', 'Germany', 'United Kingdom'],
7
+ series: [
8
+ {
9
+ name: 'GDP (Trillions)',
10
+ data: [25.4, 17.9, 4.2, 4.0, 3.0],
11
+ },
12
+ ],
13
+ };
14
+
15
+ const multiSeriesData: ChartData = {
16
+ labels: ['Q1', 'Q2', 'Q3', 'Q4'],
17
+ series: [
18
+ { name: 'Product A', data: [40, 30, 50, 70] },
19
+ { name: 'Product B', data: [20, 40, 30, 60] },
20
+ ],
21
+ };
22
+
23
+ export const barChartDemo: DemoStory<any> = {
24
+ id: 'barChartDemo',
25
+ text: 'Bar Chart',
26
+ args: {
27
+ title: 'Top Economies',
28
+ width: '100%',
29
+ height: '350px',
30
+ showLegend: true,
31
+ },
32
+ argTypes: {
33
+ title: { control: 'text' },
34
+ width: { control: 'text' },
35
+ height: { control: 'text' },
36
+ showLegend: { control: 'boolean' },
37
+ },
38
+ render: (args: any) => {
39
+ const css = {
40
+ display: 'flex',
41
+ flexDirection: 'column',
42
+ gap: '48px',
43
+ padding: '24px',
44
+ alignItems: 'center',
45
+
46
+ '.demo-section': {
47
+ display: 'flex',
48
+ flexDirection: 'column',
49
+ alignItems: 'center',
50
+ gap: '24px',
51
+ width: '100%',
52
+ maxWidth: '800px',
53
+ },
54
+ '.section-title': {
55
+ fontSize: '20px',
56
+ fontWeight: 'bold',
57
+ color: 'var(--primary-color)',
58
+ marginBottom: '16px',
59
+ borderBottom: '2px solid var(--secondary-border-color)',
60
+ width: '100%',
61
+ paddingBottom: '8px',
62
+ },
63
+ '.chart-box': {
64
+ padding: '20px',
65
+ border: '1px solid var(--secondary-border-color)',
66
+ borderRadius: '8px',
67
+ backgroundColor: 'var(--primary-bg-color)',
68
+ width: '100%',
69
+ },
70
+ };
71
+
72
+ return (
73
+ <div css={css}>
74
+ <section class='demo-section'>
75
+ <div class='section-title'>Interactive Control</div>
76
+ <div class='chart-box'>
77
+ <BarChart
78
+ data={singleSeriesData}
79
+ title={args.title}
80
+ width={args.width}
81
+ height={args.height}
82
+ showLegend={args.showLegend}
83
+ xAxisFormatter={(val) => '$' + val + 'T'}
84
+ />
85
+ </div>
86
+ </section>
87
+
88
+ <section class='demo-section'>
89
+ <div class='section-title'>Multi-Series (Grouped Horizontal)</div>
90
+ <div class='chart-box'>
91
+ <BarChart data={multiSeriesData} title='Quarterly Comparison' height='300px' showLegend={true} />
92
+ </div>
93
+ </section>
94
+ </div>
95
+ );
96
+ },
97
+ code: `import { BarChart } from 'lupine.components/component-pool/charts';
98
+
99
+ const data = {
100
+ labels: ['US', 'CN', 'JP'],
101
+ series: [{ name: 'GDP', data: [25, 18, 4] }]
102
+ };
103
+
104
+ <BarChart
105
+ data={data}
106
+ title="Top Economies"
107
+ xAxisFormatter={val => '$' + val + 'T'}
108
+ />
109
+ `,
110
+ };