jcicl 1.1.5 → 1.2.2

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 (49) hide show
  1. package/Button/Button.d.ts +1 -0
  2. package/Button/Button.js +73 -70
  3. package/DesktopStepper/DesktopStepper.d.ts +24 -0
  4. package/DesktopStepper/DesktopStepper.js +204 -0
  5. package/DesktopStepper/index.d.ts +1 -0
  6. package/DesktopStepper/index.js +5 -0
  7. package/FieldGroup/FieldGroup.js +15 -12
  8. package/FieldVisibilityWrapper/FieldVisibilityWrapper.d.ts +11 -0
  9. package/FieldVisibilityWrapper/FieldVisibilityWrapper.js +48 -0
  10. package/FieldVisibilityWrapper/index.d.ts +1 -0
  11. package/FieldVisibilityWrapper/index.js +4 -0
  12. package/FormContext/createFormContext.js +91 -72
  13. package/FormContext/types.d.ts +8 -0
  14. package/FormFields/FormFields.d.ts +2 -2
  15. package/FormFields/FormFields.js +18 -18
  16. package/FormInput/FormInput.d.ts +4 -0
  17. package/FormInput/FormInput.js +39 -14
  18. package/LabeledCheckbox/LabeledCheckbox.js +4 -4
  19. package/LabeledCurrencyInput/LabeledCurrencyInput.js +33 -33
  20. package/LabeledInput/LabeledInput.d.ts +2 -0
  21. package/LabeledInput/LabeledInput.js +16 -14
  22. package/LabeledRadio/LabeledRadio.js +12 -12
  23. package/Nav/Nav.js +174 -151
  24. package/SelectableItemCard/SelectableItemCard.d.ts +9 -0
  25. package/SelectableItemCard/SelectableItemCard.js +24 -0
  26. package/SelectableItemCard/index.d.ts +1 -0
  27. package/SelectableItemCard/index.js +4 -0
  28. package/Stepper/Stepper.js +184 -226
  29. package/Table/Table.js +236 -234
  30. package/Tabs/Tabs.d.ts +42 -0
  31. package/Tabs/Tabs.js +121 -0
  32. package/Tabs/index.d.ts +1 -0
  33. package/Tabs/index.js +4 -0
  34. package/api.d.ts +16 -5
  35. package/api.js +42 -28
  36. package/assets/style.css +1 -1
  37. package/assets/tailwind.css +2 -2
  38. package/constants.js +1 -1
  39. package/formatters.d.ts +1 -0
  40. package/formatters.js +30 -17
  41. package/index.d.ts +2 -0
  42. package/index.js +64 -62
  43. package/package.json +1 -1
  44. package/problemDetails.d.ts +20 -0
  45. package/problemDetails.js +19 -0
  46. package/utils.d.ts +2 -2
  47. package/utils.js +35 -29
  48. package/validators.d.ts +8 -0
  49. package/validators.js +19 -9
package/Tabs/Tabs.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ export interface TabItem {
2
+ /** Text shown in the tab header. */
3
+ label: string;
4
+ /** Optional element rendered before the label (e.g. an icon). */
5
+ icon?: React.ReactNode;
6
+ /** Content rendered in the panel when this tab is active. */
7
+ content: React.ReactNode;
8
+ }
9
+ export type TabHeaderAlignment = 'left' | 'center' | 'right';
10
+ export interface TabsProps {
11
+ /** Ordered list of tabs. The first tab is shown by default. */
12
+ tabs: TabItem[];
13
+ /** Initially active tab index when uncontrolled. Defaults to 0. */
14
+ defaultIndex?: number;
15
+ /** Active tab index. Provide together with onChange for controlled use. */
16
+ activeIndex?: number;
17
+ /** Called with the next index whenever a tab is selected. */
18
+ onChange?: (index: number) => void;
19
+ /**
20
+ * Opt-in URL persistence (uncontrolled mode only). When set, the active tab's
21
+ * label is stored in the URL query param of this name via the History API —
22
+ * no router/dependency required. Give each Tabs on a page a UNIQUE key so they
23
+ * don't collide; persistence is per browser window since each window has its
24
+ * own URL. Tab labels should be unique, since the active tab is stored by label.
25
+ */
26
+ persistKey?: string;
27
+ /** Horizontal alignment of the tab headers. Defaults to 'left'. */
28
+ tabHeaderAlignment?: TabHeaderAlignment;
29
+ /** Header label font size override (CSS length; number is treated as px). Defaults to 27px. */
30
+ headerFontSize?: string | number;
31
+ /** Extra class(es) merged onto each tab header button (consumer override). */
32
+ headerClassName?: string;
33
+ /** Inline style merged onto each tab header button (consumer override). */
34
+ headerStyle?: React.CSSProperties;
35
+ }
36
+ /**
37
+ * A horizontal tab layout: a header row of selectable tabs and a panel showing
38
+ * the active tab's content. Works uncontrolled (manages its own active tab,
39
+ * seeded by `defaultIndex`) or controlled (pass `activeIndex` plus `onChange`).
40
+ */
41
+ export declare const Tabs: React.FC<TabsProps>;
42
+ export default Tabs;
package/Tabs/Tabs.js ADDED
@@ -0,0 +1,121 @@
1
+ import { jsxs as h, jsx as v } from "react/jsx-runtime";
2
+ import { useId as E, useState as S, useRef as T, useEffect as k } from "react";
3
+ import { cn as y } from "../cn.js";
4
+ const A = {
5
+ left: "justify-start",
6
+ center: "justify-between",
7
+ // spread headers across the full width
8
+ right: "justify-end"
9
+ }, x = (e, i) => {
10
+ if (typeof window > "u") return null;
11
+ const r = new URLSearchParams(window.location.search).get(e);
12
+ if (r == null) return null;
13
+ const l = i.findIndex((d) => d.label === r);
14
+ return l >= 0 ? l : null;
15
+ }, N = (e, i) => {
16
+ if (typeof window > "u") return;
17
+ const r = new URLSearchParams(window.location.search);
18
+ r.set(e, i), window.history.replaceState(
19
+ window.history.state,
20
+ "",
21
+ `${window.location.pathname}?${r}${window.location.hash}`
22
+ );
23
+ }, M = ({
24
+ tabs: e,
25
+ defaultIndex: i = 0,
26
+ activeIndex: r,
27
+ onChange: l,
28
+ persistKey: d,
29
+ tabHeaderAlignment: j = "left",
30
+ headerFontSize: f,
31
+ headerClassName: $,
32
+ headerStyle: I
33
+ }) => {
34
+ const s = E(), u = r !== void 0, o = !u && d, [g, b] = S(
35
+ () => (o ? x(o, e) : null) ?? i
36
+ ), L = u ? r : g, p = T(e);
37
+ if (p.current = e, k(() => {
38
+ if (!o) return;
39
+ const t = () => {
40
+ const n = x(o, p.current);
41
+ n != null && b(n);
42
+ };
43
+ return window.addEventListener("popstate", t), () => window.removeEventListener("popstate", t);
44
+ }, [o]), e.length === 0) return null;
45
+ const c = Math.min(Math.max(L, 0), e.length - 1), w = (t) => {
46
+ u || b(t), o && N(o, e[t].label), l == null || l(t);
47
+ }, P = {
48
+ ...f != null ? { fontSize: f } : {},
49
+ ...I
50
+ }, R = (t) => {
51
+ var m;
52
+ if (t.key !== "ArrowRight" && t.key !== "ArrowLeft") return;
53
+ t.preventDefault();
54
+ const n = t.key === "ArrowRight" ? 1 : -1, a = (c + n + e.length) % e.length;
55
+ w(a), (m = document.getElementById(`${s}-tab-${a}`)) == null || m.focus();
56
+ };
57
+ return /* @__PURE__ */ h("div", { className: "jcTabs flex w-full flex-col", children: [
58
+ /* @__PURE__ */ v(
59
+ "div",
60
+ {
61
+ role: "tablist",
62
+ className: y(
63
+ "jcTabList flex flex-wrap gap-1 border-b-2 border-jc-gray-2",
64
+ A[j]
65
+ ),
66
+ children: e.map((t, n) => {
67
+ const a = n === c;
68
+ return /* @__PURE__ */ h(
69
+ "button",
70
+ {
71
+ id: `${s}-tab-${n}`,
72
+ type: "button",
73
+ role: "tab",
74
+ "aria-selected": a,
75
+ "aria-controls": `${s}-panel-${n}`,
76
+ tabIndex: a ? 0 : -1,
77
+ "data-active": a,
78
+ onClick: () => w(n),
79
+ onKeyDown: R,
80
+ style: P,
81
+ className: y(
82
+ // The lib's CSS omits Tailwind preflight, so reset the native button
83
+ // border explicitly (none on top/sides) and keep only the bottom indicator.
84
+ "jcTab -mb-0.5 inline-flex cursor-pointer appearance-none items-center gap-2 bg-transparent px-5 py-3",
85
+ "border-solid border-t-0 border-x-0 border-b-[3px] border-b-[color:transparent]",
86
+ "text-[27px] font-normal text-jc-black transition-[color,border-color] duration-200",
87
+ // Active indicator + focus ring track the app's selected theme color
88
+ // (--jc-theme-color, injected by DefaultTemplate); jc-gold (#fab62d) is the
89
+ // fallback outside a themed app. Keep these strings literal — Tailwind only
90
+ // scans complete class strings, so the color can't be interpolated.
91
+ "data-[active=true]:font-semibold data-[active=true]:border-b-[color:var(--jc-theme-color,#fab62d)]",
92
+ "focus-visible:rounded-[4px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--jc-theme-color,#fab62d)]",
93
+ $
94
+ ),
95
+ children: [
96
+ t.icon,
97
+ t.label
98
+ ]
99
+ },
100
+ n
101
+ );
102
+ })
103
+ }
104
+ ),
105
+ /* @__PURE__ */ v(
106
+ "div",
107
+ {
108
+ role: "tabpanel",
109
+ id: `${s}-panel-${c}`,
110
+ "aria-labelledby": `${s}-tab-${c}`,
111
+ tabIndex: 0,
112
+ className: "jcTabPanel pt-6",
113
+ children: e[c].content
114
+ }
115
+ )
116
+ ] });
117
+ };
118
+ export {
119
+ M as Tabs,
120
+ M as default
121
+ };
@@ -0,0 +1 @@
1
+ export { default, type TabsProps, type TabItem } from './Tabs';
package/Tabs/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { Tabs as f } from "./Tabs.js";
2
+ export {
3
+ f as default
4
+ };
package/api.d.ts CHANGED
@@ -1,11 +1,22 @@
1
1
  export interface ApiResponse<T> {
2
2
  success: boolean;
3
3
  data?: T | null;
4
- error?: {
5
- status: number;
6
- statusText: string;
7
- message: string;
8
- };
4
+ error?: ApiError;
5
+ }
6
+ /**
7
+ * Error shape for a failed request. `message` is always a best-effort human-readable string
8
+ * (back-compat for `message`-only consumers). The remaining fields mirror an RFC 7807 / 9457
9
+ * ProblemDetails body when the server sends one — `errors` carries field-level validation
10
+ * messages (field key → messages), e.g. for surfacing inline form errors.
11
+ */
12
+ export interface ApiError {
13
+ status: number;
14
+ statusText: string;
15
+ message: string;
16
+ detail?: string;
17
+ title?: string;
18
+ traceId?: string;
19
+ errors?: Record<string, string[]>;
9
20
  }
10
21
  /**
11
22
  * Base API client for JCIT applications.
package/api.js CHANGED
@@ -1,70 +1,84 @@
1
- var T = Object.defineProperty;
2
- var h = (s, e, r) => e in s ? T(s, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : s[e] = r;
3
- var m = (s, e, r) => h(s, typeof e != "symbol" ? e + "" : e, r);
4
- class x {
1
+ var E = Object.defineProperty;
2
+ var w = (a, e, i) => e in a ? E(a, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : a[e] = i;
3
+ var o = (a, e, i) => w(a, typeof e != "symbol" ? e + "" : e, i);
4
+ import { firstProblemMessage as v } from "./problemDetails.js";
5
+ class I {
5
6
  constructor(e) {
6
7
  // `protected` (not `private`): subclasses may need to read or override the URL
7
8
  // (e.g., versioned-API subclass that prepends '/v2'). Not `public`: external
8
9
  // code shouldn't be able to read or mutate the base URL after construction.
9
- m(this, "baseUrl");
10
+ o(this, "baseUrl");
10
11
  if (!e)
11
12
  throw new Error(
12
13
  "BaseApiClient: baseUrl is required. Pass import.meta.env.VITE_API_BASE_URL or equivalent."
13
14
  );
14
15
  this.baseUrl = e.endsWith("/") ? e.slice(0, -1) : e;
15
16
  }
16
- async request(e, r = "GET", n, a) {
17
- const o = (a == null ? void 0 : a.timeoutMs) ?? 3e4, l = new AbortController(), d = setTimeout(() => l.abort(), o);
17
+ async request(e, i = "GET", l, c) {
18
+ const f = (c == null ? void 0 : c.timeoutMs) ?? 3e4, m = new AbortController(), T = setTimeout(() => m.abort(), f);
18
19
  try {
19
- const f = {
20
- method: r,
20
+ const g = {
21
+ method: i,
21
22
  headers: {
22
- ...n !== void 0 ? { "Content-Type": "application/json" } : {},
23
- ...a == null ? void 0 : a.headers
23
+ ...l !== void 0 ? { "Content-Type": "application/json" } : {},
24
+ ...c == null ? void 0 : c.headers
24
25
  },
25
26
  credentials: "include",
26
27
  // Required for Windows Authentication with CORS
27
- signal: l.signal
28
+ signal: m.signal
28
29
  };
29
- n !== void 0 && (f.body = JSON.stringify(n));
30
- const t = await fetch(`${this.baseUrl}${e}`, f);
31
- if (clearTimeout(d), !t.ok)
30
+ l !== void 0 && (g.body = JSON.stringify(l));
31
+ const r = await fetch(`${this.baseUrl}${e}`, g);
32
+ if (clearTimeout(T), !r.ok) {
33
+ const h = await r.text();
34
+ let t;
35
+ try {
36
+ t = h ? JSON.parse(h) : void 0;
37
+ } catch {
38
+ t = void 0;
39
+ }
40
+ const x = v(t == null ? void 0 : t.errors);
32
41
  return {
33
42
  success: !1,
34
43
  error: {
35
- status: t.status,
36
- statusText: t.statusText,
37
- message: `HTTP ${t.status}: ${t.statusText}`
44
+ status: r.status,
45
+ statusText: r.statusText || (t == null ? void 0 : t.title) || "",
46
+ message: (t == null ? void 0 : t.message) || (t == null ? void 0 : t.detail) || (t == null ? void 0 : t.title) || x || `HTTP ${r.status}: ${r.statusText}`,
47
+ detail: t == null ? void 0 : t.detail,
48
+ title: t == null ? void 0 : t.title,
49
+ traceId: t == null ? void 0 : t.traceId,
50
+ errors: t == null ? void 0 : t.errors
38
51
  }
39
52
  };
40
- if (t.status === 204)
53
+ }
54
+ if (r.status === 204)
41
55
  return {
42
56
  success: !0,
43
57
  data: void 0
44
58
  };
45
- const c = await t.text();
46
- if (!c || c === "null")
59
+ const s = await r.text();
60
+ if (!s || s === "null")
47
61
  return {
48
62
  success: !0,
49
63
  data: null
50
64
  };
51
- let i;
65
+ let d;
52
66
  try {
53
- i = JSON.parse(c);
67
+ d = JSON.parse(s);
54
68
  } catch {
55
- i = c;
69
+ d = s;
56
70
  }
57
71
  return {
58
72
  success: !0,
59
- data: i
73
+ data: d
60
74
  };
61
75
  } catch (u) {
62
- return clearTimeout(d), u instanceof Error && u.name === "AbortError" ? {
76
+ return clearTimeout(T), u instanceof Error && u.name === "AbortError" ? {
63
77
  success: !1,
64
78
  error: {
65
79
  status: 0,
66
80
  statusText: "Timeout",
67
- message: `Request timed out after ${o}ms`
81
+ message: `Request timed out after ${f}ms`
68
82
  }
69
83
  } : {
70
84
  success: !1,
@@ -78,5 +92,5 @@ class x {
78
92
  }
79
93
  }
80
94
  export {
81
- x as BaseApiClient
95
+ I as BaseApiClient
82
96
  };