@kinetixui/ui 0.4.2 → 0.4.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.
package/dist/index.d.ts CHANGED
@@ -482,6 +482,20 @@ declare const ChartContainer: React$1.ForwardRefExoticComponent<Omit<React$1.Cla
482
482
  * primitive for keyboard data-point navigation.
483
483
  */
484
484
  label?: string;
485
+ /**
486
+ * Render a placeholder in place of the chart. `"loading"` shows a shimmer,
487
+ * `"empty"` / `"error"` show a message (override with `stateMessage`).
488
+ */
489
+ state?: "loading" | "empty" | "error";
490
+ stateMessage?: string;
491
+ /**
492
+ * Visually-hidden `<table>` rendered after the chart so a screen reader can
493
+ * read the underlying numbers. Pair with a concise `label`.
494
+ */
495
+ srTable?: {
496
+ columns: React$1.ReactNode[];
497
+ rows: React$1.ReactNode[][];
498
+ };
485
499
  }, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
486
500
  declare const ChartStyle: ({ id, config }: {
487
501
  id: string;
package/dist/index.js CHANGED
@@ -1545,7 +1545,19 @@ function useChart() {
1545
1545
  if (!context) throw new Error("useChart must be used within a <ChartContainer />");
1546
1546
  return context;
1547
1547
  }
1548
- var ChartContainer = React32.forwardRef(({ id, className, children, config, label, role, "aria-label": ariaLabel, ...props }, ref) => {
1548
+ var ChartContainer = React32.forwardRef(({
1549
+ id,
1550
+ className,
1551
+ children,
1552
+ config,
1553
+ label,
1554
+ state,
1555
+ stateMessage,
1556
+ srTable,
1557
+ role,
1558
+ "aria-label": ariaLabel,
1559
+ ...props
1560
+ }, ref) => {
1549
1561
  const uniqueId = React32.useId();
1550
1562
  const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
1551
1563
  const name = ariaLabel ?? label ?? "Chart";
@@ -1584,12 +1596,53 @@ var ChartContainer = React32.forwardRef(({ id, className, children, config, labe
1584
1596
  ...props,
1585
1597
  children: [
1586
1598
  /* @__PURE__ */ jsx37(ChartStyle, { id: chartId, config }),
1587
- /* @__PURE__ */ jsx37(RechartsPrimitive.ResponsiveContainer, { children })
1599
+ state ? /* @__PURE__ */ jsx37(ChartState, { state, message: stateMessage }) : /* @__PURE__ */ jsx37(RechartsPrimitive.ResponsiveContainer, { children }),
1600
+ srTable && !state ? /* @__PURE__ */ jsx37(ChartSrTable, { ...srTable }) : null
1588
1601
  ]
1589
1602
  }
1590
1603
  ) });
1591
1604
  });
1592
1605
  ChartContainer.displayName = "Chart";
1606
+ var STATE_COPY = {
1607
+ loading: "Loading chart\u2026",
1608
+ empty: "No data to show",
1609
+ error: "Couldn't load this chart"
1610
+ };
1611
+ function ChartState({
1612
+ state,
1613
+ message
1614
+ }) {
1615
+ return /* @__PURE__ */ jsxs15(
1616
+ "div",
1617
+ {
1618
+ role: state === "error" ? "alert" : "status",
1619
+ className: cn(
1620
+ "flex h-full w-full flex-col items-center justify-center gap-2 rounded-md border border-dashed p-6 text-center text-xs",
1621
+ state === "error" ? "border-destructive/40 text-destructive" : "border-border text-muted-foreground"
1622
+ ),
1623
+ children: [
1624
+ state === "loading" ? /* @__PURE__ */ jsx37("div", { "aria-hidden": true, className: "flex w-full max-w-[220px] items-end justify-between gap-1.5", children: [40, 72, 56, 88, 48, 64, 80].map((h, i) => /* @__PURE__ */ jsx37(
1625
+ "span",
1626
+ {
1627
+ className: "w-full animate-pulse rounded-sm bg-muted",
1628
+ style: { height: `${h}px`, animationDelay: `${i * 90}ms` }
1629
+ },
1630
+ i
1631
+ )) }) : /* @__PURE__ */ jsx37("svg", { "aria-hidden": true, viewBox: "0 0 24 24", className: "size-5", fill: "none", stroke: "currentColor", strokeWidth: 1.5, children: state === "empty" ? /* @__PURE__ */ jsx37("path", { d: "M3 3v18h18M7 15l4-4 3 3 5-6", strokeLinecap: "round", strokeLinejoin: "round" }) : /* @__PURE__ */ jsx37("path", { d: "M12 9v4m0 4h.01M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.7 3.86a2 2 0 0 0-3.42 0Z", strokeLinecap: "round", strokeLinejoin: "round" }) }),
1632
+ /* @__PURE__ */ jsx37("span", { children: message ?? STATE_COPY[state] })
1633
+ ]
1634
+ }
1635
+ );
1636
+ }
1637
+ function ChartSrTable({
1638
+ columns,
1639
+ rows
1640
+ }) {
1641
+ return /* @__PURE__ */ jsxs15("table", { className: "sr-only", children: [
1642
+ /* @__PURE__ */ jsx37("thead", { children: /* @__PURE__ */ jsx37("tr", { children: columns.map((c, i) => /* @__PURE__ */ jsx37("th", { scope: "col", children: c }, i)) }) }),
1643
+ /* @__PURE__ */ jsx37("tbody", { children: rows.map((r, i) => /* @__PURE__ */ jsx37("tr", { children: r.map((cell, j) => /* @__PURE__ */ jsx37("td", { children: cell }, j)) }, i)) })
1644
+ ] });
1645
+ }
1593
1646
  var ChartStyle = ({ id, config }) => {
1594
1647
  const colorConfig = Object.entries(config).filter(([, c]) => c.theme || c.color);
1595
1648
  if (!colorConfig.length) return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kinetixui/ui",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "private": false,
5
5
  "description": "KinetixUI React components — CVA + Radix + Tailwind on the KinetixUI token contract. Also distributed via the kinetixui CLI as a component registry.",
6
6
  "keywords": [
@@ -89,7 +89,7 @@
89
89
  "react-dom": ">=18"
90
90
  },
91
91
  "devDependencies": {
92
- "@kinetixui/tokens": "0.4.2",
92
+ "@kinetixui/tokens": "0.4.3",
93
93
  "@testing-library/jest-dom": "^6.6.3",
94
94
  "@testing-library/react": "^16.1.0",
95
95
  "@testing-library/user-event": "^14.5.2",
@@ -35,8 +35,31 @@ const ChartContainer = React.forwardRef<
35
35
  * primitive for keyboard data-point navigation.
36
36
  */
37
37
  label?: string;
38
+ /**
39
+ * Render a placeholder in place of the chart. `"loading"` shows a shimmer,
40
+ * `"empty"` / `"error"` show a message (override with `stateMessage`).
41
+ */
42
+ state?: "loading" | "empty" | "error";
43
+ stateMessage?: string;
44
+ /**
45
+ * Visually-hidden `<table>` rendered after the chart so a screen reader can
46
+ * read the underlying numbers. Pair with a concise `label`.
47
+ */
48
+ srTable?: { columns: React.ReactNode[]; rows: React.ReactNode[][] };
38
49
  }
39
- >(({ id, className, children, config, label, role, "aria-label": ariaLabel, ...props }, ref) => {
50
+ >(({
51
+ id,
52
+ className,
53
+ children,
54
+ config,
55
+ label,
56
+ state,
57
+ stateMessage,
58
+ srTable,
59
+ role,
60
+ "aria-label": ariaLabel,
61
+ ...props
62
+ }, ref) => {
40
63
  const uniqueId = React.useId();
41
64
  const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
42
65
  const name = ariaLabel ?? label ?? "Chart";
@@ -83,13 +106,94 @@ const ChartContainer = React.forwardRef<
83
106
  {...props}
84
107
  >
85
108
  <ChartStyle id={chartId} config={config} />
86
- <RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer>
109
+ {state ? (
110
+ <ChartState state={state} message={stateMessage} />
111
+ ) : (
112
+ <RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer>
113
+ )}
114
+ {srTable && !state ? <ChartSrTable {...srTable} /> : null}
87
115
  </div>
88
116
  </ChartContext.Provider>
89
117
  );
90
118
  });
91
119
  ChartContainer.displayName = "Chart";
92
120
 
121
+ const STATE_COPY = {
122
+ loading: "Loading chart…",
123
+ empty: "No data to show",
124
+ error: "Couldn't load this chart",
125
+ } as const;
126
+
127
+ function ChartState({
128
+ state,
129
+ message,
130
+ }: {
131
+ state: "loading" | "empty" | "error";
132
+ message?: string;
133
+ }) {
134
+ return (
135
+ <div
136
+ role={state === "error" ? "alert" : "status"}
137
+ className={cn(
138
+ "flex h-full w-full flex-col items-center justify-center gap-2 rounded-md border border-dashed p-6 text-center text-xs",
139
+ state === "error" ? "border-destructive/40 text-destructive" : "border-border text-muted-foreground",
140
+ )}
141
+ >
142
+ {state === "loading" ? (
143
+ <div aria-hidden className="flex w-full max-w-[220px] items-end justify-between gap-1.5">
144
+ {[40, 72, 56, 88, 48, 64, 80].map((h, i) => (
145
+ <span
146
+ key={i}
147
+ className="w-full animate-pulse rounded-sm bg-muted"
148
+ style={{ height: `${h}px`, animationDelay: `${i * 90}ms` }}
149
+ />
150
+ ))}
151
+ </div>
152
+ ) : (
153
+ <svg aria-hidden viewBox="0 0 24 24" className="size-5" fill="none" stroke="currentColor" strokeWidth={1.5}>
154
+ {state === "empty" ? (
155
+ <path d="M3 3v18h18M7 15l4-4 3 3 5-6" strokeLinecap="round" strokeLinejoin="round" />
156
+ ) : (
157
+ <path d="M12 9v4m0 4h.01M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.7 3.86a2 2 0 0 0-3.42 0Z" strokeLinecap="round" strokeLinejoin="round" />
158
+ )}
159
+ </svg>
160
+ )}
161
+ <span>{message ?? STATE_COPY[state]}</span>
162
+ </div>
163
+ );
164
+ }
165
+
166
+ function ChartSrTable({
167
+ columns,
168
+ rows,
169
+ }: {
170
+ columns: React.ReactNode[];
171
+ rows: React.ReactNode[][];
172
+ }) {
173
+ return (
174
+ <table className="sr-only">
175
+ <thead>
176
+ <tr>
177
+ {columns.map((c, i) => (
178
+ <th key={i} scope="col">
179
+ {c}
180
+ </th>
181
+ ))}
182
+ </tr>
183
+ </thead>
184
+ <tbody>
185
+ {rows.map((r, i) => (
186
+ <tr key={i}>
187
+ {r.map((cell, j) => (
188
+ <td key={j}>{cell}</td>
189
+ ))}
190
+ </tr>
191
+ ))}
192
+ </tbody>
193
+ </table>
194
+ );
195
+ }
196
+
93
197
  const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
94
198
  const colorConfig = Object.entries(config).filter(([, c]) => c.theme || c.color);
95
199
  if (!colorConfig.length) return null;
@@ -70,6 +70,9 @@ export default {
70
70
  3: c("--chart-3"),
71
71
  4: c("--chart-4"),
72
72
  5: c("--chart-5"),
73
+ 6: c("--chart-6"),
74
+ 7: c("--chart-7"),
75
+ 8: c("--chart-8"),
73
76
  },
74
77
  sidebar: {
75
78
  DEFAULT: c("--sidebar"),