@smallwebco/tinypivot-react 1.0.79 → 1.0.80

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/README.md CHANGED
@@ -45,6 +45,96 @@ export default function App() {
45
45
  }
46
46
  ```
47
47
 
48
+ ## Theming
49
+
50
+ TinyPivot ships 22 themes — 2 neutral (`light`, `dark`) plus 10 brand themes each with a light and dark variant. Themes are applied via the `theme` prop on `DataGrid`.
51
+
52
+ ### Quick start
53
+
54
+ ```tsx
55
+ <DataGrid data={data} theme="slate-dark" />
56
+ ```
57
+
58
+ `theme="auto"` resolves to `'light'` or `'dark'` based on the user's system preference (`prefers-color-scheme`).
59
+
60
+ ### Available themes
61
+
62
+ | Theme | Accent | Vibe |
63
+ |---|---|---|
64
+ | `light` / `dark` / `auto` | indigo / violet | TinyPivot defaults — neutral cool grays |
65
+ | `slate` / `slate-dark` | `#4f46e5` indigo | Linear / Stripe — cool neutral |
66
+ | `zinc` / `zinc-dark` | near-mono | Vercel / Anthropic — minimalist |
67
+ | `indigo` / `indigo-dark` | `#6366f1` vivid indigo | Premium SaaS |
68
+ | `violet` / `violet-dark` | `#8b5cf6` purple | Data viz / AI tools |
69
+ | `emerald` / `emerald-dark` | `#10b981` green | Fintech / finance |
70
+ | `sky` / `sky-dark` | `#0ea5e9` light blue | Productivity / airy |
71
+ | `rose` / `rose-dark` | `#f43f5e` warm pink | Friendly / creator |
72
+ | `amber` / `amber-dark` | `#f59e0b` warm orange | Energy / wellness |
73
+ | `solar` / `solar-dark` | `#b58900` mustard | Solarized-inspired warm cream + dark teal |
74
+ | `mono` / `mono-dark` | `#000` / `#fff` | Editorial — pure grayscale, high contrast |
75
+
76
+ ### Custom themes
77
+
78
+ Brand themes redefine ~25 CSS custom property tokens at the grid root. You can override these in your own CSS to create a custom theme:
79
+
80
+ ```tsx
81
+ import './my-brand.css'
82
+
83
+ <DataGrid data={data} theme="light" className="my-brand" />
84
+ ```
85
+
86
+ ```css
87
+ /* my-brand.css */
88
+ .vpg-data-grid.my-brand {
89
+ --vpg-accent: #ff6b35;
90
+ --vpg-accent-hover: #e55426;
91
+ --vpg-surface-bg: #fafaf7;
92
+ --vpg-surface-panel: #f0eee7;
93
+ /* …override any of the tokens below */
94
+ }
95
+ ```
96
+
97
+ **Token reference** — these are the variables you can override:
98
+
99
+ - **Surfaces**: `--vpg-surface-bg`, `--vpg-surface-panel`, `--vpg-surface-elevated`, `--vpg-surface-hover`, `--vpg-surface-selected`, `--vpg-surface-striped`
100
+ - **Text**: `--vpg-text-primary`, `--vpg-text-secondary`, `--vpg-text-muted`, `--vpg-text-inverse`
101
+ - **Borders**: `--vpg-border-default`, `--vpg-border-strong`, `--vpg-border-subtle`
102
+ - **Accent**: `--vpg-accent`, `--vpg-accent-hover`, `--vpg-accent-soft-bg`, `--vpg-accent-soft-text`, `--vpg-focus-ring`
103
+ - **States**: `--vpg-state-error`, `--vpg-state-warning`, `--vpg-state-success`, `--vpg-state-info`
104
+ - **Scrollbar**: `--vpg-scrollbar-thumb`, `--vpg-scrollbar-track`
105
+
106
+ Custom theme classes layer on top of any preset, so you can start from `theme="dark"` and tweak just the accent, for example.
107
+
108
+ ### Adding a theme switcher
109
+
110
+ Let users pick their own theme — bind a state value to a `<select>`:
111
+
112
+ ```tsx
113
+ import { DataGrid } from '@smallwebco/tinypivot-react'
114
+ import '@smallwebco/tinypivot-react/style.css'
115
+ import { useState } from 'react'
116
+
117
+ type Theme = 'light' | 'dark' | 'slate' | 'slate-dark' | 'emerald' | 'emerald-dark'
118
+
119
+ export function MyGrid({ data }) {
120
+ const [theme, setTheme] = useState<Theme>('dark')
121
+
122
+ return (
123
+ <>
124
+ <select value={theme} onChange={e => setTheme(e.target.value as Theme)}>
125
+ <option value="light">Light</option>
126
+ <option value="dark">Dark</option>
127
+ <option value="slate-dark">Slate (dark)</option>
128
+ <option value="emerald-dark">Emerald (dark)</option>
129
+ {/* …add more themes */}
130
+ </select>
131
+
132
+ <DataGrid data={data} theme={theme} />
133
+ </>
134
+ )
135
+ }
136
+ ```
137
+
48
138
  ## Features
49
139
 
50
140
  | Feature | Free | Pro |
@@ -79,7 +169,7 @@ export default function App() {
79
169
  | `pageSize` | `number` | `50` | Rows per page |
80
170
  | `enableColumnResize` | `boolean` | `true` | Drag to resize columns |
81
171
  | `enableClipboard` | `boolean` | `true` | Ctrl+C to copy cells |
82
- | `theme` | `'light' \| 'dark' \| 'auto'` | `'light'` | Color theme |
172
+ | `theme` | `string` | `'light'` | Color theme — see [Theming](#theming) for the full list (22 presets) |
83
173
  | `numberFormat` | `'us' \| 'eu' \| 'plain'` | `'us'` | Number display format: US (1,234.56), EU (1.234,56), plain (1234.56) |
84
174
  | `dateFormat` | `'us' \| 'eu' \| 'iso'` | `'iso'` | Date display format: US (MM/DD/YYYY), EU (DD/MM/YYYY), ISO (YYYY-MM-DD) |
85
175
  | `fieldRoleOverrides` | `Record<string, FieldRole>` | `undefined` | Override auto-detected chart field roles per column (`'dimension'` \| `'measure'` \| `'temporal'`) |
package/dist/index.cjs CHANGED
@@ -832,6 +832,7 @@ var AIAnalyst = (0, import_react2.forwardRef)(({
832
832
  onQueryExecuted,
833
833
  onError
834
834
  });
835
+ const isDarkTheme = theme === "dark" || typeof theme === "string" && theme.endsWith("-dark");
835
836
  (0, import_react2.useImperativeHandle)(ref, () => ({
836
837
  loadFullData,
837
838
  selectedDataSource
@@ -993,7 +994,7 @@ var AIAnalyst = (0, import_react2.forwardRef)(({
993
994
  return !!message.metadata?.data && message.metadata.data.length > 0;
994
995
  };
995
996
  if (!selectedDataSource) {
996
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `vpg-ai-analyst ${theme === "dark" ? "vpg-theme-dark" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vpg-ai-picker-fullscreen", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-ai-picker-content", children: [
997
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `vpg-ai-analyst ${isDarkTheme ? "vpg-theme-dark" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vpg-ai-picker-fullscreen", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-ai-picker-content", children: [
997
998
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-ai-picker-header", children: [
998
999
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vpg-ai-icon-lg", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
999
1000
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7h1a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1H2a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h1a7 7 0 0 1 7-7h1V5.73c-.6-.34-1-.99-1-1.73a2 2 0 0 1 2-2z" }),
@@ -1065,7 +1066,7 @@ var AIAnalyst = (0, import_react2.forwardRef)(({
1065
1066
  ] })
1066
1067
  ] }) }) });
1067
1068
  }
1068
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `vpg-ai-analyst ${theme === "dark" ? "vpg-theme-dark" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-ai-split-layout", children: [
1069
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `vpg-ai-analyst ${isDarkTheme ? "vpg-theme-dark" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-ai-split-layout", children: [
1069
1070
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-ai-chat-panel", children: [
1070
1071
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-ai-chat-header", children: [
1071
1072
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1795,7 +1796,7 @@ function ChartBuilder({
1795
1796
  return val.toFixed(dec);
1796
1797
  }, []);
1797
1798
  const chartOptions = (0, import_react4.useMemo)(() => {
1798
- const isDark = theme === "dark";
1799
+ const isDark = theme === "dark" || typeof theme === "string" && theme.endsWith("-dark");
1799
1800
  const config = chartConfig;
1800
1801
  const options = config.options || {};
1801
1802
  const baseOptions = {
@@ -3884,7 +3885,8 @@ function PivotConfig({
3884
3885
  onAddColumnField,
3885
3886
  onAddCalculatedField,
3886
3887
  onRemoveCalculatedField,
3887
- onUpdateCalculatedField
3888
+ onUpdateCalculatedField,
3889
+ theme
3888
3890
  }) {
3889
3891
  const [fieldSearch, setFieldSearch] = (0, import_react12.useState)("");
3890
3892
  const [showCalcModal, setShowCalcModal] = (0, import_react12.useState)(false);
@@ -4010,7 +4012,7 @@ function PivotConfig({
4010
4012
  setShowCalcModal(false);
4011
4013
  setEditingCalcField(null);
4012
4014
  }, []);
4013
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "vpg-pivot-config", children: [
4015
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `vpg-pivot-config ${theme ? `vpg-theme-${theme}` : ""}`, children: [
4014
4016
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "vpg-config-header", children: [
4015
4017
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("h3", { className: "vpg-config-title", children: [
4016
4018
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
@@ -4278,7 +4280,8 @@ function PivotSkeleton({
4278
4280
  onAddValueField,
4279
4281
  onRemoveValueField,
4280
4282
  onReorderRowFields,
4281
- onReorderColumnFields
4283
+ onReorderColumnFields,
4284
+ theme
4282
4285
  }) {
4283
4286
  const { showWatermark, canUsePivot, isDemo } = useLicense();
4284
4287
  const getValueFieldDisplayName = (0, import_react13.useCallback)((field) => {
@@ -4644,7 +4647,7 @@ function PivotSkeleton({
4644
4647
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4645
4648
  "div",
4646
4649
  {
4647
- className: `vpg-pivot-skeleton vpg-font-${currentFontSize} ${draggingField ? "vpg-is-dragging" : ""}`,
4650
+ className: `vpg-pivot-skeleton vpg-font-${currentFontSize} ${theme ? `vpg-theme-${theme}` : ""} ${draggingField ? "vpg-is-dragging" : ""}`,
4648
4651
  children: [
4649
4652
  showCopyToast && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "vpg-toast", children: [
4650
4653
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }),
@@ -6195,7 +6198,8 @@ function DataGrid({
6195
6198
  onRemoveValueField: removeValueField,
6196
6199
  onAddCalculatedField: addCalculatedField,
6197
6200
  onRemoveCalculatedField: removeCalculatedField,
6198
- onUpdateCalculatedField: addCalculatedField
6201
+ onUpdateCalculatedField: addCalculatedField,
6202
+ theme: currentTheme
6199
6203
  }
6200
6204
  ) }),
6201
6205
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `vpg-pivot-main ${!showPivotConfig ? "vpg-full-width" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
@@ -6220,7 +6224,8 @@ function DataGrid({
6220
6224
  onRemoveValueField: removeValueField,
6221
6225
  onUpdateAggregation: updateValueFieldAggregation,
6222
6226
  onReorderRowFields: setRowFields,
6223
- onReorderColumnFields: setColumnFields
6227
+ onReorderColumnFields: setColumnFields,
6228
+ theme: currentTheme
6224
6229
  }
6225
6230
  ) })
6226
6231
  ] }),