jcicl 1.0.60 → 1.0.62

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.
@@ -0,0 +1,24 @@
1
+ import { DataPageControls, DataPageResult } from './useDataPage';
2
+ import { WithLoadingProps } from '../WithLoading/WithLoading';
3
+ export interface DataPageProps<T> {
4
+ /** Async function that fetches data. Throw for fatal error. Return withWarnings() for partial success. */
5
+ fetchFn: () => Promise<T | DataPageResult<T>>;
6
+ /** useEffect dependency array. Default: [] (fetch once on mount). */
7
+ deps?: unknown[];
8
+ /** Render function for happy path. Receives data + controls. Read-only pages can ignore the 2nd arg. */
9
+ children: (data: T, controls: DataPageControls<T>) => React.ReactNode;
10
+ /** Shown on empty data. String → centered <p>. ReactNode → rendered directly. Omit for interactive empty. */
11
+ emptyState?: React.ReactNode | string;
12
+ /** Shown on fatal error. String → centered red <p>. Defaults to the caught error message. */
13
+ errorState?: React.ReactNode | string;
14
+ /** Data-dependent guards. Each receives data, returns ReactNode to short-circuit or null to continue. */
15
+ guards?: ((data: T) => React.ReactNode | null)[];
16
+ /** Determines if data is "empty". Default: array length === 0 or nullish. */
17
+ isEmpty?: (data: T) => boolean;
18
+ /** Props forwarded to the internal WithLoading wrapper (e.g. size, style). */
19
+ loadingProps?: Omit<WithLoadingProps, 'loading' | 'children'>;
20
+ /** Custom warning banner renderer. Default: orange centered <p> per warning. Pass null to suppress. */
21
+ renderWarnings?: ((warnings: string[]) => React.ReactNode) | null;
22
+ }
23
+ declare function DataPage<T>({ fetchFn, deps, children, emptyState, errorState, guards, isEmpty, loadingProps, renderWarnings, }: DataPageProps<T>): import("react/jsx-runtime").JSX.Element;
24
+ export default DataPage;
@@ -0,0 +1,34 @@
1
+ import { jsx as e, Fragment as i, jsxs as A } from "react/jsx-runtime";
2
+ import B, { defaultIsEmpty as P } from "./useDataPage.js";
3
+ import k from "../WithLoading/WithLoading.js";
4
+ const c = (n, t) => typeof n == "string" ? /* @__PURE__ */ e("p", { style: { textAlign: "center", color: t }, children: n }) : /* @__PURE__ */ e(i, { children: n }), v = (n) => n.map((t, o) => /* @__PURE__ */ e("p", { style: { textAlign: "center", color: "darkorange" }, children: t }, o));
5
+ function L({
6
+ fetchFn: n,
7
+ deps: t,
8
+ children: o,
9
+ emptyState: s,
10
+ errorState: d,
11
+ guards: u,
12
+ isEmpty: m = P,
13
+ loadingProps: p,
14
+ renderWarnings: f
15
+ }) {
16
+ const { data: r, loading: l, error: g, warnings: a, setData: x, refetch: y } = B({ fetchFn: n, deps: t }), D = { setData: x, refetch: y, warnings: a };
17
+ if (!l && g)
18
+ return /* @__PURE__ */ e(i, { children: c(d || g, "red") });
19
+ if (!l && r !== null && m(r) && s !== void 0)
20
+ return /* @__PURE__ */ e(i, { children: c(s) });
21
+ if (!l && r !== null && u)
22
+ for (const w of u) {
23
+ const h = w(r);
24
+ if (h !== null) return /* @__PURE__ */ e(i, { children: h });
25
+ }
26
+ const j = a.length > 0 && f !== null ? (f ?? v)(a) : null;
27
+ return /* @__PURE__ */ A(k, { loading: l, ...p, children: [
28
+ j,
29
+ r !== null ? o(r, D) : null
30
+ ] });
31
+ }
32
+ export {
33
+ L as default
34
+ };
@@ -0,0 +1,2 @@
1
+ export { default, type DataPageProps } from './DataPage';
2
+ export { default as useDataPage, withWarnings, type UseDataPageOptions, type UseDataPageReturn, type DataPageControls, type DataPageResult, } from './useDataPage';
@@ -0,0 +1,7 @@
1
+ import { default as t } from "./DataPage.js";
2
+ import { default as f, withWarnings as o } from "./useDataPage.js";
3
+ export {
4
+ t as default,
5
+ f as useDataPage,
6
+ o as withWarnings
7
+ };
@@ -0,0 +1,33 @@
1
+ declare const DATA_PAGE_RESULT: unique symbol;
2
+ export interface DataPageResult<T> {
3
+ [DATA_PAGE_RESULT]: true;
4
+ data: T;
5
+ warnings: string[];
6
+ }
7
+ /** Wrap a fetchFn return value to signal partial success with warnings. */
8
+ export declare function withWarnings<T>(data: T, warnings: string[]): DataPageResult<T>;
9
+ export interface DataPageControls<T> {
10
+ /** Update local data without re-fetching. For optimistic CRUD (add/edit/delete). */
11
+ setData: (updater: (prev: T) => T) => void;
12
+ /** Re-run fetchFn from scratch (resets to loading state). */
13
+ refetch: () => void;
14
+ /** Warnings from partial success (empty array if none). */
15
+ warnings: string[];
16
+ }
17
+ export interface UseDataPageOptions<T> {
18
+ /** Async function that fetches data. Throw for fatal error. Return withWarnings() for partial success. */
19
+ fetchFn: () => Promise<T | DataPageResult<T>>;
20
+ /** useEffect dependency array. Default: [] (fetch once on mount). */
21
+ deps?: unknown[];
22
+ }
23
+ export interface UseDataPageReturn<T> {
24
+ data: T | null;
25
+ loading: boolean;
26
+ error: string | null;
27
+ warnings: string[];
28
+ setData: (updater: (prev: T) => T) => void;
29
+ refetch: () => void;
30
+ }
31
+ export declare const defaultIsEmpty: <T>(data: T) => boolean;
32
+ declare function useDataPage<T>({ fetchFn, deps }: UseDataPageOptions<T>): UseDataPageReturn<T>;
33
+ export default useDataPage;
@@ -0,0 +1,46 @@
1
+ import { useState as r, useRef as R, useEffect as F, useCallback as d } from "react";
2
+ const g = Symbol("DataPageResult");
3
+ function P(t, u) {
4
+ return { [g]: !0, data: t, warnings: u };
5
+ }
6
+ const S = (t) => t == null ? !0 : Array.isArray(t) ? t.length === 0 : !1;
7
+ function W({ fetchFn: t, deps: u = [] }) {
8
+ const [h, c] = r(null), [y, i] = r(!0), [w, a] = r(null), [m, f] = r([]), [A, E] = r(0), l = R(t);
9
+ l.current = t, F(() => {
10
+ let n = !1;
11
+ return i(!0), a(null), f([]), (async () => {
12
+ try {
13
+ const e = await l.current();
14
+ if (n) return;
15
+ if (e === void 0) {
16
+ a("fetchFn returned undefined — did you forget a return statement?");
17
+ return;
18
+ }
19
+ if (e && typeof e == "object" && g in e) {
20
+ const o = e;
21
+ if (o.data === void 0) {
22
+ a("fetchFn returned undefined data inside withWarnings()");
23
+ return;
24
+ }
25
+ c(o.data), f(o.warnings);
26
+ } else
27
+ c(e);
28
+ } catch (e) {
29
+ n || a(e instanceof Error ? e.message : "An unexpected error occurred");
30
+ } finally {
31
+ n || i(!1);
32
+ }
33
+ })(), () => {
34
+ n = !0;
35
+ };
36
+ }, [...u, A]);
37
+ const p = d((n) => {
38
+ c((s) => s !== null ? n(s) : s);
39
+ }, []), D = d(() => E((n) => n + 1), []);
40
+ return { data: h, loading: y, error: w, warnings: m, setData: p, refetch: D };
41
+ }
42
+ export {
43
+ W as default,
44
+ S as defaultIsEmpty,
45
+ P as withWarnings
46
+ };
@@ -19,6 +19,8 @@ export type DefaultTemplateProps = AppContainerProps & {
19
19
  hasPagination?: boolean;
20
20
  fade?: boolean;
21
21
  siteBannerText?: string;
22
+ siteBannerBackgroundColor?: string;
23
+ siteBannerTextColor?: string;
22
24
  };
23
25
  declare const DefaultTemplate: React.FC<PropsWithChildren<DefaultTemplateProps>>;
24
26
  export default DefaultTemplate;
@@ -1,25 +1,25 @@
1
- import { jsx as r, jsxs as g, Fragment as $ } from "react/jsx-runtime";
2
- import { useState as S, useMemo as z } from "react";
1
+ import { jsx as r, jsxs as g, Fragment as O } from "react/jsx-runtime";
2
+ import { useState as b, useMemo as A } from "react";
3
3
  import { n as y } from "../.chunks/emotion-styled.browser.esm.js";
4
4
  import { c as C } from "../.chunks/emotion-react.browser.esm.js";
5
- import t, { defaultTheme as T } from "../theme.js";
5
+ import o, { defaultTheme as T } from "../theme.js";
6
6
  import { getHueFromHex as u, updateTheme as p } from "../themeUtils.js";
7
- import { ThemeContext as O } from "../ThemeContext.js";
8
- import A from "../AppContainer/AppContainer.js";
9
- import L from "../WithLoading/WithLoading.js";
10
- import { TopFade as j, BottomFade as D } from "../Overlays/Overlays.js";
11
- import { SiteBanner as W } from "../SiteBanner/SiteBanner.js";
12
- import { P as B } from "../.chunks/AppHeader.js";
13
- import { u as b } from "../.chunks/useMediaQuery.js";
14
- const H = ["loading", "hasPagination", "loadingColor", "hasFade", "isTablet"], G = y("div", { shouldForwardProp: (o) => !H.includes(o) })(({ loading: o, hasPagination: i, hasFade: m, loadingColor: l = t.colors.green }) => ({
7
+ import { ThemeContext as L } from "../ThemeContext.js";
8
+ import j from "../AppContainer/AppContainer.js";
9
+ import D from "../WithLoading/WithLoading.js";
10
+ import { TopFade as W, BottomFade as B } from "../Overlays/Overlays.js";
11
+ import { SiteBanner as H } from "../SiteBanner/SiteBanner.js";
12
+ import { P as G } from "../.chunks/AppHeader.js";
13
+ import { u as S } from "../.chunks/useMediaQuery.js";
14
+ const Q = ["loading", "hasPagination", "loadingColor", "hasFade", "isTablet"], N = y("div", { shouldForwardProp: (t) => !Q.includes(t) })(({ loading: t, hasPagination: i, hasFade: m, loadingColor: l = o.colors.green }) => ({
15
15
  ...C`
16
- color: ${t.colors.midnight};
17
- height: ${o ? "100%" : "auto"};
16
+ color: ${o.colors.midnight};
17
+ height: ${t ? "100%" : "auto"};
18
18
  padding: 19px;
19
19
  padding-top: 36px;
20
20
  display: flex;
21
21
  flex-direction: column;
22
- justify-content: ${o ? "center" : "start"};
22
+ justify-content: ${t ? "center" : "start"};
23
23
  align-items: center;
24
24
  width: 100%;
25
25
  min-height: 100%;
@@ -33,7 +33,7 @@ const H = ["loading", "hasPagination", "loadingColor", "hasFade", "isTablet"], G
33
33
  color: ${l};
34
34
  }
35
35
  `
36
- })), Q = y("div")(() => ({
36
+ })), q = y("div")(() => ({
37
37
  ...C`
38
38
  border-radius: 8px;
39
39
  padding: 0;
@@ -47,25 +47,27 @@ const H = ["loading", "hasPagination", "loadingColor", "hasFade", "isTablet"], G
47
47
  box-sizing: border-box;
48
48
  `
49
49
  }));
50
- function N(o, i) {
51
- return o == "green" ? T : p(i);
50
+ function E(t, i) {
51
+ return t == "green" ? T : p(i);
52
52
  }
53
- const oe = ({
54
- children: o,
53
+ const ie = ({
54
+ children: t,
55
55
  loading: i = !1,
56
56
  hasPagination: m = !1,
57
57
  fade: l = !0,
58
58
  siteBannerText: n,
59
- userProfileMenuItems: w,
59
+ siteBannerBackgroundColor: w,
60
+ siteBannerTextColor: k,
61
+ userProfileMenuItems: I,
60
62
  ...s
61
63
  }) => {
62
- const I = localStorage.getItem("theme") || "green", f = localStorage.getItem("hue") || u(t.themeOptions.green).toString(), x = b(`(max-width: ${t.screenSizes.mobile})`), M = b(`(max-width: ${t.screenSizes.tablet})`), [e, d] = S(
63
- () => N(I, parseFloat(f))
64
- ), [P, c] = S({ h: Math.max(parseFloat(f), 0) * 360, s: 0, v: 68, a: 1 }), k = [
64
+ const M = localStorage.getItem("theme") || "green", f = localStorage.getItem("hue") || u(o.themeOptions.green).toString(), x = S(`(max-width: ${o.screenSizes.mobile})`), P = S(`(max-width: ${o.screenSizes.tablet})`), [e, d] = b(
65
+ () => E(M, parseFloat(f))
66
+ ), [v, c] = b({ h: Math.max(parseFloat(f), 0) * 360, s: 0, v: 68, a: 1 }), F = [
65
67
  {
66
68
  label: "Default Theme",
67
69
  onClick: () => {
68
- localStorage.setItem("theme", "green"), localStorage.setItem("hue", u(t.themeOptions.green).toString()), c((h) => ({ ...h, h: u(t.themeOptions.green) * 360 })), d(T);
70
+ localStorage.setItem("theme", "green"), localStorage.setItem("hue", u(o.themeOptions.green).toString()), c((h) => ({ ...h, h: u(o.themeOptions.green) * 360 })), d(T);
69
71
  }
70
72
  },
71
73
  {
@@ -78,15 +80,15 @@ const oe = ({
78
80
  },
79
81
  {
80
82
  label: "Hue",
81
- hue: P.h,
83
+ hue: v.h,
82
84
  onChange: (a) => {
83
- localStorage.setItem("theme", "custom"), localStorage.setItem("hue", (a.h / 360).toString()), c((F) => ({ ...F, h: a.h }));
85
+ localStorage.setItem("theme", "custom"), localStorage.setItem("hue", (a.h / 360).toString()), c((z) => ({ ...z, h: a.h }));
84
86
  const h = p(a.h / 360);
85
87
  d(h);
86
88
  }
87
89
  },
88
- ...w || []
89
- ], v = z(
90
+ ...I || []
91
+ ], $ = A(
90
92
  () => ({
91
93
  backgroundPrimary: e.themeLight,
92
94
  backgroundSecondary: e.themeLightA,
@@ -100,9 +102,9 @@ const oe = ({
100
102
  scrollColor: e.themeShadowA,
101
103
  loadingColor: e.themeColor,
102
104
  iconColors: {
103
- icon: /* @__PURE__ */ r(B, {}),
104
- iconColor: e.themeDark ?? t.colors.darkGreen,
105
- backgroundColor: e.themeMediumA ?? t.colors.mint
105
+ icon: /* @__PURE__ */ r(G, {}),
106
+ iconColor: e.themeDark ?? o.colors.darkGreen,
107
+ backgroundColor: e.themeMediumA ?? o.colors.mint
106
108
  },
107
109
  highlightPrimary: e.highlightPrimary,
108
110
  highlightSecondary: e.highlightSecondary,
@@ -110,30 +112,37 @@ const oe = ({
110
112
  }),
111
113
  [e]
112
114
  );
113
- return /* @__PURE__ */ g(O.Provider, { value: e, children: [
114
- n && /* @__PURE__ */ r(W, { text: n }),
115
+ return /* @__PURE__ */ g(L.Provider, { value: e, children: [
116
+ n && /* @__PURE__ */ r(
117
+ H,
118
+ {
119
+ text: n,
120
+ backgroundColor: w,
121
+ textColor: k
122
+ }
123
+ ),
115
124
  /* @__PURE__ */ r(
116
- A,
125
+ j,
117
126
  {
118
127
  ...s,
119
- userProfileMenuItems: k,
120
- themeColors: v ?? (s == null ? void 0 : s.themeColors),
128
+ userProfileMenuItems: F,
129
+ themeColors: $ ?? (s == null ? void 0 : s.themeColors),
121
130
  hasSiteBanner: !!n,
122
131
  children: /* @__PURE__ */ r(
123
- G,
132
+ N,
124
133
  {
125
134
  className: "jcPageWrapper",
126
135
  loading: i,
127
136
  loadingColor: e.themeColor,
128
137
  hasPagination: m,
129
138
  hasFade: l,
130
- isTablet: M,
131
- children: /* @__PURE__ */ g(L, { loading: i, size: x ? 114 : 201, children: [
132
- l && /* @__PURE__ */ g($, { children: [
133
- /* @__PURE__ */ r(j, { color: e.themeLight, hasSiteBanner: !!n }),
134
- /* @__PURE__ */ r(D, { hasPagination: m, isMobile: x, color: e.themeLight })
139
+ isTablet: P,
140
+ children: /* @__PURE__ */ g(D, { loading: i, size: x ? 114 : 201, children: [
141
+ l && /* @__PURE__ */ g(O, { children: [
142
+ /* @__PURE__ */ r(W, { color: e.themeLight, hasSiteBanner: !!n }),
143
+ /* @__PURE__ */ r(B, { hasPagination: m, isMobile: x, color: e.themeLight })
135
144
  ] }),
136
- /* @__PURE__ */ r(Q, { children: o })
145
+ /* @__PURE__ */ r(q, { children: t })
137
146
  ] })
138
147
  }
139
148
  )
@@ -142,7 +151,7 @@ const oe = ({
142
151
  ] });
143
152
  };
144
153
  export {
145
- Q as MainContentContainer,
146
- G as PageWrapper,
147
- oe as default
154
+ q as MainContentContainer,
155
+ N as PageWrapper,
156
+ ie as default
148
157
  };
@@ -1,6 +1,8 @@
1
1
  import { default as React } from 'react';
2
2
  export interface SiteBannerProps {
3
3
  text: string;
4
+ backgroundColor?: string;
5
+ textColor?: string;
4
6
  }
5
7
  export declare const SiteBanner: React.FC<SiteBannerProps>;
6
8
  export default SiteBanner;
@@ -2,8 +2,8 @@ import { jsx as e } from "react/jsx-runtime";
2
2
  import { n as t } from "../.chunks/emotion-styled.browser.esm.js";
3
3
  import { c as r } from "../.chunks/emotion-react.browser.esm.js";
4
4
  import n from "../theme.js";
5
- import { useThemeColors as i } from "../ThemeContext.js";
6
- const a = t("div")(({ backgroundColor: o }) => ({
5
+ import { useThemeColors as c } from "../ThemeContext.js";
6
+ const m = t("div")(({ $backgroundColor: o }) => ({
7
7
  ...r`
8
8
  width: 100%;
9
9
  height: 54px;
@@ -14,20 +14,20 @@ const a = t("div")(({ backgroundColor: o }) => ({
14
14
  justify-content: center;
15
15
  box-sizing: border-box;
16
16
  `
17
- })), c = t("span")(() => ({
17
+ })), l = t("span")(({ $textColor: o }) => ({
18
18
  ...r`
19
- color: ${n.colors.white};
19
+ color: ${o};
20
20
  font-family: Roboto, sans-serif;
21
21
  font-size: 19px;
22
22
  font-weight: 500;
23
23
  text-align: center;
24
24
  text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.5);
25
25
  `
26
- })), f = ({ text: o }) => {
27
- const s = i();
28
- return /* @__PURE__ */ e(a, { backgroundColor: s.themeColor, children: /* @__PURE__ */ e(c, { children: o }) });
26
+ })), b = ({ text: o, backgroundColor: s, textColor: i }) => {
27
+ const a = c();
28
+ return /* @__PURE__ */ e(m, { $backgroundColor: s ?? a.themeColor, children: /* @__PURE__ */ e(l, { $textColor: i ?? n.colors.white, children: o }) });
29
29
  };
30
30
  export {
31
- f as SiteBanner,
32
- f as default
31
+ b as SiteBanner,
32
+ b as default
33
33
  };
package/api.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ export interface ApiResponse<T> {
2
+ success: boolean;
3
+ data?: T | null;
4
+ error?: {
5
+ status: number;
6
+ statusText: string;
7
+ message: string;
8
+ };
9
+ }
10
+ /**
11
+ * Base API client for JCIT applications.
12
+ *
13
+ * Provides a generic `request<T>()` method with timeout, JSON parsing,
14
+ * and standardized error wrapping into `ApiResponse<T>`.
15
+ *
16
+ * Consumers extend this class, pass the base URL via `super()`, and
17
+ * add app-specific endpoint methods:
18
+ *
19
+ * ```typescript
20
+ * import { BaseApiClient } from 'jcicl/api';
21
+ *
22
+ * class MyAppApi extends BaseApiClient {
23
+ * constructor() {
24
+ * super(import.meta.env.VITE_API_BASE_URL);
25
+ * }
26
+ * async getItems() {
27
+ * return this.request<Item[]>('/api/Items/GetItems');
28
+ * }
29
+ * }
30
+ *
31
+ * export const api = new MyAppApi();
32
+ * ```
33
+ */
34
+ export declare class BaseApiClient {
35
+ protected baseUrl: string;
36
+ constructor(baseUrl: string);
37
+ protected request<T>(endpoint: string, method?: 'GET' | 'POST' | 'PUT' | 'DELETE', body?: unknown, options?: {
38
+ timeoutMs?: number;
39
+ headers?: Record<string, string>;
40
+ }): Promise<ApiResponse<T>>;
41
+ }
package/api.js ADDED
@@ -0,0 +1,79 @@
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 {
5
+ constructor(e) {
6
+ m(this, "baseUrl");
7
+ if (!e)
8
+ throw new Error(
9
+ "BaseApiClient: baseUrl is required. Pass import.meta.env.VITE_API_BASE_URL or equivalent."
10
+ );
11
+ this.baseUrl = e.endsWith("/") ? e.slice(0, -1) : e;
12
+ }
13
+ async request(e, r = "GET", n, a) {
14
+ const o = (a == null ? void 0 : a.timeoutMs) ?? 3e4, l = new AbortController(), d = setTimeout(() => l.abort(), o);
15
+ try {
16
+ const f = {
17
+ method: r,
18
+ headers: {
19
+ ...n !== void 0 ? { "Content-Type": "application/json" } : {},
20
+ ...a == null ? void 0 : a.headers
21
+ },
22
+ credentials: "include",
23
+ // Required for Windows Authentication with CORS
24
+ signal: l.signal
25
+ };
26
+ n !== void 0 && (f.body = JSON.stringify(n));
27
+ const t = await fetch(`${this.baseUrl}${e}`, f);
28
+ if (clearTimeout(d), !t.ok)
29
+ return {
30
+ success: !1,
31
+ error: {
32
+ status: t.status,
33
+ statusText: t.statusText,
34
+ message: `HTTP ${t.status}: ${t.statusText}`
35
+ }
36
+ };
37
+ if (t.status === 204)
38
+ return {
39
+ success: !0,
40
+ data: void 0
41
+ };
42
+ const c = await t.text();
43
+ if (!c || c === "null")
44
+ return {
45
+ success: !0,
46
+ data: null
47
+ };
48
+ let i;
49
+ try {
50
+ i = JSON.parse(c);
51
+ } catch {
52
+ i = c;
53
+ }
54
+ return {
55
+ success: !0,
56
+ data: i
57
+ };
58
+ } catch (u) {
59
+ return clearTimeout(d), u instanceof Error && u.name === "AbortError" ? {
60
+ success: !1,
61
+ error: {
62
+ status: 0,
63
+ statusText: "Timeout",
64
+ message: `Request timed out after ${o}ms`
65
+ }
66
+ } : {
67
+ success: !1,
68
+ error: {
69
+ status: 0,
70
+ statusText: "Network Error",
71
+ message: u instanceof Error ? u.message : "Unknown error occurred"
72
+ }
73
+ };
74
+ }
75
+ }
76
+ }
77
+ export {
78
+ x as BaseApiClient
79
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jcicl",
3
3
  "private": false,
4
- "version": "1.0.60",
4
+ "version": "1.0.62",
5
5
  "description": "Component library for the websites of Johnson County Iowa",
6
6
  "license": "MIT",
7
7
  "homepage": "https://devops.jc.net/JCIT/Business%20Solutions%20Delivery/_git/JCComponentLibrary?path=%2FREADME.md&version=GBmaster",