indas-ui 0.1.3 → 0.1.4

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 (59) hide show
  1. package/dist/cjs/components/chat/ai-messages.js +3 -2
  2. package/dist/cjs/components/chat/chat.js +2 -1
  3. package/dist/cjs/components/dashboard/charts/Area.js +6 -4
  4. package/dist/cjs/components/dashboard/charts/Bar.js +6 -4
  5. package/dist/cjs/components/dashboard/charts/FlowMap.js +2 -1
  6. package/dist/cjs/components/dashboard/charts/Funnel.js +2 -1
  7. package/dist/cjs/components/dashboard/charts/GeoMap.js +2 -1
  8. package/dist/cjs/components/dashboard/charts/Line.js +5 -3
  9. package/dist/cjs/components/dashboard/charts/Pareto.js +2 -1
  10. package/dist/cjs/components/dashboard/charts/Sankey.js +4 -3
  11. package/dist/cjs/components/dashboard/charts/Sparkline.js +2 -1
  12. package/dist/cjs/components/dashboard/charts/Treemap.js +2 -1
  13. package/dist/cjs/components/dashboard/charts/index.js +7 -1
  14. package/dist/cjs/components/dashboard/kpi/KpiBreakdown.js +3 -2
  15. package/dist/cjs/components/dashboard/kpi/KpiComparison.js +2 -1
  16. package/dist/cjs/components/dashboard/kpi/KpiQuickStats.js +2 -1
  17. package/dist/cjs/components/dashboard/kpi/KpiRating.js +3 -2
  18. package/dist/cjs/components/datagrid/columns/ColumnContextMenu.js +2 -1
  19. package/dist/cjs/components/datagrid/controls/menu/view/CardView.js +2 -1
  20. package/dist/cjs/components/datagrid/controls/menu/view/ChartView.js +2 -1
  21. package/dist/cjs/components/datagrid/utils/grid-helpers.js +6 -5
  22. package/dist/cjs/components/layout/footer.js +2 -1
  23. package/dist/cjs/components/layout/side-panel.js +4 -1
  24. package/dist/cjs/components/ui/display/stats-card.js +2 -1
  25. package/dist/cjs/lib/number-format.js +60 -0
  26. package/dist/cjs/lib/utils.js +4 -3
  27. package/dist/components/chat/ai-messages.js +3 -2
  28. package/dist/components/chat/chat.js +2 -1
  29. package/dist/components/dashboard/charts/Area.d.ts +5 -1
  30. package/dist/components/dashboard/charts/Area.js +6 -4
  31. package/dist/components/dashboard/charts/Bar.d.ts +5 -1
  32. package/dist/components/dashboard/charts/Bar.js +6 -4
  33. package/dist/components/dashboard/charts/FlowMap.js +2 -1
  34. package/dist/components/dashboard/charts/Funnel.js +2 -1
  35. package/dist/components/dashboard/charts/GeoMap.js +2 -1
  36. package/dist/components/dashboard/charts/Line.d.ts +5 -1
  37. package/dist/components/dashboard/charts/Line.js +5 -3
  38. package/dist/components/dashboard/charts/Pareto.js +2 -1
  39. package/dist/components/dashboard/charts/Sankey.js +4 -3
  40. package/dist/components/dashboard/charts/Sparkline.js +2 -1
  41. package/dist/components/dashboard/charts/Treemap.js +2 -1
  42. package/dist/components/dashboard/charts/index.d.ts +1 -0
  43. package/dist/components/dashboard/charts/index.js +2 -0
  44. package/dist/components/dashboard/kpi/KpiBreakdown.js +3 -2
  45. package/dist/components/dashboard/kpi/KpiComparison.js +2 -1
  46. package/dist/components/dashboard/kpi/KpiQuickStats.js +2 -1
  47. package/dist/components/dashboard/kpi/KpiRating.js +3 -2
  48. package/dist/components/datagrid/columns/ColumnContextMenu.js +2 -1
  49. package/dist/components/datagrid/controls/menu/view/CardView.js +2 -1
  50. package/dist/components/datagrid/controls/menu/view/ChartView.js +2 -1
  51. package/dist/components/datagrid/utils/grid-helpers.d.ts +1 -1
  52. package/dist/components/datagrid/utils/grid-helpers.js +3 -2
  53. package/dist/components/layout/footer.js +2 -1
  54. package/dist/components/layout/side-panel.js +4 -1
  55. package/dist/components/ui/display/stats-card.js +2 -1
  56. package/dist/lib/number-format.d.ts +18 -0
  57. package/dist/lib/number-format.js +54 -0
  58. package/dist/lib/utils.js +4 -3
  59. package/package.json +4 -4
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { TrendingUp, TrendingDown, Info } from 'lucide-react';
4
4
  import { cn } from '../../../lib/utils.js';
5
+ import { formatNumber } from '../../../lib/number-format.js';
5
6
  /**
6
7
  * KpiRating - KPI card with star rating display
7
8
  *
@@ -27,9 +28,9 @@ export function KpiRating({ title, score, maxScore = 5, reviewCount, status, sta
27
28
  return 'bg-[rgb(var(--color-warning))]';
28
29
  return 'bg-[rgb(var(--color-error))]';
29
30
  };
30
- return (_jsxs("div", { className: cn('bg-[rgb(var(--bg-surface))] rounded-xl p-5 border border-[rgb(var(--bd-default))] shadow-sm relative group', className), children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("p", { className: "text-sm text-[rgb(var(--fg-muted))]", children: title }), tooltip && (_jsxs("div", { className: "relative", children: [_jsx("div", { className: "w-5 h-5 rounded-full bg-[rgb(var(--bg-subtle))] flex items-center justify-center cursor-help hover:bg-[rgb(var(--color-primary))]/10 transition-colors peer", children: _jsx(Info, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) }), _jsxs("div", { className: "absolute right-0 top-7 w-72 p-3 bg-[rgb(var(--bg-surface))] border border-[rgb(var(--bd-default))] rounded-lg shadow-lg opacity-0 invisible peer-hover:opacity-100 peer-hover:visible transition-all z-10", children: [tooltip.formula && (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-xs font-medium text-[rgb(var(--fg-default))] mb-2", children: "Formula:" }), _jsx("div", { className: "bg-[rgb(var(--bg-subtle))] rounded p-2 mb-3", children: _jsx("p", { className: "text-[10px] font-mono text-[rgb(var(--fg-muted))]", children: tooltip.formula }) })] })), tooltip.description && (_jsx("p", { className: "text-[10px] text-[rgb(var(--fg-muted))] mb-3", children: tooltip.description })), tooltip.breakdown && tooltip.breakdown.length > 0 && (_jsxs("div", { className: "border-t border-[rgb(var(--bd-default))] pt-3", children: [_jsx("p", { className: "text-xs font-medium text-[rgb(var(--fg-default))] mb-2", children: "Rating Breakdown:" }), _jsx("div", { className: "space-y-1.5", children: tooltip.breakdown.map((item) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] text-[rgb(var(--fg-muted))] w-3", children: item.stars }), _jsx("svg", { className: "w-3 h-3 text-yellow-400", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" }) }), _jsx("div", { className: "flex-1 h-1.5 bg-[rgb(var(--bg-subtle))] rounded-full overflow-hidden", children: _jsx("div", { className: cn('h-full rounded-full', getBreakdownBarColor(item.percentage)), style: { width: `${item.percentage}%` } }) }), _jsx("span", { className: "text-[10px] text-[rgb(var(--fg-muted))] w-10 text-right", children: item.count.toLocaleString() }), _jsxs("span", { className: "text-[10px] text-[rgb(var(--fg-muted))] w-6 text-right", children: [item.percentage, "%"] })] }, item.stars))) })] }))] })] }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { children: _jsx("p", { className: "text-4xl font-bold text-[rgb(var(--fg-default))]", children: score }) }), _jsxs("div", { children: [_jsx("div", { className: "flex gap-0.5", children: Array.from({ length: maxScore }, (_, i) => {
31
+ return (_jsxs("div", { className: cn('bg-[rgb(var(--bg-surface))] rounded-xl p-5 border border-[rgb(var(--bd-default))] shadow-sm relative group', className), children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("p", { className: "text-sm text-[rgb(var(--fg-muted))]", children: title }), tooltip && (_jsxs("div", { className: "relative", children: [_jsx("div", { className: "w-5 h-5 rounded-full bg-[rgb(var(--bg-subtle))] flex items-center justify-center cursor-help hover:bg-[rgb(var(--color-primary))]/10 transition-colors peer", children: _jsx(Info, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) }), _jsxs("div", { className: "absolute right-0 top-7 w-72 p-3 bg-[rgb(var(--bg-surface))] border border-[rgb(var(--bd-default))] rounded-lg shadow-lg opacity-0 invisible peer-hover:opacity-100 peer-hover:visible transition-all z-10", children: [tooltip.formula && (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-xs font-medium text-[rgb(var(--fg-default))] mb-2", children: "Formula:" }), _jsx("div", { className: "bg-[rgb(var(--bg-subtle))] rounded p-2 mb-3", children: _jsx("p", { className: "text-[10px] font-mono text-[rgb(var(--fg-muted))]", children: tooltip.formula }) })] })), tooltip.description && (_jsx("p", { className: "text-[10px] text-[rgb(var(--fg-muted))] mb-3", children: tooltip.description })), tooltip.breakdown && tooltip.breakdown.length > 0 && (_jsxs("div", { className: "border-t border-[rgb(var(--bd-default))] pt-3", children: [_jsx("p", { className: "text-xs font-medium text-[rgb(var(--fg-default))] mb-2", children: "Rating Breakdown:" }), _jsx("div", { className: "space-y-1.5", children: tooltip.breakdown.map((item) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] text-[rgb(var(--fg-muted))] w-3", children: item.stars }), _jsx("svg", { className: "w-3 h-3 text-yellow-400", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" }) }), _jsx("div", { className: "flex-1 h-1.5 bg-[rgb(var(--bg-subtle))] rounded-full overflow-hidden", children: _jsx("div", { className: cn('h-full rounded-full', getBreakdownBarColor(item.percentage)), style: { width: `${item.percentage}%` } }) }), _jsx("span", { className: "text-[10px] text-[rgb(var(--fg-muted))] w-10 text-right", children: formatNumber(item.count) }), _jsxs("span", { className: "text-[10px] text-[rgb(var(--fg-muted))] w-6 text-right", children: [item.percentage, "%"] })] }, item.stars))) })] }))] })] }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { children: _jsx("p", { className: "text-4xl font-bold text-[rgb(var(--fg-default))]", children: score }) }), _jsxs("div", { children: [_jsx("div", { className: "flex gap-0.5", children: Array.from({ length: maxScore }, (_, i) => {
31
32
  const starIndex = i + 1;
32
33
  const isFilled = starIndex <= filledStars || (starIndex === filledStars + 1 && hasPartialStar);
33
34
  return (_jsx("svg", { className: cn('w-5 h-5 transition-transform hover:scale-110 cursor-pointer', isFilled ? 'text-yellow-400' : 'text-yellow-400/30'), fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" }) }, i));
34
- }) }), reviewCount !== undefined && (_jsxs("p", { className: "text-xs text-[rgb(var(--fg-muted))] mt-1", children: [reviewCount.toLocaleString(), " reviews"] }))] }), status && (_jsx("div", { className: "ml-auto text-right", children: _jsx("span", { className: cn('text-xs px-2 py-0.5 rounded-full', getStatusStyles(statusType)), children: status }) }))] }), (change !== undefined || periodLabel) && (_jsxs("div", { className: "flex items-center justify-between pt-3 mt-3 border-t border-[rgb(var(--bd-default))]", children: [change !== undefined && (_jsxs("div", { className: "flex items-center gap-1", children: [change > 0 ? (_jsx(TrendingUp, { className: "w-3 h-3 text-[rgb(var(--color-success))]" })) : change < 0 ? (_jsx(TrendingDown, { className: "w-3 h-3 text-[rgb(var(--color-error))]" })) : null, _jsxs("span", { className: cn('text-xs', change > 0 ? 'text-[rgb(var(--color-success))]' : change < 0 ? 'text-[rgb(var(--color-error))]' : 'text-[rgb(var(--fg-muted))]'), children: [change > 0 ? '+' : '', change, " ", changeLabel] })] })), periodLabel && (_jsx("span", { className: "text-[10px] text-[rgb(var(--fg-muted))]", children: periodLabel }))] }))] }));
35
+ }) }), reviewCount !== undefined && (_jsxs("p", { className: "text-xs text-[rgb(var(--fg-muted))] mt-1", children: [formatNumber(reviewCount), " reviews"] }))] }), status && (_jsx("div", { className: "ml-auto text-right", children: _jsx("span", { className: cn('text-xs px-2 py-0.5 rounded-full', getStatusStyles(statusType)), children: status }) }))] }), (change !== undefined || periodLabel) && (_jsxs("div", { className: "flex items-center justify-between pt-3 mt-3 border-t border-[rgb(var(--bd-default))]", children: [change !== undefined && (_jsxs("div", { className: "flex items-center gap-1", children: [change > 0 ? (_jsx(TrendingUp, { className: "w-3 h-3 text-[rgb(var(--color-success))]" })) : change < 0 ? (_jsx(TrendingDown, { className: "w-3 h-3 text-[rgb(var(--color-error))]" })) : null, _jsxs("span", { className: cn('text-xs', change > 0 ? 'text-[rgb(var(--color-success))]' : change < 0 ? 'text-[rgb(var(--color-error))]' : 'text-[rgb(var(--fg-muted))]'), children: [change > 0 ? '+' : '', change, " ", changeLabel] })] })), periodLabel && (_jsx("span", { className: "text-[10px] text-[rgb(var(--fg-muted))]", children: periodLabel }))] }))] }));
35
36
  }
@@ -8,6 +8,7 @@ import { Input } from '../../ui/index.js';
8
8
  import { Checkbox } from '../../ui/index.js';
9
9
  import { Separator } from '../../ui/index.js';
10
10
  import { Badge } from '../../ui/index.js';
11
+ import { formatNumber } from '../../../lib/number-format.js';
11
12
  export function ColumnContextMenu({ column, table, data, isVisible, position, onClose, onApplyFilter, }) {
12
13
  const [activeSubmenu, setActiveSubmenu] = useState(null);
13
14
  const [quickFilterValue, setQuickFilterValue] = useState('');
@@ -316,7 +317,7 @@ export function ColumnContextMenu({ column, table, data, isVisible, position, on
316
317
  }, className: "w-full flex items-center gap-2 px-2 py-1 text-left text-xs rounded hover:bg-[rgb(var(--bg-subtle))]", children: [_jsx(Eye, { className: "h-3.5 w-3.5 text-[rgb(var(--fg-muted))] flex-shrink-0" }), _jsx("span", { className: "truncate flex-1", children: col.label })] }, col.id))) })] }) }, item.id));
317
318
  }
318
319
  if (item.id === 'column-stats' && columnStats) {
319
- return (_jsxs("div", { className: "p-2 space-y-1.5", children: [_jsx("div", { className: "text-sm font-medium text-[rgb(var(--fg-default))]", children: "Column Statistics" }), _jsxs("div", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-xs", children: [_jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Total:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.totalCount.toLocaleString() }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Unique:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.uniqueCount.toLocaleString() }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Null:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.nullCount.toLocaleString() }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Type:" }), _jsx("div", { className: "text-right", children: _jsx(Badge, { variant: "outline", className: "text-xs h-4", children: columnStats.dataType }) }), columnStats.dataType === 'number' && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Min:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.min }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Max:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.max }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Avg:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.avg?.toFixed(2) }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Sum:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.sum?.toLocaleString() })] }))] })] }, item.id));
320
+ return (_jsxs("div", { className: "p-2 space-y-1.5", children: [_jsx("div", { className: "text-sm font-medium text-[rgb(var(--fg-default))]", children: "Column Statistics" }), _jsxs("div", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-xs", children: [_jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Total:" }), _jsx("span", { className: "font-medium text-right", children: formatNumber(columnStats.totalCount) }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Unique:" }), _jsx("span", { className: "font-medium text-right", children: formatNumber(columnStats.uniqueCount) }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Null:" }), _jsx("span", { className: "font-medium text-right", children: formatNumber(columnStats.nullCount) }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Type:" }), _jsx("div", { className: "text-right", children: _jsx(Badge, { variant: "outline", className: "text-xs h-4", children: columnStats.dataType }) }), columnStats.dataType === 'number' && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Min:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.min }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Max:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.max }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Avg:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.avg?.toFixed(2) }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Sum:" }), _jsx("span", { className: "font-medium text-right", children: formatNumber(columnStats.sum) })] }))] })] }, item.id));
320
321
  }
321
322
  }
322
323
  const Icon = item.icon;
@@ -7,6 +7,7 @@ import { Badge } from '../../../../ui/index.js';
7
7
  import { SelectionCheckbox } from '../../../cells/SelectionCell.js';
8
8
  import { Skeleton } from '../../../../ui/feedback/skeleton.js';
9
9
  import { useLanguage } from '../../../../../contexts/LanguageContext.js';
10
+ import { formatNumber } from '../../../../../lib/number-format.js';
10
11
  const sizeConfig = {
11
12
  compact: {
12
13
  grid: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
@@ -128,7 +129,7 @@ export function CardView({ data, columns, onRowClick, selectedRows, onRowSelect,
128
129
  return value ? (_jsx(Badge, { variant: "default", className: "h-4.5 text-[0.6rem] px-1.5 py-0 bg-[rgb(var(--color-success))]/10 text-[rgb(var(--color-success))] border-0 font-medium", children: t('Yes') })) : (_jsx(Badge, { variant: "secondary", className: "h-4.5 text-[0.6rem] px-1.5 py-0 bg-[rgb(var(--bg-subtle))] text-[rgb(var(--fg-muted))] border-0 font-medium", children: t('No') }));
129
130
  }
130
131
  if (typeof value === 'number')
131
- return value.toLocaleString();
132
+ return formatNumber(value);
132
133
  return value?.toString() || '—';
133
134
  }, [t]);
134
135
  const getFieldLabel = useCallback((column) => {
@@ -8,6 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '../../../../ui/index.j
8
8
  import { Button } from '../../../../ui/index.js';
9
9
  import { Dropdown } from '../../../../index.js';
10
10
  import { Badge } from '../../../../ui/index.js';
11
+ import { formatNumber } from '../../../../../lib/number-format.js';
11
12
  const CHART_COLORS = [
12
13
  '#3b82f6', // Blue
13
14
  '#ef4444', // Red
@@ -341,7 +342,7 @@ export function DataVisualization({ data, columns, title = "Data Insights" }) {
341
342
  if (data.length === 0) {
342
343
  return (_jsx(Card, { className: "p-8", children: _jsxs("div", { className: "text-center text-gray-500", children: [_jsx(BarChart3, { className: "h-12 w-12 mx-auto mb-4 opacity-50" }), _jsx("h3", { className: "text-lg font-medium mb-2", children: "No Data Available" }), _jsx("p", { className: "text-sm", children: "Add some data to view visualizations" })] }) }));
343
344
  }
344
- return (_jsxs("div", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold tracking-tight", children: title }), _jsxs("p", { className: "text-sm text-gray-600 mt-1", children: ["Visual analysis of ", data.length, " records"] })] }), _jsxs("div", { className: "flex items-center space-x-2", children: [_jsxs(Button, { variant: selectedChart === 'bar' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('bar'), children: [_jsx(BarChart3, { className: "h-4 w-4 mr-2" }), "Bar"] }), _jsxs(Button, { variant: selectedChart === 'line' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('line'), children: [_jsx(LineChartIcon, { className: "h-4 w-4 mr-2" }), "Line"] }), _jsxs(Button, { variant: selectedChart === 'area' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('area'), children: [_jsx(Activity, { className: "h-4 w-4 mr-2" }), "Area"] }), _jsxs(Button, { variant: selectedChart === 'pie' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('pie'), children: [_jsx(PieChartIcon, { className: "h-4 w-4 mr-2" }), "Pie"] })] })] }), summaryStats && (_jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-blue-600", children: summaryStats.sum.toLocaleString() }), _jsx("div", { className: "text-xs text-gray-600", children: "Total" })] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-green-600", children: summaryStats.avg.toFixed(1) }), _jsx("div", { className: "text-xs text-gray-600", children: "Average" })] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-orange-600", children: summaryStats.min.toLocaleString() }), _jsx("div", { className: "text-xs text-gray-600", children: "Minimum" })] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-purple-600", children: summaryStats.max.toLocaleString() }), _jsx("div", { className: "text-xs text-gray-600", children: "Maximum" })] }) })] })), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg", children: "Chart Configuration" }) }), _jsx(CardContent, { children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { children: [_jsxs("label", { className: "text-sm font-medium mb-2 block", children: ["X-Axis (", selectedChart === 'pie' ? 'Category' : 'Categories', ")"] }), _jsx(Dropdown, { options: categoricalColumns.map((col) => ({
345
+ return (_jsxs("div", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold tracking-tight", children: title }), _jsxs("p", { className: "text-sm text-gray-600 mt-1", children: ["Visual analysis of ", data.length, " records"] })] }), _jsxs("div", { className: "flex items-center space-x-2", children: [_jsxs(Button, { variant: selectedChart === 'bar' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('bar'), children: [_jsx(BarChart3, { className: "h-4 w-4 mr-2" }), "Bar"] }), _jsxs(Button, { variant: selectedChart === 'line' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('line'), children: [_jsx(LineChartIcon, { className: "h-4 w-4 mr-2" }), "Line"] }), _jsxs(Button, { variant: selectedChart === 'area' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('area'), children: [_jsx(Activity, { className: "h-4 w-4 mr-2" }), "Area"] }), _jsxs(Button, { variant: selectedChart === 'pie' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('pie'), children: [_jsx(PieChartIcon, { className: "h-4 w-4 mr-2" }), "Pie"] })] })] }), summaryStats && (_jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-blue-600", children: formatNumber(summaryStats.sum) }), _jsx("div", { className: "text-xs text-gray-600", children: "Total" })] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-green-600", children: summaryStats.avg.toFixed(1) }), _jsx("div", { className: "text-xs text-gray-600", children: "Average" })] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-orange-600", children: formatNumber(summaryStats.min) }), _jsx("div", { className: "text-xs text-gray-600", children: "Minimum" })] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-purple-600", children: formatNumber(summaryStats.max) }), _jsx("div", { className: "text-xs text-gray-600", children: "Maximum" })] }) })] })), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg", children: "Chart Configuration" }) }), _jsx(CardContent, { children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { children: [_jsxs("label", { className: "text-sm font-medium mb-2 block", children: ["X-Axis (", selectedChart === 'pie' ? 'Category' : 'Categories', ")"] }), _jsx(Dropdown, { options: categoricalColumns.map((col) => ({
345
346
  value: col.key,
346
347
  label: col.label,
347
348
  })), value: xAxisColumn, onValueChange: (value) => setXAxisColumn(typeof value === 'string' ? value : value[0] || ''), placeholder: "Select column" })] }), selectedChart !== 'pie' && (_jsxs("div", { children: [_jsx("label", { className: "text-sm font-medium mb-2 block", children: "Y-Axis (Values)" }), _jsx(Dropdown, { options: numericColumns.map((col) => ({
@@ -25,7 +25,7 @@ export declare function debounce<T extends (...args: any[]) => any>(func: T, del
25
25
  */
26
26
  export declare function getRowId<TData>(row: TData, index: number, getRowIdFn?: (row: TData) => string): string;
27
27
  /**
28
- * Format large numbers with commas
28
+ * Format large numbers with grouping, in the library's number locale (Indian by default)
29
29
  */
30
30
  export declare function formatNumber(num: number): string;
31
31
  /**
@@ -1,4 +1,5 @@
1
1
  'use client';
2
+ import { formatNumber as formatGrouped } from '../../../lib/number-format.js';
2
3
  /**
3
4
  * Grid utility functions and helpers
4
5
  */
@@ -75,10 +76,10 @@ export function getRowId(row, index, getRowIdFn) {
75
76
  return String(index);
76
77
  }
77
78
  /**
78
- * Format large numbers with commas
79
+ * Format large numbers with grouping, in the library's number locale (Indian by default)
79
80
  */
80
81
  export function formatNumber(num) {
81
- return num.toLocaleString();
82
+ return formatGrouped(num);
82
83
  }
83
84
  /**
84
85
  * Check if value matches search term
@@ -2,6 +2,7 @@
2
2
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
3
  import { cn } from "../../lib/utils.js";
4
4
  import { useTranslation } from "../../hooks/useTranslation.js";
5
+ import { formatNumber } from '../../lib/number-format.js';
5
6
  export function Footer({ kpis, actions, iconButtons, leftContent, className, sticky = false, compact = false, padding, variant = 'page', gradient = false }) {
6
7
  if (!kpis && !actions && !leftContent && !iconButtons) {
7
8
  return null;
@@ -41,7 +42,7 @@ export function Footer({ kpis, actions, iconButtons, leftContent, className, sti
41
42
  padding === 'normal' ? "py-3 sm:py-4" :
42
43
  compact ? "py-1" :
43
44
  isModal ? "py-1.5 sm:py-2" : "py-3 sm:py-4"), children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 sm:gap-4 w-full sm:w-auto", children: [leftContent, kpis && kpis.length > 0 && (_jsx("div", { className: "flex flex-wrap items-center gap-3 sm:gap-6 md:gap-8", children: kpis.map((kpi) => (_jsxs("div", { className: cn("flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm", kpi.onClick && "cursor-pointer hover:text-[rgb(var(--color-primary-hover))] transition-colors"), onClick: kpi.onClick, children: [_jsxs("span", { className: "text-[rgb(var(--fg-muted))] whitespace-nowrap", children: [kpi.label, ":"] }), _jsx("span", { className: "font-medium text-[rgb(var(--fg-default))]", children: typeof kpi.value === 'number'
44
- ? kpi.value.toLocaleString()
45
+ ? formatNumber(kpi.value)
45
46
  : kpi.value }), kpi.trend && (_jsxs("span", { className: cn("text-xs font-medium", kpi.trend.direction === 'up'
46
47
  ? "text-[rgb(var(--color-success))]"
47
48
  : "text-[rgb(var(--color-error))]"), children: [kpi.trend.direction === 'up' ? '↑' : '↓', Math.abs(kpi.trend.value), "%"] }))] }, kpi.id))) }))] }), iconButtons && iconButtons.length > 0 && (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 sm:gap-2", children: iconButtons.map((button) => {
@@ -107,7 +107,10 @@ export function SidePanel({ title, icon: Icon, categories, activeItemId, onItemS
107
107
  return _jsx(ItemIcon, { className: "w-4 h-4 shrink-0 opacity-50" });
108
108
  })(), _jsx("span", { className: "truncate flex-1", children: item.label }), item.badge !== undefined && item.badge !== 0 && (_jsx("span", { className: cn("text-xs px-1.5 py-0.5 rounded-full shrink-0 tabular-nums", activeItemId === item.id
109
109
  ? "bg-[rgb(var(--color-primary))] text-white"
110
- : "bg-[rgb(var(--bg-subtle))] text-[rgb(var(--fg-muted))]"), children: item.badge }))] }), itemContextMenu && (_jsx("button", { onClick: (e) => handleMenuToggle(e, item.id), className: "absolute right-1.5 top-1/2 -translate-y-1/2 w-6 h-6 rounded-md text-[rgb(var(--fg-muted))]/50 flex items-center justify-center hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-all opacity-0 group-hover:opacity-100", children: _jsx(MoreVertical, { className: "w-3.5 h-3.5" }) }))] }, item.id))) }))] }, category.id))))] })) }), footer && !isCollapsed && (_jsx("div", { className: cn("shrink-0 border-t border-[rgb(var(--bd-default))] transition-opacity duration-200", isAnimating ? "opacity-0" : "opacity-100"), children: footer }))] }), menuOpen !== null && itemContextMenu && (_jsx("div", { ref: menuRef, className: "fixed bg-[rgb(var(--bg-surface))] border border-[rgb(var(--bd-default))] rounded-lg min-w-[170px] shadow-lg overflow-hidden z-[9999]", style: {
110
+ : "bg-[rgb(var(--bg-subtle))] text-[rgb(var(--fg-muted))]"), children: item.badge }))] }), itemContextMenu && (
111
+ // Named for screen readers, and shown on keyboard focus as well as hover:
112
+ // it was an unlabelled button nobody tabbing could see.
113
+ _jsx("button", { type: "button", onClick: (e) => handleMenuToggle(e, item.id), "aria-label": `More actions for ${item.label}`, "aria-haspopup": "menu", "aria-expanded": menuOpen === item.id, className: "absolute right-1.5 top-1/2 -translate-y-1/2 w-6 h-6 rounded-md text-[rgb(var(--fg-muted))]/50 flex items-center justify-center hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-all opacity-0 group-hover:opacity-100 focus-visible:opacity-100", children: _jsx(MoreVertical, { className: "w-3.5 h-3.5" }) }))] }, item.id))) }))] }, category.id))))] })) }), footer && !isCollapsed && (_jsx("div", { className: cn("shrink-0 border-t border-[rgb(var(--bd-default))] transition-opacity duration-200", isAnimating ? "opacity-0" : "opacity-100"), children: footer }))] }), menuOpen !== null && itemContextMenu && (_jsx("div", { ref: menuRef, className: "fixed bg-[rgb(var(--bg-surface))] border border-[rgb(var(--bd-default))] rounded-lg min-w-[170px] shadow-lg overflow-hidden z-[9999]", style: {
111
114
  left: `${menuPosition.left}px`,
112
115
  top: `${menuPosition.top}px`,
113
116
  transform: 'translateY(-50%)'
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { TrendingUp, TrendingDown } from "lucide-react";
4
4
  import { cn } from "../../../lib/utils.js";
5
5
  import { Card } from "./card.js";
6
+ import { formatNumber } from '../../../lib/number-format.js';
6
7
  const iconVariants = {
7
8
  default: "bg-primary text-white",
8
9
  accent: "bg-accent text-white",
@@ -12,7 +13,7 @@ const iconVariants = {
12
13
  export function StatsCard({ title, value, icon: Icon, trend, description, className, variant = 'default' }) {
13
14
  const TrendIcon = trend?.direction === 'up' ? TrendingUp : TrendingDown;
14
15
  const trendColor = trend?.direction === 'up' ? 'text-success' : 'text-error';
15
- return (_jsx(Card, { className: cn("p-6 min-h-[140px] w-full", className), children: _jsxs("div", { className: "flex items-start gap-4", children: [Icon && (_jsx("div", { className: cn("flex-shrink-0 p-3 rounded-lg", iconVariants[variant]), children: _jsx(Icon, { className: "h-6 w-6" }) })), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-baseline flex-wrap gap-2", children: [_jsx("p", { className: "text-2xl font-semibold text-fg-default truncate", children: typeof value === 'number' ? value.toLocaleString() : value }), trend && (_jsxs("div", { className: cn("flex items-center text-sm font-semibold", trendColor), children: [_jsx(TrendIcon, { className: "h-4 w-4 flex-shrink-0" }), _jsxs("span", { className: "ml-1", children: [Math.abs(trend.value), "%", trend.label && ` ${trend.label}`] })] }))] }), _jsx("p", { className: "text-sm font-medium text-fg-muted mt-2 line-clamp-2", children: title }), description && (_jsx("p", { className: "text-sm text-fg-subtle mt-1 line-clamp-1", children: description }))] })] }) }));
16
+ return (_jsx(Card, { className: cn("p-6 min-h-[140px] w-full", className), children: _jsxs("div", { className: "flex items-start gap-4", children: [Icon && (_jsx("div", { className: cn("flex-shrink-0 p-3 rounded-lg", iconVariants[variant]), children: _jsx(Icon, { className: "h-6 w-6" }) })), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-baseline flex-wrap gap-2", children: [_jsx("p", { className: "text-2xl font-semibold text-fg-default truncate", children: typeof value === 'number' ? formatNumber(value) : value }), trend && (_jsxs("div", { className: cn("flex items-center text-sm font-semibold", trendColor), children: [_jsx(TrendIcon, { className: "h-4 w-4 flex-shrink-0" }), _jsxs("span", { className: "ml-1", children: [Math.abs(trend.value), "%", trend.label && ` ${trend.label}`] })] }))] }), _jsx("p", { className: "text-sm font-medium text-fg-muted mt-2 line-clamp-2", children: title }), description && (_jsx("p", { className: "text-sm text-fg-subtle mt-1 line-clamp-1", children: description }))] })] }) }));
16
17
  }
17
18
  export function StatsGrid({ children, columns = 4, className }) {
18
19
  const gridCols = {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * How every number in the library is written. Indian grouping by default (20,00,000, not
3
+ * 2,000,000): the products built on this library keep Indian books, and a bare `toLocaleString()`
4
+ * follows whatever locale the browser happens to be set to, so the same figure read differently on
5
+ * two machines.
6
+ *
7
+ * A product that needs another convention calls `setNumberLocale()` once at start-up.
8
+ */
9
+ export declare function setNumberLocale(locale: string): void;
10
+ export declare function getNumberLocale(): string;
11
+ /** A full number with grouping, up to two decimals. Empty for null, undefined or NaN. */
12
+ export declare function formatNumber(value: number | string | null | undefined, maximumFractionDigits?: number): string;
13
+ /**
14
+ * A short number for chart axes, where a full figure runs into its neighbour: 12,500 · 20 L ·
15
+ * 1.4 Cr in Indian grouping; 12.5K · 2M · 1.4B otherwise. Below a lakh (or a thousand) it is the
16
+ * full number.
17
+ */
18
+ export declare function formatCompactNumber(value: number | string | null | undefined): string;
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+ /**
3
+ * How every number in the library is written. Indian grouping by default (20,00,000, not
4
+ * 2,000,000): the products built on this library keep Indian books, and a bare `toLocaleString()`
5
+ * follows whatever locale the browser happens to be set to, so the same figure read differently on
6
+ * two machines.
7
+ *
8
+ * A product that needs another convention calls `setNumberLocale()` once at start-up.
9
+ */
10
+ let numberLocale = 'en-IN';
11
+ export function setNumberLocale(locale) {
12
+ numberLocale = locale;
13
+ }
14
+ export function getNumberLocale() {
15
+ return numberLocale;
16
+ }
17
+ /** A full number with grouping, up to two decimals. Empty for null, undefined or NaN. */
18
+ export function formatNumber(value, maximumFractionDigits = 2) {
19
+ if (value == null || value === '')
20
+ return '';
21
+ const n = typeof value === 'number' ? value : Number(value);
22
+ if (!Number.isFinite(n))
23
+ return typeof value === 'string' ? value : '';
24
+ return n.toLocaleString(numberLocale, { maximumFractionDigits });
25
+ }
26
+ const trim = (n) => n.toFixed(n >= 100 ? 0 : n >= 10 ? 1 : 2).replace(/\.?0+$/, '');
27
+ /**
28
+ * A short number for chart axes, where a full figure runs into its neighbour: 12,500 · 20 L ·
29
+ * 1.4 Cr in Indian grouping; 12.5K · 2M · 1.4B otherwise. Below a lakh (or a thousand) it is the
30
+ * full number.
31
+ */
32
+ export function formatCompactNumber(value) {
33
+ if (value == null || value === '')
34
+ return '';
35
+ const n = typeof value === 'number' ? value : Number(value);
36
+ if (!Number.isFinite(n))
37
+ return String(value);
38
+ const sign = n < 0 ? '-' : '';
39
+ const a = Math.abs(n);
40
+ if (numberLocale === 'en-IN') {
41
+ if (a >= 1e7)
42
+ return `${sign}${trim(a / 1e7)} Cr`;
43
+ if (a >= 1e5)
44
+ return `${sign}${trim(a / 1e5)} L`;
45
+ return formatNumber(n, 0);
46
+ }
47
+ if (a >= 1e9)
48
+ return `${sign}${trim(a / 1e9)}B`;
49
+ if (a >= 1e6)
50
+ return `${sign}${trim(a / 1e6)}M`;
51
+ if (a >= 1e3)
52
+ return `${sign}${trim(a / 1e3)}K`;
53
+ return formatNumber(n, 0);
54
+ }
package/dist/lib/utils.js CHANGED
@@ -1,11 +1,12 @@
1
1
  'use client';
2
2
  import { clsx } from "clsx";
3
3
  import { twMerge } from "tailwind-merge";
4
+ import { getNumberLocale } from './number-format.js';
4
5
  export function cn(...inputs) {
5
6
  return twMerge(clsx(inputs));
6
7
  }
7
- export function formatCurrency(amount, currency = 'USD') {
8
- return new Intl.NumberFormat('en-US', {
8
+ export function formatCurrency(amount, currency = 'INR') {
9
+ return new Intl.NumberFormat(getNumberLocale(), {
9
10
  style: 'currency',
10
11
  currency,
11
12
  }).format(amount);
@@ -18,7 +19,7 @@ export function formatDate(date) {
18
19
  }).format(new Date(date));
19
20
  }
20
21
  export function formatNumber(num, decimals = 0) {
21
- return new Intl.NumberFormat('en-US', {
22
+ return new Intl.NumberFormat(getNumberLocale(), {
22
23
  minimumFractionDigits: decimals,
23
24
  maximumFractionDigits: decimals,
24
25
  }).format(num);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indas-ui",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Indas UI — React component library for business apps: Tailwind design tokens, Radix primitives, Lucide icons, DataGrid, dashboards and charts.",
5
5
  "license": "MIT",
6
6
  "author": "Indas Analytics Pvt Ltd",
@@ -121,8 +121,8 @@
121
121
  "exceljs": "^4.4.0",
122
122
  "file-saver": "^2.0.5",
123
123
  "framer-motion": "^12.23.12",
124
- "jspdf": "^3.0.2",
125
- "jspdf-autotable": "^5.0.2",
124
+ "jspdf": "^4.2.1",
125
+ "jspdf-autotable": "^5.0.8",
126
126
  "ldrs": "^1.1.9",
127
127
  "lucide-react": "^0.562.0",
128
128
  "next-auth": "^4.24.7",
@@ -133,7 +133,7 @@
133
133
  "rehype-highlight": "^7.0.2",
134
134
  "rehype-raw": "^7.0.0",
135
135
  "remark-gfm": "^4.0.1",
136
- "svg2pdf.js": "^2.7.0",
136
+ "svg2pdf.js": "^2.8.1",
137
137
  "tailwind-merge": "^3.3.1"
138
138
  },
139
139
  "devDependencies": {