@xex/ui 0.1.0 → 0.2.0

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.
@@ -29,10 +29,10 @@ import clsx2 from "clsx";
29
29
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
30
30
  var variants = {
31
31
  primary: "bg-brand-600 hover:bg-brand-700 text-white shadow-sm",
32
- secondary: "bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 shadow-sm",
32
+ secondary: "bg-surface border border-gray-300 text-gray-700 hover:bg-gray-50 shadow-sm",
33
33
  ghost: "bg-transparent text-gray-600 hover:bg-gray-100",
34
34
  danger: "bg-red-600 hover:bg-red-700 text-white shadow-sm",
35
- outline: "border border-brand-500 text-brand-700 hover:bg-brand-50 bg-transparent"
35
+ outline: "border border-brand-500 text-accent hover:bg-accent-soft bg-transparent"
36
36
  };
37
37
  var sizes2 = {
38
38
  xs: "px-2 py-1 text-xs rounded-md gap-1",
@@ -2,7 +2,7 @@ import {
2
2
  Button,
3
3
  LoadingDots,
4
4
  useTheme
5
- } from "./chunk-3M3PAPT3.js";
5
+ } from "./chunk-DBZZKHSF.js";
6
6
 
7
7
  // src/font-picker/FontPicker.tsx
8
8
  import { useEffect as useEffect2, useId, useLayoutEffect, useMemo, useRef, useState as useState2 } from "react";
@@ -193,16 +193,16 @@ function FontPicker() {
193
193
  openList();
194
194
  }
195
195
  },
196
- className: "flex w-full cursor-default items-center justify-between rounded-lg border border-gray-300 bg-white px-3 py-2 text-left text-sm text-gray-900 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:border-brand-400",
196
+ className: "flex w-full cursor-default items-center justify-between rounded-lg border border-gray-300 bg-surface px-3 py-2 text-left text-sm text-gray-900 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:border-brand-400",
197
197
  children: [
198
198
  /* @__PURE__ */ jsx("span", { className: "truncate", style: { fontFamily: fontStack(font) }, children: font.family }),
199
- /* @__PURE__ */ jsx(ChevronUpDownIcon, { className: "h-5 w-5 shrink-0 text-gray-400" })
199
+ /* @__PURE__ */ jsx(ChevronUpDownIcon, { className: "h-5 w-5 shrink-0 text-gray-500" })
200
200
  ]
201
201
  }
202
202
  ),
203
- open && /* @__PURE__ */ jsxs("div", { className: "absolute left-0 right-0 z-40 mt-1 rounded-xl border border-gray-200 bg-white p-2 shadow-xl", children: [
203
+ open && /* @__PURE__ */ jsxs("div", { className: "absolute left-0 right-0 z-40 mt-1 rounded-xl border border-gray-200 bg-surface p-2 shadow-xl", children: [
204
204
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
205
- /* @__PURE__ */ jsx(MagnifyingGlassIcon, { className: "pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" }),
205
+ /* @__PURE__ */ jsx(MagnifyingGlassIcon, { className: "pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-500" }),
206
206
  /* @__PURE__ */ jsx(
207
207
  "input",
208
208
  {
@@ -217,7 +217,7 @@ function FontPicker() {
217
217
  "aria-label": "Search fonts",
218
218
  "aria-activedescendant": matches[active] ? optId(active) : void 0,
219
219
  placeholder: "Search Google Fonts\u2026",
220
- className: "w-full rounded-lg border border-gray-300 bg-white py-1.5 pl-8 pr-2 text-sm text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:border-brand-400"
220
+ className: "w-full rounded-lg border border-gray-300 bg-surface py-1.5 pl-8 pr-2 text-sm text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:border-brand-400"
221
221
  }
222
222
  )
223
223
  ] }),
@@ -239,7 +239,7 @@ function FontPicker() {
239
239
  "Loading fonts ",
240
240
  /* @__PURE__ */ jsx(LoadingDots, {})
241
241
  ] }),
242
- catalog.status === "error" && /* @__PURE__ */ jsxs("li", { className: "px-3 py-2 text-sm text-red-600", children: [
242
+ catalog.status === "error" && /* @__PURE__ */ jsxs("li", { className: "px-3 py-2 text-sm text-danger-ink", children: [
243
243
  "Couldn\u2019t load the font list.",
244
244
  " ",
245
245
  /* @__PURE__ */ jsx("button", { type: "button", className: "font-medium underline", onClick: catalog.retry, children: "Retry" })
@@ -254,19 +254,19 @@ function FontPicker() {
254
254
  className: clsx(
255
255
  "flex w-full cursor-default items-center justify-between rounded-md px-3 py-1.5 text-left text-sm text-gray-800",
256
256
  i === active && "bg-gray-100 ring-2 ring-inset ring-brand-300",
257
- f.family === font.family && "bg-brand-50 font-medium"
257
+ f.family === font.family && "bg-accent-soft font-medium"
258
258
  ),
259
259
  children: [
260
260
  /* @__PURE__ */ jsx("span", { style: { fontFamily: fontStack(f) }, children: f.family }),
261
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-xs text-gray-400", children: [
261
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-xs text-gray-500", children: [
262
262
  f.category,
263
- f.family === font.family && /* @__PURE__ */ jsx(CheckIcon, { className: "h-4 w-4 text-brand-600" })
263
+ f.family === font.family && /* @__PURE__ */ jsx(CheckIcon, { className: "h-4 w-4 text-accent" })
264
264
  ] })
265
265
  ]
266
266
  }
267
267
  ) }, f.family)),
268
268
  catalog.status === "ready" && matches.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-gray-500", children: "No fonts match." }),
269
- visible < matches.length && /* @__PURE__ */ jsxs("li", { className: "px-3 py-2 text-xs text-gray-400", children: [
269
+ visible < matches.length && /* @__PURE__ */ jsxs("li", { className: "px-3 py-2 text-xs text-gray-500", children: [
270
270
  "Showing ",
271
271
  visible,
272
272
  " of ",
@@ -282,7 +282,7 @@ function FontPicker() {
282
282
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500", children: [
283
283
  "Selected: ",
284
284
  /* @__PURE__ */ jsx("span", { className: "font-semibold text-gray-900", children: font.family }),
285
- font.stack ? "" : /* @__PURE__ */ jsxs("span", { className: "text-gray-400", children: [
285
+ font.stack ? "" : /* @__PURE__ */ jsxs("span", { className: "text-gray-500", children: [
286
286
  " \xB7 ",
287
287
  font.category
288
288
  ] })
package/dist/index.d.ts CHANGED
@@ -137,7 +137,7 @@ interface DataTableColumn<T> {
137
137
  sortable?: boolean;
138
138
  /** Adds a multi-select dropdown listing this column's distinct values (OR within a column, AND across columns). Default: false. */
139
139
  filter?: boolean;
140
- /** Placeholder for that dropdown, e.g. "All roles". */
140
+ /** Placeholder for that dropdown, e.g. "All roles". Defaults to "Any <header>". */
141
141
  filterLabel?: string;
142
142
  align?: 'left' | 'right';
143
143
  /** Default width as a relative weight (a column with 2 starts twice as wide as one with 1). Default: 1. */
package/dist/index.js CHANGED
@@ -4,18 +4,18 @@ import {
4
4
  Spinner,
5
5
  ThemeProvider,
6
6
  useTheme
7
- } from "./chunk-3M3PAPT3.js";
7
+ } from "./chunk-DBZZKHSF.js";
8
8
 
9
9
  // src/components/Badge.tsx
10
10
  import clsx from "clsx";
11
11
  import { jsx, jsxs } from "react/jsx-runtime";
12
12
  var variants = {
13
- default: { pill: "bg-brand-100 text-brand-700", dot: "bg-brand-500" },
14
- success: { pill: "bg-emerald-100 text-emerald-700", dot: "bg-emerald-500" },
15
- warning: { pill: "bg-amber-100 text-amber-700", dot: "bg-amber-500" },
16
- danger: { pill: "bg-red-100 text-red-700", dot: "bg-red-500" },
17
- info: { pill: "bg-sky-100 text-sky-700", dot: "bg-sky-500" },
18
- purple: { pill: "bg-purple-100 text-purple-700", dot: "bg-purple-500" },
13
+ default: { pill: "bg-accent-softer text-accent", dot: "bg-brand-500" },
14
+ success: { pill: "bg-success-soft text-success-ink", dot: "bg-success" },
15
+ warning: { pill: "bg-warning-soft text-warning-ink", dot: "bg-warning" },
16
+ danger: { pill: "bg-danger-soft text-danger-ink", dot: "bg-danger" },
17
+ info: { pill: "bg-info-soft text-info-ink", dot: "bg-info" },
18
+ purple: { pill: "bg-purple-soft text-purple-ink", dot: "bg-purple" },
19
19
  gray: { pill: "bg-gray-100 text-gray-700", dot: "bg-gray-500" }
20
20
  };
21
21
  function Badge({ variant = "default", dot = false, className, children }) {
@@ -128,7 +128,7 @@ function Modal({ open, onClose, title, footer, size = "md", ariaLabel, children
128
128
  "aria-label": title ? void 0 : ariaLabel,
129
129
  tabIndex: -1,
130
130
  onKeyDown: trapTab,
131
- className: clsx2("relative flex max-h-[90vh] w-full flex-col rounded-2xl bg-white shadow-2xl focus:outline-none", sizes[size]),
131
+ className: clsx2("relative flex max-h-[90vh] w-full flex-col rounded-2xl bg-surface shadow-2xl focus:outline-none", sizes[size]),
132
132
  children: [
133
133
  title && /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between border-b border-gray-100 px-6 py-4", children: [
134
134
  /* @__PURE__ */ jsx2("h2", { id: titleId, className: "text-lg font-semibold text-gray-900", children: title }),
@@ -138,7 +138,7 @@ function Modal({ open, onClose, title, footer, size = "md", ariaLabel, children
138
138
  type: "button",
139
139
  onClick: onClose,
140
140
  "aria-label": "Close",
141
- className: "rounded-lg p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400",
141
+ className: "rounded-lg p-1 text-gray-500 hover:bg-gray-100 hover:text-gray-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400",
142
142
  children: /* @__PURE__ */ jsx2(XMarkIcon, { className: "h-5 w-5" })
143
143
  }
144
144
  )
@@ -165,11 +165,11 @@ function ConfirmDialog({
165
165
  onCancel
166
166
  }) {
167
167
  return /* @__PURE__ */ jsxs3(Modal, { open, onClose: onCancel, size: "lg", ariaLabel: title, children: [
168
- /* @__PURE__ */ jsxs3("div", { role: "alert", className: "flex items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4", children: [
169
- /* @__PURE__ */ jsx3(ExclamationTriangleIcon, { className: "mt-0.5 h-6 w-6 shrink-0 text-red-600" }),
168
+ /* @__PURE__ */ jsxs3("div", { role: "alert", className: "flex items-start gap-3 rounded-xl border border-danger-line bg-danger-soft p-4", children: [
169
+ /* @__PURE__ */ jsx3(ExclamationTriangleIcon, { className: "mt-0.5 h-6 w-6 shrink-0 text-danger-ink" }),
170
170
  /* @__PURE__ */ jsxs3("div", { children: [
171
- /* @__PURE__ */ jsx3("p", { className: "font-bold text-red-800", children: title }),
172
- /* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm text-red-700", children: message })
171
+ /* @__PURE__ */ jsx3("p", { className: "font-bold text-danger-ink", children: title }),
172
+ /* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm text-danger-ink", children: message })
173
173
  ] })
174
174
  ] }),
175
175
  /* @__PURE__ */ jsxs3("div", { className: "mt-5 flex justify-end gap-2", children: [
@@ -197,10 +197,10 @@ function Select({ label, options, placeholder, error, hint, className, id, disab
197
197
  disabled,
198
198
  "aria-invalid": !!error || void 0,
199
199
  className: clsx3(
200
- "w-full appearance-none rounded-lg border bg-white px-3 py-2 pr-9 text-sm text-gray-900",
200
+ "w-full appearance-none rounded-lg border bg-surface px-3 py-2 pr-9 text-sm text-gray-900",
201
201
  "focus:outline-none focus:ring-2 focus:ring-brand-400 focus:border-brand-400",
202
202
  "disabled:bg-gray-50 disabled:text-gray-400 disabled:cursor-not-allowed",
203
- error ? "border-red-400" : "border-gray-300"
203
+ error ? "border-danger" : "border-gray-300"
204
204
  ),
205
205
  ...rest,
206
206
  children: [
@@ -209,9 +209,9 @@ function Select({ label, options, placeholder, error, hint, className, id, disab
209
209
  ]
210
210
  }
211
211
  ),
212
- /* @__PURE__ */ jsx4(ChevronDownIcon, { className: "pointer-events-none absolute right-3 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-400" })
212
+ /* @__PURE__ */ jsx4(ChevronDownIcon, { className: "pointer-events-none absolute right-3 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-500" })
213
213
  ] }),
214
- error ? /* @__PURE__ */ jsx4("p", { className: "mt-1 text-xs text-red-600", children: error }) : hint ? /* @__PURE__ */ jsx4("p", { className: "mt-1 text-xs text-gray-500", children: hint }) : null
214
+ error ? /* @__PURE__ */ jsx4("p", { className: "mt-1 text-xs text-danger-ink", children: error }) : hint ? /* @__PURE__ */ jsx4("p", { className: "mt-1 text-xs text-gray-500", children: hint }) : null
215
215
  ] });
216
216
  }
217
217
 
@@ -225,24 +225,24 @@ function Input({ label, error, hint, icon, className, id, ...rest }) {
225
225
  return /* @__PURE__ */ jsxs5("div", { className, children: [
226
226
  label && /* @__PURE__ */ jsx5("label", { htmlFor: inputId, className: "mb-1 block text-sm font-medium text-gray-700", children: label }),
227
227
  /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
228
- icon && /* @__PURE__ */ jsx5("span", { className: "pointer-events-none absolute left-3 top-1/2 flex -translate-y-1/2 items-center text-gray-400 [&>svg]:h-5 [&>svg]:w-5", children: icon }),
228
+ icon && /* @__PURE__ */ jsx5("span", { className: "pointer-events-none absolute left-3 top-1/2 flex -translate-y-1/2 items-center text-gray-500 [&>svg]:h-5 [&>svg]:w-5", children: icon }),
229
229
  /* @__PURE__ */ jsx5(
230
230
  "input",
231
231
  {
232
232
  id: inputId,
233
233
  "aria-invalid": !!error || void 0,
234
234
  className: clsx4(
235
- "w-full rounded-lg border bg-white px-3 py-2 text-sm text-gray-900 placeholder:text-gray-400",
235
+ "w-full rounded-lg border bg-surface px-3 py-2 text-sm text-gray-900 placeholder:text-gray-400",
236
236
  "focus:outline-none focus:ring-2 focus:ring-brand-400 focus:border-brand-400",
237
237
  "disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400",
238
238
  icon && "pl-9",
239
- error ? "border-red-400" : "border-gray-300"
239
+ error ? "border-danger" : "border-gray-300"
240
240
  ),
241
241
  ...rest
242
242
  }
243
243
  )
244
244
  ] }),
245
- error ? /* @__PURE__ */ jsx5("p", { className: "mt-1 text-xs text-red-600", children: error }) : hint ? /* @__PURE__ */ jsx5("p", { className: "mt-1 text-xs text-gray-500", children: hint }) : null
245
+ error ? /* @__PURE__ */ jsx5("p", { className: "mt-1 text-xs text-danger-ink", children: error }) : hint ? /* @__PURE__ */ jsx5("p", { className: "mt-1 text-xs text-gray-500", children: hint }) : null
246
246
  ] });
247
247
  }
248
248
 
@@ -256,7 +256,7 @@ var sizes2 = {
256
256
  lg: "h-12 w-12 text-base",
257
257
  xl: "h-14 w-14 text-lg"
258
258
  };
259
- var AVATAR_COLORS = ["#6366f1", "#0ea5e9", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899", "#14b8a6"];
259
+ var AVATAR_COLORS = ["#4f46e5", "#0369a1", "#047857", "#b45309", "#dc2626", "#7c3aed", "#db2777", "#0f766e"];
260
260
  function initialsOf(name) {
261
261
  const parts = name.trim().split(/\s+/).filter(Boolean);
262
262
  if (parts.length === 0) return "?";
@@ -320,7 +320,7 @@ function PaginationBar({
320
320
  /* @__PURE__ */ jsx7("div", { children: totalPages > 1 && /* @__PURE__ */ jsxs6("nav", { "aria-label": placement ? `Pagination, ${placement} table` : "Pagination", className: "flex items-center gap-1", children: [
321
321
  /* @__PURE__ */ jsx7("button", { type: "button", className: navBtn, disabled: page <= 1, onClick: () => onPageChange(page - 1), children: "\xAB Prev" }),
322
322
  pageWindow(page, totalPages).map(
323
- (p, i) => p === null ? /* @__PURE__ */ jsx7("span", { className: "px-1 text-gray-400", children: "\u2026" }, `gap-${i}`) : /* @__PURE__ */ jsx7(
323
+ (p, i) => p === null ? /* @__PURE__ */ jsx7("span", { className: "px-1 text-gray-500", children: "\u2026" }, `gap-${i}`) : /* @__PURE__ */ jsx7(
324
324
  "button",
325
325
  {
326
326
  type: "button",
@@ -349,7 +349,7 @@ function PaginationBar({
349
349
  {
350
350
  value: pageSize,
351
351
  onChange: (e) => onPageSizeChange(Number(e.target.value)),
352
- className: "rounded-md border border-gray-300 bg-white px-1.5 py-1 text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:border-brand-400",
352
+ className: "rounded-md border border-gray-300 bg-surface px-1.5 py-1 text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:border-brand-400",
353
353
  children: pageSizeOptions.map((n) => /* @__PURE__ */ jsx7("option", { value: n, children: n }, n))
354
354
  }
355
355
  )
@@ -390,7 +390,7 @@ function SegmentedControl({ options, value, onChange, label, className }) {
390
390
  onClick: () => onChange(o.value),
391
391
  className: clsx7(
392
392
  "rounded-md px-2.5 py-1 text-xs font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400",
393
- selected ? "bg-white text-gray-900 shadow-sm" : "text-gray-500 hover:text-gray-800"
393
+ selected ? "bg-surface text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"
394
394
  ),
395
395
  children: o.label
396
396
  },
@@ -457,12 +457,12 @@ function MultiSelect({ options, value, onChange, label, className }) {
457
457
  "aria-label": labels.length ? `${label}: ${labels.join(", ")}` : label,
458
458
  onClick: () => setOpen((o) => !o),
459
459
  className: clsx8(
460
- "flex w-full items-center justify-between gap-2 rounded-lg border bg-white px-3 py-2 text-left text-sm focus:outline-none focus:ring-2 focus:ring-brand-400 focus:border-brand-400",
460
+ "flex w-full items-center justify-between gap-2 rounded-lg border bg-surface px-3 py-2 text-left text-sm focus:outline-none focus:ring-2 focus:ring-brand-400 focus:border-brand-400",
461
461
  value.length ? "border-brand-500 text-gray-900" : "border-gray-300 text-gray-500"
462
462
  ),
463
463
  children: [
464
464
  /* @__PURE__ */ jsx9("span", { className: "truncate", children: summary }),
465
- /* @__PURE__ */ jsx9(ChevronDownIcon2, { className: "h-5 w-5 shrink-0 text-gray-400" })
465
+ /* @__PURE__ */ jsx9(ChevronDownIcon2, { className: "h-5 w-5 shrink-0 text-gray-500" })
466
466
  ]
467
467
  }
468
468
  ),
@@ -475,19 +475,19 @@ function MultiSelect({ options, value, onChange, label, className }) {
475
475
  onMouseDown: (e) => {
476
476
  if (!(e.target instanceof HTMLInputElement)) e.preventDefault();
477
477
  },
478
- className: "absolute left-0 z-20 mt-1 max-h-64 min-w-full overflow-y-auto rounded-xl border border-gray-200 bg-white p-1 shadow-xl",
478
+ className: "absolute left-0 z-20 mt-1 max-h-64 min-w-full overflow-y-auto rounded-xl border border-gray-200 bg-surface p-1 shadow-xl",
479
479
  children: [
480
480
  options.map((o) => /* @__PURE__ */ jsxs7("label", { className: "flex cursor-default items-center gap-2 rounded-md px-2.5 py-1.5 text-sm text-gray-800 hover:bg-gray-50", children: [
481
481
  /* @__PURE__ */ jsx9("input", { type: "checkbox", className: "h-4 w-4 rounded", checked: value.includes(o.value), onChange: () => toggle(o.value) }),
482
482
  /* @__PURE__ */ jsx9("span", { className: "flex-1 whitespace-nowrap", children: o.label }),
483
- o.count !== void 0 && /* @__PURE__ */ jsx9("span", { className: "text-xs tabular-nums text-gray-400", children: o.count })
483
+ o.count !== void 0 && /* @__PURE__ */ jsx9("span", { className: "text-xs tabular-nums text-gray-500", children: o.count })
484
484
  ] }, o.value)),
485
485
  value.length > 0 && /* @__PURE__ */ jsx9(
486
486
  "button",
487
487
  {
488
488
  type: "button",
489
489
  onClick: () => onChange([]),
490
- className: "mt-1 w-full rounded-md border-t border-gray-100 px-2.5 py-1.5 text-left text-xs font-medium text-brand-700 hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400",
490
+ className: "mt-1 w-full rounded-md border-t border-gray-100 px-2.5 py-1.5 text-left text-xs font-medium text-accent hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400",
491
491
  children: "Clear selection"
492
492
  }
493
493
  )
@@ -700,7 +700,7 @@ function DataTable({
700
700
  MultiSelect,
701
701
  {
702
702
  className: "w-full sm:w-44",
703
- label: c.filterLabel ?? `All ${c.header.toLowerCase()}`,
703
+ label: c.filterLabel ?? `Any ${c.header.toLowerCase()}`,
704
704
  value: filters[c.id] ?? [],
705
705
  onChange: (v) => {
706
706
  setFilters((f) => ({ ...f, [c.id]: v }));
@@ -776,7 +776,7 @@ function DataTable({
776
776
  ),
777
777
  children: [
778
778
  /* @__PURE__ */ jsx10("span", { className: "truncate", children: c.header }),
779
- /* @__PURE__ */ jsx10(Icon, { className: clsx9("h-4 w-4 shrink-0", sorted ? "text-brand-600" : "text-gray-400"), "aria-hidden": "true" })
779
+ /* @__PURE__ */ jsx10(Icon, { className: clsx9("h-4 w-4 shrink-0", sorted ? "text-accent" : "text-gray-500"), "aria-hidden": "true" })
780
780
  ]
781
781
  }
782
782
  ) : /* @__PURE__ */ jsx10("span", { className: "block truncate", children: c.header }),
@@ -822,7 +822,7 @@ function DataTable({
822
822
  pageRows.length === 0 && /* @__PURE__ */ jsx10("tr", { className: "border-t border-gray-100", children: /* @__PURE__ */ jsxs8("td", { colSpan: columns.length, className: "px-4 py-10 text-center text-sm text-gray-500", children: [
823
823
  emptyMessage,
824
824
  " ",
825
- filterActive && /* @__PURE__ */ jsx10("button", { type: "button", onClick: clearAll, className: "font-medium text-brand-700 underline", children: "Clear filters" })
825
+ filterActive && /* @__PURE__ */ jsx10("button", { type: "button", onClick: clearAll, className: "font-medium text-accent underline", children: "Clear filters" })
826
826
  ] }) })
827
827
  ] })
828
828
  ]
@@ -837,15 +837,15 @@ import clsx10 from "clsx";
837
837
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
838
838
  var fills = {
839
839
  brand: "bg-brand-600",
840
- success: "bg-emerald-500",
841
- warning: "bg-amber-500",
842
- danger: "bg-red-500"
840
+ success: "bg-success",
841
+ warning: "bg-warning",
842
+ danger: "bg-danger"
843
843
  };
844
844
  var strokes = {
845
- brand: "text-brand-600",
846
- success: "text-emerald-500",
847
- warning: "text-amber-500",
848
- danger: "text-red-500"
845
+ brand: "text-accent",
846
+ success: "text-success",
847
+ warning: "text-warning",
848
+ danger: "text-danger"
849
849
  };
850
850
  var heights = { sm: "h-1", md: "h-2", lg: "h-3" };
851
851
  function ProgressBar({ value, label, showLabel = false, tone = "brand", size = "md", className }) {
@@ -943,7 +943,7 @@ function LoadingOverlay({ loading, label = "Loading\u2026", children, className
943
943
  return /* @__PURE__ */ jsxs10("div", { className: className ? `relative ${className}` : "relative", "aria-busy": loading || void 0, children: [
944
944
  /* @__PURE__ */ jsx13("div", { inert: loading || void 0, children }),
945
945
  loading && /* @__PURE__ */ jsxs10("div", { className: "xex-overlay absolute inset-0 z-10 flex items-center justify-center gap-3 rounded-[inherit] backdrop-blur-[1px]", children: [
946
- /* @__PURE__ */ jsx13(Spinner, { size: "lg", label, className: "text-brand-600" }),
946
+ /* @__PURE__ */ jsx13(Spinner, { size: "lg", label, className: "text-accent" }),
947
947
  /* @__PURE__ */ jsx13("span", { className: "text-sm font-medium text-gray-700", "aria-hidden": "true", children: label })
948
948
  ] })
949
949
  ] });
@@ -962,12 +962,12 @@ var defaults = {
962
962
  function FetchStatus({ state, messages, onRetry }) {
963
963
  const text = messages?.[state] ?? defaults[state];
964
964
  return /* @__PURE__ */ jsxs11("p", { role: "status", "aria-live": "polite", className: "flex items-center gap-2 text-sm", children: [
965
- state === "idle" && /* @__PURE__ */ jsx14(CloudIcon, { className: "h-5 w-5 text-gray-400" }),
966
- state === "loading" && /* @__PURE__ */ jsx14(Spinner, { size: "md", decorative: true, className: "text-brand-600" }),
967
- state === "success" && /* @__PURE__ */ jsx14(CheckCircleIcon, { className: "h-5 w-5 text-emerald-500" }),
968
- state === "error" && /* @__PURE__ */ jsx14(ExclamationCircleIcon, { className: "h-5 w-5 text-red-500" }),
969
- /* @__PURE__ */ jsx14("span", { className: state === "error" ? "text-red-700" : state === "idle" ? "text-gray-500" : "text-gray-700", children: text }),
970
- state === "loading" && /* @__PURE__ */ jsx14(LoadingDots, { className: "text-gray-400" }),
965
+ state === "idle" && /* @__PURE__ */ jsx14(CloudIcon, { className: "h-5 w-5 text-gray-500" }),
966
+ state === "loading" && /* @__PURE__ */ jsx14(Spinner, { size: "md", decorative: true, className: "text-accent" }),
967
+ state === "success" && /* @__PURE__ */ jsx14(CheckCircleIcon, { className: "h-5 w-5 text-success" }),
968
+ state === "error" && /* @__PURE__ */ jsx14(ExclamationCircleIcon, { className: "h-5 w-5 text-danger" }),
969
+ /* @__PURE__ */ jsx14("span", { className: state === "error" ? "text-danger-ink" : state === "idle" ? "text-gray-500" : "text-gray-700", children: text }),
970
+ state === "loading" && /* @__PURE__ */ jsx14(LoadingDots, { className: "text-gray-500" }),
971
971
  state === "error" && onRetry && /* @__PURE__ */ jsx14(Button, { size: "xs", variant: "outline", onClick: onRetry, children: "Retry" })
972
972
  ] });
973
973
  }
@@ -1064,7 +1064,7 @@ function Tabs({
1064
1064
  "inline-flex shrink-0 items-center justify-center whitespace-nowrap font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400 disabled:cursor-not-allowed disabled:opacity-50",
1065
1065
  sizes3[size],
1066
1066
  fullWidth && "flex-1",
1067
- variant === "underline" ? clsx12("rounded-t-lg", isSelected ? "text-brand-700" : "text-gray-500 hover:text-gray-800") : clsx12("rounded-full", isSelected ? "bg-brand-600 text-white" : "text-gray-600 hover:bg-gray-100")
1067
+ variant === "underline" ? clsx12("rounded-t-lg", isSelected ? "text-accent" : "text-gray-500 hover:text-gray-800") : clsx12("rounded-full", isSelected ? "bg-brand-600 text-white" : "text-gray-600 hover:bg-gray-100")
1068
1068
  ),
1069
1069
  children: [
1070
1070
  item.icon,
@@ -1074,7 +1074,7 @@ function Tabs({
1074
1074
  {
1075
1075
  className: clsx12(
1076
1076
  "rounded-full px-1.5 py-px text-[11px] font-semibold tabular-nums",
1077
- isSelected ? variant === "pills" ? "bg-white/25 text-white" : "bg-brand-100 text-brand-700" : "bg-gray-100 text-gray-600"
1077
+ isSelected ? variant === "pills" ? "bg-white/25 text-white" : "bg-accent-softer text-accent" : "bg-gray-100 text-gray-600"
1078
1078
  ),
1079
1079
  children: item.badge
1080
1080
  }
@@ -1107,7 +1107,7 @@ function Tabs({
1107
1107
  import { Bars3Icon } from "@heroicons/react/24/outline";
1108
1108
  import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1109
1109
  function Header({ title, subtitle, actions, onMenu }) {
1110
- return /* @__PURE__ */ jsxs13("header", { className: "sticky top-0 z-30 flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 bg-white px-4 py-4 sm:px-6", children: [
1110
+ return /* @__PURE__ */ jsxs13("header", { className: "sticky top-0 z-30 flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 bg-surface px-4 py-4 sm:px-6", children: [
1111
1111
  /* @__PURE__ */ jsxs13("div", { className: "flex min-w-0 items-center gap-3", children: [
1112
1112
  onMenu && /* @__PURE__ */ jsx16(
1113
1113
  "button",
@@ -1279,7 +1279,7 @@ function PageGuide({ sections }) {
1279
1279
  },
1280
1280
  className: clsx13(
1281
1281
  "block py-1.5 pl-3 pr-2 text-sm transition-colors",
1282
- i === active ? "font-medium text-brand-700" : "text-gray-500 hover:text-gray-900"
1282
+ i === active ? "font-medium text-accent" : "text-gray-500 hover:text-gray-900"
1283
1283
  ),
1284
1284
  children: s.label
1285
1285
  }
@@ -1330,7 +1330,7 @@ function Sidebar({ items, active, onSelect, open, onClose }) {
1330
1330
  "aria-label": "Main navigation",
1331
1331
  onKeyDown: open ? trapTab : void 0,
1332
1332
  className: clsx14(
1333
- "fixed inset-y-0 left-0 z-50 flex w-64 flex-col border-r border-gray-200 bg-white duration-200",
1333
+ "fixed inset-y-0 left-0 z-50 flex w-64 flex-col border-r border-gray-200 bg-surface duration-200",
1334
1334
  "lg:sticky lg:top-0 lg:z-auto lg:h-screen lg:w-60 lg:shrink-0 lg:translate-x-0",
1335
1335
  // Closed on small screens: invisible = out of the tab order. It switches off only after
1336
1336
  // the slide-out finishes, but switches on immediately so focus can move in on open.
@@ -1342,7 +1342,7 @@ function Sidebar({ items, active, onSelect, open, onClose }) {
1342
1342
  /* @__PURE__ */ jsx19("span", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-brand-600 text-sm font-bold text-white", children: "X" }),
1343
1343
  /* @__PURE__ */ jsx19("span", { className: "text-lg font-bold tracking-tight text-gray-900", children: "XeX" })
1344
1344
  ] }),
1345
- /* @__PURE__ */ jsx19("button", { type: "button", onClick: onClose, "aria-label": "Close navigation", className: "rounded-lg p-1 text-gray-400 hover:bg-gray-100 lg:hidden", children: /* @__PURE__ */ jsx19(XMarkIcon3, { className: "h-5 w-5" }) })
1345
+ /* @__PURE__ */ jsx19("button", { type: "button", onClick: onClose, "aria-label": "Close navigation", className: "rounded-lg p-1 text-gray-500 hover:bg-gray-100 lg:hidden", children: /* @__PURE__ */ jsx19(XMarkIcon3, { className: "h-5 w-5" }) })
1346
1346
  ] }),
1347
1347
  /* @__PURE__ */ jsx19("nav", { className: "flex-1 space-y-1 overflow-y-auto px-3 pb-4 pt-2", children: items.map(({ id, label, icon: Icon }) => /* @__PURE__ */ jsxs16(
1348
1348
  "a",
@@ -1411,7 +1411,7 @@ function ThemeSwitcher({ showLabels = false }) {
1411
1411
  type: "button",
1412
1412
  onClick: toggleMode,
1413
1413
  "aria-label": `Switch to ${mode === "dark" ? "light" : "dark"} mode`,
1414
- className: "flex items-center gap-1.5 rounded-lg border border-gray-300 bg-white px-2.5 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400",
1414
+ className: "flex items-center gap-1.5 rounded-lg border border-gray-300 bg-surface px-2.5 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400",
1415
1415
  children: [
1416
1416
  mode === "dark" ? /* @__PURE__ */ jsx20(SunIcon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx20(MoonIcon, { className: "h-4 w-4" }),
1417
1417
  mode === "dark" ? "Light" : "Dark"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xex/ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "XeX design system: React components, app shell and theme provider.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -46,11 +46,13 @@
46
46
  "scripts": {
47
47
  "build": "tsup",
48
48
  "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
49
- "typecheck": "tsc --noEmit"
49
+ "typecheck": "tsc --noEmit",
50
+ "test": "vitest run",
51
+ "test:watch": "vitest"
50
52
  },
51
53
  "dependencies": {
52
54
  "@heroicons/react": "^2.2.0",
53
- "@xex/tokens": "^0.1.0",
55
+ "@xex/tokens": "^0.2.0",
54
56
  "clsx": "^2.1.1"
55
57
  },
56
58
  "peerDependencies": {
@@ -58,9 +60,16 @@
58
60
  "react-dom": "^19.0.0"
59
61
  },
60
62
  "devDependencies": {
63
+ "@testing-library/jest-dom": "^7.0.1",
64
+ "@testing-library/react": "^16.3.3",
65
+ "@testing-library/user-event": "^14.6.8",
61
66
  "@types/react": "^19.0.0",
62
67
  "@types/react-dom": "^19.0.0",
68
+ "axe-core": "^4.14.0",
69
+ "jsdom": "^30.1.2",
63
70
  "tsup": "^8.3.5",
64
- "typescript": "^5.7.2"
71
+ "typescript": "^5.7.2",
72
+ "vitest": "^3.2.7",
73
+ "vitest-axe": "^0.1.0"
65
74
  }
66
75
  }