jcicl 1.2.7 → 1.2.8

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.
@@ -1,11 +1,11 @@
1
- import { jsxs as i, jsx as r } from "react/jsx-runtime";
1
+ import { jsxs as o, jsx as t } from "react/jsx-runtime";
2
2
  import { n as a } from "../.chunks/emotion-styled.browser.esm.js";
3
3
  import { c as n } from "../.chunks/emotion-react.browser.esm.js";
4
- import t from "../theme.js";
4
+ import s from "../theme.js";
5
5
  import { useThemeColors as d } from "../ThemeContext.js";
6
6
  import { T as l } from "../.chunks/TextField.js";
7
7
  const m = a("div", { shouldForwardProp: (e) => !["customTheme"].includes(e) })(({ customTheme: e }) => {
8
- var o;
8
+ var r;
9
9
  return {
10
10
  ...n`
11
11
  display: flex;
@@ -23,11 +23,11 @@ const m = a("div", { shouldForwardProp: (e) => !["customTheme"].includes(e) })((
23
23
  textarea {
24
24
  &:hover,
25
25
  :focus {
26
- box-shadow: ${(o = t.boxShadows.green) == null ? void 0 : o.split("#")[0]} ${e.themeColor} inset;
26
+ box-shadow: ${(r = s.boxShadows.green) == null ? void 0 : r.split("#")[0]} ${e.themeColor} inset;
27
27
  }
28
28
  width: 100%;
29
29
  padding: 13px;
30
- border: 1px solid ${t.colors.gray};
30
+ border: 1px solid ${s.colors.gray};
31
31
  border-radius: 9px;
32
32
  resize: vertical;
33
33
  min-height: 130px;
@@ -48,11 +48,14 @@ const m = a("div", { shouldForwardProp: (e) => !["customTheme"].includes(e) })((
48
48
  }
49
49
  `
50
50
  };
51
- }), b = ({ label: e, ...o }) => {
52
- const s = d();
53
- return /* @__PURE__ */ i(m, { className: "jcLabeledTextArea", customTheme: s, children: [
54
- e && /* @__PURE__ */ r("span", { children: e }),
55
- /* @__PURE__ */ r(l, { ...o, multiline: !0, fullWidth: !0, rows: 1 })
51
+ }), b = ({ label: e, ...r }) => {
52
+ const i = d();
53
+ return /* @__PURE__ */ o(m, { className: "jcLabeledTextArea", customTheme: i, children: [
54
+ e && /* @__PURE__ */ o("span", { children: [
55
+ e,
56
+ r.required && /* @__PURE__ */ t("span", { className: "mx-0.5 text-jc-gray", children: "*" })
57
+ ] }),
58
+ /* @__PURE__ */ t(l, { ...r, multiline: !0, fullWidth: !0, rows: 1 })
56
59
  ] });
57
60
  };
58
61
  export {
@@ -0,0 +1,42 @@
1
+ export interface TimeSlotOption {
2
+ /** Form value for the slot (e.g. `'14:30'`). */
3
+ value: string;
4
+ /** Display label for the pill (e.g. `'2:30 PM'`). */
5
+ label: string;
6
+ /**
7
+ * Marks the currently scheduled time (e.g. when rescheduling). Rendered as a thicker
8
+ * theme-accent border — no extra text, so every pill keeps the same size.
9
+ */
10
+ current?: boolean;
11
+ }
12
+ export interface TimeSlotPickerProps {
13
+ /** Slots to render as selectable pills, in display order. */
14
+ options: TimeSlotOption[];
15
+ /** Value of the currently selected slot. */
16
+ value?: string;
17
+ onChange: (value: string) => void;
18
+ /** Bold heading above the grid (e.g. `'Available Times'`). */
19
+ title?: string;
20
+ loading?: boolean;
21
+ /** Shown centered in the grid when `options` is empty and not loading. */
22
+ emptyText?: string;
23
+ /** Error display: red border + red helper text. */
24
+ error?: boolean;
25
+ helperText?: string;
26
+ /**
27
+ * Fixed height of the scrolling pill area. The default matches LabeledTextArea's
28
+ * rendered box (130px content min-height + 13px padding ×2 + 1px border ×2) so the
29
+ * picker can sit beside a message box with identical border heights.
30
+ */
31
+ height?: string;
32
+ className?: string;
33
+ }
34
+ /**
35
+ * Calendly-style time-slot selector: a scrolling pill grid of appointment times
36
+ * (library ScrollContainer scrollbar). Purely presentational — the consumer fetches
37
+ * availability (e.g. from JC.Scheduling) and maps it to `options`. Single-select,
38
+ * keyboard accessible per the ARIA radio-group pattern (one tab stop, arrow keys move
39
+ * and select), themed via the app's custom theme accent.
40
+ */
41
+ export declare const TimeSlotPicker: React.FC<TimeSlotPickerProps>;
42
+ export default TimeSlotPicker;
@@ -0,0 +1,78 @@
1
+ import { jsxs as w, jsx as r } from "react/jsx-runtime";
2
+ import { useRef as j } from "react";
3
+ import { cn as s } from "../cn.js";
4
+ import N from "../ScrollContainer/ScrollContainer.js";
5
+ import { useThemeColors as k } from "../ThemeContext.js";
6
+ const S = ({
7
+ options: t,
8
+ value: n,
9
+ onChange: i,
10
+ title: o,
11
+ loading: f = !1,
12
+ emptyText: h = "No available times.",
13
+ error: d = !1,
14
+ helperText: m,
15
+ height: v = "158px",
16
+ className: x
17
+ }) => {
18
+ const p = { "--slot-accent": k().themeColor }, u = j([]), b = t.findIndex((e) => e.value === n), g = b >= 0 ? b : 0, y = (e, l) => {
19
+ var c;
20
+ let a;
21
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") a = (l + 1) % t.length;
22
+ else if (e.key === "ArrowLeft" || e.key === "ArrowUp")
23
+ a = (l - 1 + t.length) % t.length;
24
+ else return;
25
+ e.preventDefault(), i(t[a].value), (c = u.current[a]) == null || c.focus();
26
+ };
27
+ return (
28
+ // gap/radius/border mirror LabeledTextArea (3px label gap, 9px radius, gray border)
29
+ // so the picker pairs seamlessly beside a message box.
30
+ /* @__PURE__ */ w("div", { className: s("flex flex-col gap-[3px] font-['Roboto',sans-serif]", x), style: p, children: [
31
+ o && /* @__PURE__ */ r("span", { className: "font-bold", children: o }),
32
+ /* @__PURE__ */ r("div", { className: s("rounded-[9px] border bg-white", d ? "border-jc-error" : "border-jc-gray"), children: /* @__PURE__ */ r(N, { height: v, direction: "vertical", autoHide: "leave", children: /* @__PURE__ */ r(
33
+ "div",
34
+ {
35
+ role: "radiogroup",
36
+ "aria-label": o ?? "Available times",
37
+ className: "grid grid-cols-2 content-start gap-2 p-2 md:grid-cols-3",
38
+ children: f ? /* @__PURE__ */ r("span", { className: "text-jc-gray-3 col-span-full animate-pulse p-2 text-sm", children: "Loading available times…" }) : t.length === 0 ? /* @__PURE__ */ r("span", { className: "text-jc-gray-3 col-span-full p-2 text-sm", children: h }) : t.map((e, l) => {
39
+ const a = e.value === n;
40
+ return /* @__PURE__ */ r(
41
+ "button",
42
+ {
43
+ ref: (c) => {
44
+ u.current[l] = c;
45
+ },
46
+ type: "button",
47
+ role: "radio",
48
+ "aria-checked": a,
49
+ tabIndex: l === g ? 0 : -1,
50
+ onKeyDown: (c) => y(c, l),
51
+ onClick: () => i(e.value),
52
+ title: e.current ? "Current appointment time" : void 0,
53
+ "aria-label": e.current ? `${e.label} (current appointment time)` : e.label,
54
+ className: s(
55
+ "cursor-pointer rounded-md border px-2 py-1.5 text-center text-sm font-medium transition-all duration-150",
56
+ "focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--slot-accent)]",
57
+ a ? "border-transparent bg-[var(--slot-accent)] text-white shadow-sm" : s(
58
+ "text-jc-charcoal bg-white hover:border-[var(--slot-accent)] hover:text-[var(--slot-accent)] hover:shadow-sm",
59
+ // The current slot reads as a 2px accent border: 1px real border +
60
+ // 1px inset shadow, so its box size matches every other pill.
61
+ e.current ? "border-[var(--slot-accent)] shadow-[inset_0_0_0_1px_var(--slot-accent)]" : "border-jc-gray-4"
62
+ )
63
+ ),
64
+ children: e.label
65
+ },
66
+ e.value
67
+ );
68
+ })
69
+ }
70
+ ) }) }),
71
+ m && /* @__PURE__ */ r("span", { className: s("text-right text-xs", d ? "text-jc-error" : "text-jc-gray-3"), children: m })
72
+ ] })
73
+ );
74
+ };
75
+ export {
76
+ S as TimeSlotPicker,
77
+ S as default
78
+ };
@@ -0,0 +1 @@
1
+ export { default, TimeSlotPicker, type TimeSlotPickerProps, type TimeSlotOption } from './TimeSlotPicker';
@@ -0,0 +1,5 @@
1
+ import { TimeSlotPicker as o, TimeSlotPicker as r } from "./TimeSlotPicker.js";
2
+ export {
3
+ o as TimeSlotPicker,
4
+ r as default
5
+ };